diff --git a/core/java/android/security/keystore/recovery/RecoverySession.java b/core/java/android/security/keystore/recovery/RecoverySession.java index 137dd8946c9a5..b44c0e1b27d05 100644 --- a/core/java/android/security/keystore/recovery/RecoverySession.java +++ b/core/java/android/security/keystore/recovery/RecoverySession.java @@ -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 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, diff --git a/core/java/android/security/keystore/recovery/TrustedRootCertificates.java b/core/java/android/security/keystore/recovery/TrustedRootCertificates.java index 27c652278512a..4bdde8a2f5b24 100644 --- a/core/java/android/security/keystore/recovery/TrustedRootCertificates.java +++ b/core/java/android/security/keystore/recovery/TrustedRootCertificates.java @@ -77,10 +77,27 @@ public class TrustedRootCertificates { private static final int NUMBER_OF_ROOT_CERTIFICATES = 1; + private static final ArrayMap ALL_ROOT_CERTIFICATES = + constructRootCertificateMap(); + /** * Returns all available root certificates, keyed by alias. */ public static Map 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 constructRootCertificateMap() { ArrayMap certificates = new ArrayMap<>(NUMBER_OF_ROOT_CERTIFICATES); certificates.put( diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 25e1589db74e0..e572b0e95fde3 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -78,7 +78,7 @@ interface ILockSettings { byte[] startRecoverySession(in String sessionId, in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge, in List 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 secrets); Map/**/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob, diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 74ebf3e44616d..3bda5859682f6 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -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 secrets) + @NonNull String rootCertificateAlias, @NonNull RecoveryCertPath verifierCertPath, + @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, + @NonNull List 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 { diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index 20f3403c56c57..06aa0baa8804a 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -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, diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java deleted file mode 100644 index 7195d628e4bd8..0000000000000 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java +++ /dev/null @@ -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); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 0ceb558627389..0f0ab9fc987ab 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -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,