Merge "Use the new root cert file under the core/ folder" into pi-dev

This commit is contained in:
TreeHugger Robot
2018-03-21 12:10:23 +00:00
committed by Android (Google) Code Review
7 changed files with 117 additions and 85 deletions

View File

@@ -136,6 +136,63 @@ public class RecoverySession implements AutoCloseable {
byte[] recoveryClaim =
mRecoveryController.getBinder().startRecoverySessionWithCertPath(
mSessionId,
/*rootCertificateAlias=*/ "", // Use the default root cert
recoveryCertPath,
vaultParams,
vaultChallenge,
secrets);
return recoveryClaim;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT
|| e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) {
throw new CertificateException(e.getMessage());
}
throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Starts a recovery session and returns a blob with proof of recovery secret possession.
* The method generates a symmetric key for a session, which trusted remote device can use to
* return recovery key.
*
* @param rootCertificateAlias The alias of the root certificate that is already in the Android
* OS. The root certificate will be used for validating {@code verifierCertPath}.
* @param verifierCertPath The certificate path used to create the recovery blob on the source
* device. Keystore will verify the certificate path by using the root of trust.
* @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
* Used to limit number of guesses.
* @param vaultChallenge Data passed from server for this recovery session and used to prevent
* replay attacks.
* @param secrets Secrets provided by user, the method only uses type and secret fields.
* @return The recovery claim. Claim provides a b binary blob with recovery claim. It is
* encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric
* key and parameters necessary to identify the counter with the number of failed recovery
* attempts.
* @throws CertificateException if the {@code verifierCertPath} is invalid.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
@NonNull public byte[] start(
@NonNull String rootCertificateAlias,
@NonNull CertPath verifierCertPath,
@NonNull byte[] vaultParams,
@NonNull byte[] vaultChallenge,
@NonNull List<KeyChainProtectionParams> secrets)
throws CertificateException, InternalRecoveryServiceException {
// Wrap the CertPath in a Parcelable so it can be passed via Binder calls.
RecoveryCertPath recoveryCertPath =
RecoveryCertPath.createRecoveryCertPath(verifierCertPath);
try {
byte[] recoveryClaim =
mRecoveryController.getBinder().startRecoverySessionWithCertPath(
mSessionId,
rootCertificateAlias,
recoveryCertPath,
vaultParams,
vaultChallenge,

View File

@@ -77,10 +77,27 @@ public class TrustedRootCertificates {
private static final int NUMBER_OF_ROOT_CERTIFICATES = 1;
private static final ArrayMap<String, X509Certificate> ALL_ROOT_CERTIFICATES =
constructRootCertificateMap();
/**
* Returns all available root certificates, keyed by alias.
*/
public static Map<String, X509Certificate> listRootCertificates() {
return new ArrayMap(ALL_ROOT_CERTIFICATES);
}
/**
* Gets a root certificate referenced by the given {@code alias}.
*
* @param alias the alias of the certificate
* @return the certificate referenced by the alias, or null if such a certificate doesn't exist.
*/
public static X509Certificate getRootCertificate(String alias) {
return ALL_ROOT_CERTIFICATES.get(alias);
}
private static ArrayMap<String, X509Certificate> constructRootCertificateMap() {
ArrayMap<String, X509Certificate> certificates =
new ArrayMap<>(NUMBER_OF_ROOT_CERTIFICATES);
certificates.put(

View File

@@ -78,7 +78,7 @@ interface ILockSettings {
byte[] startRecoverySession(in String sessionId,
in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge,
in List<KeyChainProtectionParams> secrets);
byte[] startRecoverySessionWithCertPath(in String sessionId,
byte[] startRecoverySessionWithCertPath(in String sessionId, in String rootCertificateAlias,
in RecoveryCertPath verifierCertPath, in byte[] vaultParams, in byte[] vaultChallenge,
in List<KeyChainProtectionParams> secrets);
Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,

View File

@@ -2051,11 +2051,13 @@ public class LockSettingsService extends ILockSettings.Stub {
@Override
public byte[] startRecoverySessionWithCertPath(@NonNull String sessionId,
@NonNull RecoveryCertPath verifierCertPath, @NonNull byte[] vaultParams,
@NonNull byte[] vaultChallenge, @NonNull List<KeyChainProtectionParams> secrets)
@NonNull String rootCertificateAlias, @NonNull RecoveryCertPath verifierCertPath,
@NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge,
@NonNull List<KeyChainProtectionParams> secrets)
throws RemoteException {
return mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
sessionId, verifierCertPath, vaultParams, vaultChallenge, secrets);
sessionId, rootCertificateAlias, verifierCertPath, vaultParams, vaultChallenge,
secrets);
}
public void closeSession(@NonNull String sessionId) throws RemoteException {

View File

@@ -38,6 +38,7 @@ import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryCertPath;
import android.security.keystore.recovery.RecoveryController;
import android.security.keystore.recovery.TrustedRootCertificates;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.KeyStore;
import android.util.Log;
@@ -50,7 +51,6 @@ import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKe
import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
import com.android.server.locksettings.recoverablekeystore.certificate.CertXml;
import com.android.server.locksettings.recoverablekeystore.certificate.TrustedRootCert;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -64,6 +64,7 @@ import java.security.UnrecoverableKeyException;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
@@ -200,15 +201,19 @@ public class RecoverableKeyStoreManager {
}
Log.i(TAG, "Updating the certificate with the new serial number " + newSerial);
// Randomly choose and validate an endpoint certificate from the list
CertPath certPath;
X509Certificate rootCert = getRootCertificate(rootCertificateAlias);
try {
Log.d(TAG, "Getting and validating a random endpoint certificate");
certPath = certXml.getRandomEndpointCert(TrustedRootCert.TRUSTED_ROOT_CERT);
certPath = certXml.getRandomEndpointCert(rootCert);
} catch (CertValidationException e) {
Log.e(TAG, "Invalid endpoint cert", e);
throw new ServiceSpecificException(
ERROR_INVALID_CERTIFICATE, "Failed to validate certificate.");
}
// Save the chosen and validated certificate into database
try {
Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
if (mDatabase.setRecoveryServiceCertPath(userId, uid, certPath) > 0) {
@@ -253,8 +258,9 @@ public class RecoverableKeyStoreManager {
ERROR_BAD_CERTIFICATE_FORMAT, "Failed to parse the sig file.");
}
X509Certificate rootCert = getRootCertificate(rootCertificateAlias);
try {
sigXml.verifyFileSignature(TrustedRootCert.TRUSTED_ROOT_CERT, recoveryServiceCertFile);
sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile);
} catch (CertValidationException e) {
Log.d(TAG, "The signature over the cert file is invalid."
+ " Cert: " + HexDump.toHexString(recoveryServiceCertFile)
@@ -479,6 +485,7 @@ public class RecoverableKeyStoreManager {
*/
public @NonNull byte[] startRecoverySessionWithCertPath(
@NonNull String sessionId,
@NonNull String rootCertificateAlias,
@NonNull RecoveryCertPath verifierCertPath,
@NonNull byte[] vaultParams,
@NonNull byte[] vaultChallenge,
@@ -495,11 +502,10 @@ public class RecoverableKeyStoreManager {
}
try {
CertUtils.validateCertPath(TrustedRootCert.TRUSTED_ROOT_CERT, certPath);
CertUtils.validateCertPath(getRootCertificate(rootCertificateAlias), certPath);
} catch (CertValidationException e) {
Log.e(TAG, "Failed to validate the given cert path", e);
// TODO: Change this to ERROR_INVALID_CERTIFICATE once ag/3666620 is submitted
throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
}
byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded();
@@ -837,6 +843,21 @@ public class RecoverableKeyStoreManager {
}
}
private X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException {
if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
// Use the default Google Key Vault Service CA certificate if the alias is not provided
rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
}
X509Certificate rootCertificate =
TrustedRootCertificates.getRootCertificate(rootCertificateAlias);
if (rootCertificate == null) {
throw new ServiceSpecificException(
ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid");
}
return rootCertificate;
}
private void checkRecoverKeyStorePermission() {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.RECOVER_KEYSTORE,

View File

@@ -1,74 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.locksettings.recoverablekeystore.certificate;
import java.security.cert.X509Certificate;
/**
* Holds the X509 certificate of the trusted root CA cert for the recoverable key store service.
*
* TODO: Read the certificate from a PEM file directly and remove this class.
*/
public final class TrustedRootCert {
private static final String TRUSTED_ROOT_CERT_BASE64 = ""
+ "MIIFJjCCAw6gAwIBAgIJAIobXsJlzhNdMA0GCSqGSIb3DQEBDQUAMCAxHjAcBgNV"
+ "BAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDAeFw0xODAyMDIxOTM5MTRaFw0zODAx"
+ "MjgxOTM5MTRaMCAxHjAcBgNVBAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDCCAiIw"
+ "DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2OT5i40/H7LINg/lq/0G0hR65P"
+ "Q4Mud3OnuVt6UIYV2T18+v6qW1yJd5FcnND/ZKPau4aUAYklqJuSVjOXQD0BjgS2"
+ "98Xa4dSn8Ci1rUR+5tdmrxqbYUdT2ZvJIUMMR6fRoqi+LlAbKECrV+zYQTyLU68w"
+ "V66hQpAButjJKiZzkXjmKLfJ5IWrNEn17XM988rk6qAQn/BYCCQGf3rQuJeksGmA"
+ "N1lJOwNYxmWUyouVwqwZthNEWqTuEyBFMkAT+99PXW7oVDc7oU5cevuihxQWNTYq"
+ "viGB8cck6RW3cmqrDSaJF/E+N0cXFKyYC7FDcggt6k3UrxNKTuySdDEa8+2RTQqU"
+ "Y9npxBlQE+x9Ig56OI1BG3bSBsGdPgjpyHadZeh2tgk+oqlGsSsum24YxaxuSysT"
+ "Qfcu/XhyfUXavfmGrBOXerTzIl5oBh/F5aHTV85M2tYEG0qsPPvSpZAWtdJ/2rca"
+ "OxvhwOL+leZKr8McjXVR00lBsRuKXX4nTUMwya09CO3QHFPFZtZvqjy2HaMOnVLQ"
+ "I6b6dHEfmsHybzVOe3yPEoFQSU9UhUdmi71kwwoanPD3j9fJHmXTx4PzYYBRf1ZE"
+ "o+uPgMPk7CDKQFZLjnR40z1uzu3O8aZ3AKZzP+j7T4XQKJLQLmllKtPgLgNdJyib"
+ "2Glg7QhXH/jBTL6hAgMBAAGjYzBhMB0GA1UdDgQWBBSbZfrqOYH54EJpkdKMZjMc"
+ "z/Hp+DAfBgNVHSMEGDAWgBSbZfrqOYH54EJpkdKMZjMcz/Hp+DAPBgNVHRMBAf8E"
+ "BTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQ0FAAOCAgEAKh9nm/vW"
+ "glMWp3vcCwWwJW286ecREDlI+CjGh5h+f2N4QRrXd/tKE3qQJWCqGx8sFfIUjmI7"
+ "KYdsC2gyQ2cA2zl0w7pB2QkuqE6zVbnh1D17Hwl19IMyAakFaM9ad4/EoH7oQmqX"
+ "nF/f5QXGZw4kf1HcgKgoCHWXjqR8MqHOcXR8n6WFqxjzJf1jxzi6Yo2dZ7PJbnE6"
+ "+kHIJuiCpiHL75v5g1HM41gT3ddFFSrn88ThNPWItT5Z8WpFjryVzank2Yt02LLl"
+ "WqZg9IC375QULc5B58NMnaiVJIDJQ8zoNgj1yaxqtUMnJX570lotO2OXe4ec9aCQ"
+ "DIJ84YLM/qStFdeZ9416E80dchskbDG04GuVJKlzWjxAQNMRFhyaPUSBTLLg+kwP"
+ "t9+AMmc+A7xjtFQLZ9fBYHOBsndJOmeSQeYeckl+z/1WQf7DdwXn/yijon7mxz4z"
+ "cCczfKwTJTwBh3wR5SQr2vQm7qaXM87qxF8PCAZrdZaw5I80QwkgTj0WTZ2/GdSw"
+ "d3o5SyzzBAjpwtG+4bO/BD9h9wlTsHpT6yWOZs4OYAKU5ykQrncI8OyavMggArh3"
+ "/oM58v0orUWINtIc2hBlka36PhATYQiLf+AiWKnwhCaaHExoYKfQlMtXBodNvOK8"
+ "xqx69x05q/qbHKEcTHrsss630vxrp1niXvA=";
/**
* The X509 certificate of the trusted root CA cert for the recoverable key store service.
*
* TODO: Change it to the production certificate root CA before the final launch.
*/
public static final X509Certificate TRUSTED_ROOT_CERT;
static {
try {
TRUSTED_ROOT_CERT = CertUtils.decodeCert(
CertUtils.decodeBase64(TRUSTED_ROOT_CERT_BASE64));
} catch (CertParsingException e) {
// Should not happen
throw new RuntimeException(e);
}
}
}

View File

@@ -85,7 +85,7 @@ import javax.crypto.spec.SecretKeySpec;
public class RecoverableKeyStoreManagerTest {
private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
private static final String ROOT_CERTIFICATE_ALIAS = "put_default_alias_here";
private static final String ROOT_CERTIFICATE_ALIAS = "";
private static final String TEST_SESSION_ID = "karlin";
private static final byte[] TEST_PUBLIC_KEY = new byte[] {
(byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
@@ -139,6 +139,7 @@ public class RecoverableKeyStoreManagerTest {
private static final String KEY_ALGORITHM = "AES";
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey";
private static final String TEST_ROOT_CERT_ALIAS = "";
@Mock private Context mMockContext;
@Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
@@ -449,10 +450,13 @@ public class RecoverableKeyStoreManagerTest {
eq(Manifest.permission.RECOVER_KEYSTORE), any());
}
// TODO: Add tests for non-existing cert alias
@Test
public void startRecoverySessionWithCertPath_storesTheSessionInfo() throws Exception {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
@@ -474,6 +478,7 @@ public class RecoverableKeyStoreManagerTest {
public void startRecoverySessionWithCertPath_checksPermissionFirst() throws Exception {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
@@ -591,6 +596,7 @@ public class RecoverableKeyStoreManagerTest {
try {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
@@ -609,6 +615,7 @@ public class RecoverableKeyStoreManagerTest {
try {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
vaultParams,
TEST_VAULT_CHALLENGE,
@@ -631,6 +638,7 @@ public class RecoverableKeyStoreManagerTest {
try {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(emptyCertPath),
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
@@ -655,6 +663,7 @@ public class RecoverableKeyStoreManagerTest {
try {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(shortCertPath),
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,