Loading core/java/android/security/keystore/recovery/TrustedRootCertificates.java +67 −3 Original line number Diff line number Diff line Loading @@ -37,6 +37,40 @@ public final class TrustedRootCertificates { public static final String GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS = "GoogleCloudKeyVaultServiceV1"; /** * Certificate used for client-side end-to-end encryption tests. * When recovery controller is initialized with the certificate, recovery snapshots will only * contain application keys started with {@link INSECURE_KEY_ALIAS}. * Recovery snapshot will only be created if device is unlocked with password started with * {@link #INSECURE_PASSWORD_PREFIX}. * * @hide */ public static final String TEST_ONLY_INSECURE_CERTIFICATE_ALIAS = "TEST_ONLY_INSECURE_CERTIFICATE_ALIAS"; /** * TODO: Add insecure certificate to TestApi. * @hide */ public static @NonNull X509Certificate getTestOnlyInsecureCertificate() { return parseBase64Certificate(TEST_ONLY_INSECURE_CERTIFICATE_BASE64); } /** * Keys, which alias starts with the prefix are not protected if * recovery agent uses {@link #TEST_ONLY_INSECURE_CERTIFICATE_ALIAS} root certificate. * @hide */ public static final String INSECURE_KEY_ALIAS_PREFIX = "INSECURE_KEY_ALIAS_KEY_MATERIAL_IS_NOT_PROTECTED_"; /** * Prefix for insecure passwords with length 14. * Passwords started with the prefix are not protected if recovery agent uses * {@link #TEST_ONLY_INSECURE_CERTIFICATE_ALIAS} root certificate. * @hide */ public static final String INSECURE_PASSWORD_PREFIX = "INSECURE_PSWD_"; private static final String GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64 = "" + "MIIFJjCCAw6gAwIBAgIJAIobXsJlzhNdMA0GCSqGSIb3DQEBDQUAMCAxHjAcBgNV" Loading Loading @@ -68,13 +102,43 @@ public final class TrustedRootCertificates { + "/oM58v0orUWINtIc2hBlka36PhATYQiLf+AiWKnwhCaaHExoYKfQlMtXBodNvOK8" + "xqx69x05q/qbHKEcTHrsss630vxrp1niXvA="; private static final String TEST_ONLY_INSECURE_CERTIFICATE_BASE64 = "" + "MIIFMDCCAxigAwIBAgIJAIZ9/G8KQie9MA0GCSqGSIb3DQEBDQUAMCUxIzAhBgNV" + "BAMMGlRlc3QgT25seSBVbnNlY3VyZSBSb290IENBMB4XDTE4MDMyODAwMzIyM1oX" + "DTM4MDMyMzAwMzIyM1owJTEjMCEGA1UEAwwaVGVzdCBPbmx5IFVuc2VjdXJlIFJv" + "b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGxFNzAEyzSPmw" + "E5gfuBXdXq++bl9Ep62V7Xn1UiejvmS+pRHT39pf/M7sl4Zr9ezanJTrFvf9+B85" + "VGehdsD32TgfEjThcqaoQCI6pKkHYsUo7FZ5n+G3eE8oabWRZJMVo3QDjnnFYp7z" + "20vnpjDofI2oQyxHcb/1yep+ca1+4lIvbUp/ybhNFqhRXAMcDXo7pyH38eUQ1JdK" + "Q/QlBbShpFEqx1Y6KilKfTDf7Wenqr67LkaEim//yLZjlHzn/BpuRTrpo+XmJZx1" + "P9CX9LGOXTtmsaCcYgD4yijOvV8aEsIJaf1kCIO558oH0oQc+0JG5aXeLN7BDlyZ" + "vH0RdSx5nQLS9kj2I6nthOw/q00/L+S6A0m5jyNZOAl1SY78p+wO0d9eHbqQzJwf" + "EsSq3qGAqlgQyyjp6oxHBqT9hZtN4rxw+iq0K1S4kmTLNF1FvmIB1BE+lNvvoGdY" + "5G0b6Pe4R5JFn9LV3C3PEmSYnae7iG0IQlKmRADIuvfJ7apWAVanJPJAAWh2Akfp" + "8Uxr02cHoY6o7vsEhJJOeMkipaBHThESm/XeFVubQzNfZ9gjQnB9ZX2v+lyj+WYZ" + "SAz3RuXx6TlLrmWccMpQDR1ibcgyyjLUtX3kwZl2OxmJXitjuD7xlxvAXYob15N+" + "K4xKHgxUDrbt2zU/tY0vgepAUg/xbwIDAQABo2MwYTAdBgNVHQ4EFgQUwyeNpYgs" + "XXYvh9z0/lFrja7sV+swHwYDVR0jBBgwFoAUwyeNpYgsXXYvh9z0/lFrja7sV+sw" + "DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQENBQAD" + "ggIBAGuOsvMN5SD3RIQnMJtBpcHNrxun+QFjPZFlYCLfIPrUkHpn5O1iIIq8tVLd" + "2V+12VKnToUEANsYBD3MP8XjP+6GZ7ZQ2rwLGvUABKSX4YXvmjEEXZUZp0y3tIV4" + "kUDlbACzguPneZDp5Qo7YWH4orgqzHkn0sD/ikO5XrAqmzc245ewJlrf+V11mjcu" + "ELfDrEejpPhi7Hk/ZNR0ftP737Hs/dNoCLCIaVNgYzBZhgo4kd220TeJu2ttW0XZ" + "ldyShtpcOmyWKBgVseixR6L/3sspPHyAPXkSuRo0Eh1xvzDKCg9ttb0qoacTlXMF" + "GkBpNzmVq67NWFGGa9UElift1mv6RfktPCAGZ+Ai8xUiKAUB0Eookpt/8gX9Senq" + "yP/jMxkxXmHWxUu8+KnLvj6WLrfftuuD7u3cfc7j5kkrheDz3O4h4477GnqL5wdo" + "9DuEsNc4FxJVz8Iy8RS6cJuW4pihYpM1Tyn7uopLnImpYzEY+R5aQqqr+q/A1diq" + "ogbEKPH6oUiqJUwq3nD70gPBUKJmIzS4vLwLouqUHEm1k/MgHV/BkEU0uVHszPFa" + "XUMMCHb0iT9P8LuZ7Ajer3SR/0TRVApCrk/6OV68e+6k/OFpM5kcZnNMD5ANyBri" + "Tsz3NrDwSw4i4+Dsfh6A9dB/cEghw4skLaBxnQLQIgVeqCzK"; /** * 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. */ private static final X509Certificate GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_CERTIFICATE = parseGoogleCloudKeyVaultServiceV1Certificate(); parseBase64Certificate(GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64); private static final int NUMBER_OF_ROOT_CERTIFICATES = 1; Loading Loading @@ -107,9 +171,9 @@ public final class TrustedRootCertificates { return certificates; } private static X509Certificate parseGoogleCloudKeyVaultServiceV1Certificate() { private static X509Certificate parseBase64Certificate(String base64Certificate) { try { return decodeBase64Cert(GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64); return decodeBase64Cert(base64Certificate); } catch (CertificateException e) { // Should not happen throw new RuntimeException(e); Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +24 −20 Original line number Diff line number Diff line Loading @@ -79,6 +79,7 @@ public class KeySyncTask implements Runnable { private final PlatformKeyManager mPlatformKeyManager; private final RecoverySnapshotStorage mRecoverySnapshotStorage; private final RecoverySnapshotListenersStorage mSnapshotListenersStorage; private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; public static KeySyncTask newInstance( Context context, Loading @@ -98,7 +99,8 @@ public class KeySyncTask implements Runnable { credentialType, credential, credentialUpdated, PlatformKeyManager.getInstance(context, recoverableKeyStoreDb)); PlatformKeyManager.getInstance(context, recoverableKeyStoreDb), new TestOnlyInsecureCertificateHelper()); } /** Loading @@ -110,6 +112,7 @@ public class KeySyncTask implements Runnable { * @param credential The credential, encoded as a {@link String}. * @param credentialUpdated signals weather credentials were updated. * @param platformKeyManager platform key manager * @param TestOnlyInsecureCertificateHelper utility class used for end-to-end tests */ @VisibleForTesting KeySyncTask( Loading @@ -120,7 +123,8 @@ public class KeySyncTask implements Runnable { int credentialType, String credential, boolean credentialUpdated, PlatformKeyManager platformKeyManager) { PlatformKeyManager platformKeyManager, TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) { mSnapshotListenersStorage = recoverySnapshotListenersStorage; mRecoverableKeyStoreDb = recoverableKeyStoreDb; mUserId = userId; Loading @@ -129,6 +133,7 @@ public class KeySyncTask implements Runnable { mCredentialUpdated = credentialUpdated; mPlatformKeyManager = platformKeyManager; mRecoverySnapshotStorage = snapshotStorage; mTestOnlyInsecureCertificateHelper = TestOnlyInsecureCertificateHelper; } @Override Loading Loading @@ -189,8 +194,9 @@ public class KeySyncTask implements Runnable { PublicKey publicKey; String rootCertAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(mUserId, recoveryAgentUid); rootCertAlias = mTestOnlyInsecureCertificateHelper .getDefaultCertificateAliasIfEmpty(rootCertAlias); rootCertAlias = replaceEmptyValueWithSecureDefault(rootCertAlias); CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId, recoveryAgentUid, rootCertAlias); if (certPath != null) { Loading @@ -212,12 +218,18 @@ public class KeySyncTask implements Runnable { return; } // The only place in this class which uses credential value if (!TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS.equals( rootCertAlias)) { // TODO: allow only whitelisted LSKF usage Log.w(TAG, "Untrusted root certificate is used by recovery agent " if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(rootCertAlias)) { Log.w(TAG, "Insecure root certificate is used by recovery agent " + recoveryAgentUid); if (mTestOnlyInsecureCertificateHelper.doesCredentailSupportInsecureMode( mCredentialType, mCredential)) { Log.w(TAG, "Whitelisted credential is used to generate snapshot by " + "recovery agent "+ recoveryAgentUid); } else { Log.w(TAG, "Non whitelisted credential is used to generate recovery snapshot by " + recoveryAgentUid + " - ignore attempt."); return; // User secret will not be used. } } byte[] salt = generateSalt(); Loading @@ -239,8 +251,10 @@ public class KeySyncTask implements Runnable { return; } // TODO: filter raw keys based on the root of trust. // It is the only place in the class where raw key material is used. // Only include insecure key material for test if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(rootCertAlias)) { rawKeys = mTestOnlyInsecureCertificateHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); } SecretKey recoveryKey; try { recoveryKey = generateRecoveryKey(); Loading Loading @@ -467,14 +481,4 @@ public class KeySyncTask implements Runnable { } return keyEntries; } private @NonNull String replaceEmptyValueWithSecureDefault( @Nullable String rootCertificateAlias) { if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) { Log.e(TAG, "rootCertificateAlias is null or empty"); // Use the default Google Key Vault Service CA certificate if the alias is not provided rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; } return rootCertificateAlias; } } services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +18 −30 Original line number Diff line number Diff line Loading @@ -38,7 +38,6 @@ 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.ArrayMap; Loading Loading @@ -100,6 +99,7 @@ public class RecoverableKeyStoreManager { private final RecoverySnapshotStorage mSnapshotStorage; private final PlatformKeyManager mPlatformKeyManager; private final ApplicationKeyStorage mApplicationKeyStorage; private final TestOnlyInsecureCertificateHelper mTestCertHelper; /** * Returns a new or existing instance. Loading Loading @@ -130,7 +130,8 @@ public class RecoverableKeyStoreManager { RecoverySnapshotStorage.newInstance(), new RecoverySnapshotListenersStorage(), platformKeyManager, applicationKeyStorage); applicationKeyStorage, new TestOnlyInsecureCertificateHelper()); } return mInstance; } Loading @@ -144,7 +145,8 @@ public class RecoverableKeyStoreManager { RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage, PlatformKeyManager platformKeyManager, ApplicationKeyStorage applicationKeyStorage) { ApplicationKeyStorage applicationKeyStorage, TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) { mContext = context; mDatabase = recoverableKeyStoreDb; mRecoverySessionStorage = recoverySessionStorage; Loading @@ -153,6 +155,7 @@ public class RecoverableKeyStoreManager { mSnapshotStorage = snapshotStorage; mPlatformKeyManager = platformKeyManager; mApplicationKeyStorage = applicationKeyStorage; mTestCertHelper = TestOnlyInsecureCertificateHelper; try { mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase); Loading @@ -171,7 +174,8 @@ public class RecoverableKeyStoreManager { checkRecoverKeyStorePermission(); int userId = UserHandle.getCallingUserId(); int uid = Binder.getCallingUid(); rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); rootCertificateAlias = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); // Always set active alias to the argument of the last call to initRecoveryService method, // even if cert file is incorrect. Loading Loading @@ -216,7 +220,8 @@ public class RecoverableKeyStoreManager { // Randomly choose and validate an endpoint certificate from the list CertPath certPath; X509Certificate rootCert = getRootCertificate(rootCertificateAlias); X509Certificate rootCert = mTestCertHelper.getRootCertificate(rootCertificateAlias); try { Log.d(TAG, "Getting and validating a random endpoint certificate"); certPath = certXml.getRandomEndpointCert(rootCert); Loading Loading @@ -265,7 +270,8 @@ public class RecoverableKeyStoreManager { @NonNull byte[] recoveryServiceSigFile) throws RemoteException { checkRecoverKeyStorePermission(); rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); rootCertificateAlias = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); Preconditions.checkNotNull(recoveryServiceCertFile, "recoveryServiceCertFile is null"); Preconditions.checkNotNull(recoveryServiceSigFile, "recoveryServiceSigFile is null"); Loading @@ -279,7 +285,8 @@ public class RecoverableKeyStoreManager { ERROR_BAD_CERTIFICATE_FORMAT, "Failed to parse the sig file."); } X509Certificate rootCert = getRootCertificate(rootCertificateAlias); X509Certificate rootCert = mTestCertHelper.getRootCertificate(rootCertificateAlias); try { sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile); } catch (CertValidationException e) { Loading Loading @@ -519,7 +526,8 @@ public class RecoverableKeyStoreManager { @NonNull List<KeyChainProtectionParams> secrets) throws RemoteException { checkRecoverKeyStorePermission(); rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); rootCertificateAlias = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); Preconditions.checkNotNull(sessionId, "invalid session"); Preconditions.checkNotNull(verifierCertPath, "verifierCertPath is null"); Preconditions.checkNotNull(vaultParams, "vaultParams is null"); Loading @@ -534,7 +542,8 @@ public class RecoverableKeyStoreManager { } try { CertUtils.validateCertPath(getRootCertificate(rootCertificateAlias), certPath); CertUtils.validateCertPath( mTestCertHelper.getRootCertificate(rootCertificateAlias), certPath); } catch (CertValidationException e) { Log.e(TAG, "Failed to validate the given cert path", e); throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); Loading Loading @@ -960,27 +969,6 @@ public class RecoverableKeyStoreManager { } } private X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException { rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); X509Certificate rootCertificate = TrustedRootCertificates.getRootCertificate(rootCertificateAlias); if (rootCertificate == null) { throw new ServiceSpecificException( ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid"); } return rootCertificate; } private @NonNull String replaceEmptyValueWithSecureDefault( @Nullable String rootCertificateAlias) { if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) { Log.e(TAG, "rootCertificateAlias is null or empty"); // Use the default Google Key Vault Service CA certificate if the alias is not provided rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; } return rootCertificateAlias; } private void checkRecoverKeyStorePermission() { mContext.enforceCallingOrSelfPermission( Manifest.permission.RECOVER_KEYSTORE, Loading services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java 0 → 100644 +103 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE; import com.android.internal.widget.LockPatternUtils; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.security.keystore.recovery.TrustedRootCertificates; import android.util.Log; import java.util.HashMap; import java.security.cert.X509Certificate; import java.util.Map; import javax.crypto.SecretKey; /** * The class provides helper methods to support end-to-end test with insecure certificate. */ public class TestOnlyInsecureCertificateHelper { private static final String TAG = "TestCertHelper"; /** * Constructor for the helper class. */ public TestOnlyInsecureCertificateHelper() { } /** * Returns a root certificate installed in the system for given alias. * Returns default secure certificate if alias is empty or null. * Can return insecure certificate for its alias. */ public @NonNull X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException { rootCertificateAlias = getDefaultCertificateAliasIfEmpty(rootCertificateAlias); if (isTestOnlyCertificate(rootCertificateAlias)) { return TrustedRootCertificates.getTestOnlyInsecureCertificate(); } X509Certificate rootCertificate = TrustedRootCertificates.getRootCertificate(rootCertificateAlias); if (rootCertificate == null) { throw new ServiceSpecificException( ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid"); } return rootCertificate; } public @NonNull String getDefaultCertificateAliasIfEmpty( @Nullable String rootCertificateAlias) { if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) { Log.e(TAG, "rootCertificateAlias is null or empty - use secure default value"); // Use the default Google Key Vault Service CA certificate if the alias is not provided rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; } return rootCertificateAlias; } public boolean isTestOnlyCertificate(String rootCertificateAlias) { return TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS .equals(rootCertificateAlias); } public boolean doesCredentailSupportInsecureMode(int credentialType, String credential) { return (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) && (credential != null) && credential.startsWith(TrustedRootCertificates.INSECURE_PASSWORD_PREFIX); } public Map<String, SecretKey> keepOnlyWhitelistedInsecureKeys(Map<String, SecretKey> rawKeys) { if (rawKeys == null) { return null; } Map<String, SecretKey> filteredKeys = new HashMap<>(); for (Map.Entry<String, SecretKey> entry : rawKeys.entrySet()) { String alias = entry.getKey(); if (alias != null && alias.startsWith(TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX)) { filteredKeys.put(entry.getKey(), entry.getValue()); Log.d(TAG, "adding key with insecure alias " + alias + " to the recovery snapshot"); } } return filteredKeys; } } services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +112 −6 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/security/keystore/recovery/TrustedRootCertificates.java +67 −3 Original line number Diff line number Diff line Loading @@ -37,6 +37,40 @@ public final class TrustedRootCertificates { public static final String GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS = "GoogleCloudKeyVaultServiceV1"; /** * Certificate used for client-side end-to-end encryption tests. * When recovery controller is initialized with the certificate, recovery snapshots will only * contain application keys started with {@link INSECURE_KEY_ALIAS}. * Recovery snapshot will only be created if device is unlocked with password started with * {@link #INSECURE_PASSWORD_PREFIX}. * * @hide */ public static final String TEST_ONLY_INSECURE_CERTIFICATE_ALIAS = "TEST_ONLY_INSECURE_CERTIFICATE_ALIAS"; /** * TODO: Add insecure certificate to TestApi. * @hide */ public static @NonNull X509Certificate getTestOnlyInsecureCertificate() { return parseBase64Certificate(TEST_ONLY_INSECURE_CERTIFICATE_BASE64); } /** * Keys, which alias starts with the prefix are not protected if * recovery agent uses {@link #TEST_ONLY_INSECURE_CERTIFICATE_ALIAS} root certificate. * @hide */ public static final String INSECURE_KEY_ALIAS_PREFIX = "INSECURE_KEY_ALIAS_KEY_MATERIAL_IS_NOT_PROTECTED_"; /** * Prefix for insecure passwords with length 14. * Passwords started with the prefix are not protected if recovery agent uses * {@link #TEST_ONLY_INSECURE_CERTIFICATE_ALIAS} root certificate. * @hide */ public static final String INSECURE_PASSWORD_PREFIX = "INSECURE_PSWD_"; private static final String GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64 = "" + "MIIFJjCCAw6gAwIBAgIJAIobXsJlzhNdMA0GCSqGSIb3DQEBDQUAMCAxHjAcBgNV" Loading Loading @@ -68,13 +102,43 @@ public final class TrustedRootCertificates { + "/oM58v0orUWINtIc2hBlka36PhATYQiLf+AiWKnwhCaaHExoYKfQlMtXBodNvOK8" + "xqx69x05q/qbHKEcTHrsss630vxrp1niXvA="; private static final String TEST_ONLY_INSECURE_CERTIFICATE_BASE64 = "" + "MIIFMDCCAxigAwIBAgIJAIZ9/G8KQie9MA0GCSqGSIb3DQEBDQUAMCUxIzAhBgNV" + "BAMMGlRlc3QgT25seSBVbnNlY3VyZSBSb290IENBMB4XDTE4MDMyODAwMzIyM1oX" + "DTM4MDMyMzAwMzIyM1owJTEjMCEGA1UEAwwaVGVzdCBPbmx5IFVuc2VjdXJlIFJv" + "b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGxFNzAEyzSPmw" + "E5gfuBXdXq++bl9Ep62V7Xn1UiejvmS+pRHT39pf/M7sl4Zr9ezanJTrFvf9+B85" + "VGehdsD32TgfEjThcqaoQCI6pKkHYsUo7FZ5n+G3eE8oabWRZJMVo3QDjnnFYp7z" + "20vnpjDofI2oQyxHcb/1yep+ca1+4lIvbUp/ybhNFqhRXAMcDXo7pyH38eUQ1JdK" + "Q/QlBbShpFEqx1Y6KilKfTDf7Wenqr67LkaEim//yLZjlHzn/BpuRTrpo+XmJZx1" + "P9CX9LGOXTtmsaCcYgD4yijOvV8aEsIJaf1kCIO558oH0oQc+0JG5aXeLN7BDlyZ" + "vH0RdSx5nQLS9kj2I6nthOw/q00/L+S6A0m5jyNZOAl1SY78p+wO0d9eHbqQzJwf" + "EsSq3qGAqlgQyyjp6oxHBqT9hZtN4rxw+iq0K1S4kmTLNF1FvmIB1BE+lNvvoGdY" + "5G0b6Pe4R5JFn9LV3C3PEmSYnae7iG0IQlKmRADIuvfJ7apWAVanJPJAAWh2Akfp" + "8Uxr02cHoY6o7vsEhJJOeMkipaBHThESm/XeFVubQzNfZ9gjQnB9ZX2v+lyj+WYZ" + "SAz3RuXx6TlLrmWccMpQDR1ibcgyyjLUtX3kwZl2OxmJXitjuD7xlxvAXYob15N+" + "K4xKHgxUDrbt2zU/tY0vgepAUg/xbwIDAQABo2MwYTAdBgNVHQ4EFgQUwyeNpYgs" + "XXYvh9z0/lFrja7sV+swHwYDVR0jBBgwFoAUwyeNpYgsXXYvh9z0/lFrja7sV+sw" + "DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQENBQAD" + "ggIBAGuOsvMN5SD3RIQnMJtBpcHNrxun+QFjPZFlYCLfIPrUkHpn5O1iIIq8tVLd" + "2V+12VKnToUEANsYBD3MP8XjP+6GZ7ZQ2rwLGvUABKSX4YXvmjEEXZUZp0y3tIV4" + "kUDlbACzguPneZDp5Qo7YWH4orgqzHkn0sD/ikO5XrAqmzc245ewJlrf+V11mjcu" + "ELfDrEejpPhi7Hk/ZNR0ftP737Hs/dNoCLCIaVNgYzBZhgo4kd220TeJu2ttW0XZ" + "ldyShtpcOmyWKBgVseixR6L/3sspPHyAPXkSuRo0Eh1xvzDKCg9ttb0qoacTlXMF" + "GkBpNzmVq67NWFGGa9UElift1mv6RfktPCAGZ+Ai8xUiKAUB0Eookpt/8gX9Senq" + "yP/jMxkxXmHWxUu8+KnLvj6WLrfftuuD7u3cfc7j5kkrheDz3O4h4477GnqL5wdo" + "9DuEsNc4FxJVz8Iy8RS6cJuW4pihYpM1Tyn7uopLnImpYzEY+R5aQqqr+q/A1diq" + "ogbEKPH6oUiqJUwq3nD70gPBUKJmIzS4vLwLouqUHEm1k/MgHV/BkEU0uVHszPFa" + "XUMMCHb0iT9P8LuZ7Ajer3SR/0TRVApCrk/6OV68e+6k/OFpM5kcZnNMD5ANyBri" + "Tsz3NrDwSw4i4+Dsfh6A9dB/cEghw4skLaBxnQLQIgVeqCzK"; /** * 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. */ private static final X509Certificate GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_CERTIFICATE = parseGoogleCloudKeyVaultServiceV1Certificate(); parseBase64Certificate(GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64); private static final int NUMBER_OF_ROOT_CERTIFICATES = 1; Loading Loading @@ -107,9 +171,9 @@ public final class TrustedRootCertificates { return certificates; } private static X509Certificate parseGoogleCloudKeyVaultServiceV1Certificate() { private static X509Certificate parseBase64Certificate(String base64Certificate) { try { return decodeBase64Cert(GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64); return decodeBase64Cert(base64Certificate); } catch (CertificateException e) { // Should not happen throw new RuntimeException(e); Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +24 −20 Original line number Diff line number Diff line Loading @@ -79,6 +79,7 @@ public class KeySyncTask implements Runnable { private final PlatformKeyManager mPlatformKeyManager; private final RecoverySnapshotStorage mRecoverySnapshotStorage; private final RecoverySnapshotListenersStorage mSnapshotListenersStorage; private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; public static KeySyncTask newInstance( Context context, Loading @@ -98,7 +99,8 @@ public class KeySyncTask implements Runnable { credentialType, credential, credentialUpdated, PlatformKeyManager.getInstance(context, recoverableKeyStoreDb)); PlatformKeyManager.getInstance(context, recoverableKeyStoreDb), new TestOnlyInsecureCertificateHelper()); } /** Loading @@ -110,6 +112,7 @@ public class KeySyncTask implements Runnable { * @param credential The credential, encoded as a {@link String}. * @param credentialUpdated signals weather credentials were updated. * @param platformKeyManager platform key manager * @param TestOnlyInsecureCertificateHelper utility class used for end-to-end tests */ @VisibleForTesting KeySyncTask( Loading @@ -120,7 +123,8 @@ public class KeySyncTask implements Runnable { int credentialType, String credential, boolean credentialUpdated, PlatformKeyManager platformKeyManager) { PlatformKeyManager platformKeyManager, TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) { mSnapshotListenersStorage = recoverySnapshotListenersStorage; mRecoverableKeyStoreDb = recoverableKeyStoreDb; mUserId = userId; Loading @@ -129,6 +133,7 @@ public class KeySyncTask implements Runnable { mCredentialUpdated = credentialUpdated; mPlatformKeyManager = platformKeyManager; mRecoverySnapshotStorage = snapshotStorage; mTestOnlyInsecureCertificateHelper = TestOnlyInsecureCertificateHelper; } @Override Loading Loading @@ -189,8 +194,9 @@ public class KeySyncTask implements Runnable { PublicKey publicKey; String rootCertAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(mUserId, recoveryAgentUid); rootCertAlias = mTestOnlyInsecureCertificateHelper .getDefaultCertificateAliasIfEmpty(rootCertAlias); rootCertAlias = replaceEmptyValueWithSecureDefault(rootCertAlias); CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId, recoveryAgentUid, rootCertAlias); if (certPath != null) { Loading @@ -212,12 +218,18 @@ public class KeySyncTask implements Runnable { return; } // The only place in this class which uses credential value if (!TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS.equals( rootCertAlias)) { // TODO: allow only whitelisted LSKF usage Log.w(TAG, "Untrusted root certificate is used by recovery agent " if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(rootCertAlias)) { Log.w(TAG, "Insecure root certificate is used by recovery agent " + recoveryAgentUid); if (mTestOnlyInsecureCertificateHelper.doesCredentailSupportInsecureMode( mCredentialType, mCredential)) { Log.w(TAG, "Whitelisted credential is used to generate snapshot by " + "recovery agent "+ recoveryAgentUid); } else { Log.w(TAG, "Non whitelisted credential is used to generate recovery snapshot by " + recoveryAgentUid + " - ignore attempt."); return; // User secret will not be used. } } byte[] salt = generateSalt(); Loading @@ -239,8 +251,10 @@ public class KeySyncTask implements Runnable { return; } // TODO: filter raw keys based on the root of trust. // It is the only place in the class where raw key material is used. // Only include insecure key material for test if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(rootCertAlias)) { rawKeys = mTestOnlyInsecureCertificateHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); } SecretKey recoveryKey; try { recoveryKey = generateRecoveryKey(); Loading Loading @@ -467,14 +481,4 @@ public class KeySyncTask implements Runnable { } return keyEntries; } private @NonNull String replaceEmptyValueWithSecureDefault( @Nullable String rootCertificateAlias) { if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) { Log.e(TAG, "rootCertificateAlias is null or empty"); // Use the default Google Key Vault Service CA certificate if the alias is not provided rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; } return rootCertificateAlias; } }
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +18 −30 Original line number Diff line number Diff line Loading @@ -38,7 +38,6 @@ 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.ArrayMap; Loading Loading @@ -100,6 +99,7 @@ public class RecoverableKeyStoreManager { private final RecoverySnapshotStorage mSnapshotStorage; private final PlatformKeyManager mPlatformKeyManager; private final ApplicationKeyStorage mApplicationKeyStorage; private final TestOnlyInsecureCertificateHelper mTestCertHelper; /** * Returns a new or existing instance. Loading Loading @@ -130,7 +130,8 @@ public class RecoverableKeyStoreManager { RecoverySnapshotStorage.newInstance(), new RecoverySnapshotListenersStorage(), platformKeyManager, applicationKeyStorage); applicationKeyStorage, new TestOnlyInsecureCertificateHelper()); } return mInstance; } Loading @@ -144,7 +145,8 @@ public class RecoverableKeyStoreManager { RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage, PlatformKeyManager platformKeyManager, ApplicationKeyStorage applicationKeyStorage) { ApplicationKeyStorage applicationKeyStorage, TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) { mContext = context; mDatabase = recoverableKeyStoreDb; mRecoverySessionStorage = recoverySessionStorage; Loading @@ -153,6 +155,7 @@ public class RecoverableKeyStoreManager { mSnapshotStorage = snapshotStorage; mPlatformKeyManager = platformKeyManager; mApplicationKeyStorage = applicationKeyStorage; mTestCertHelper = TestOnlyInsecureCertificateHelper; try { mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase); Loading @@ -171,7 +174,8 @@ public class RecoverableKeyStoreManager { checkRecoverKeyStorePermission(); int userId = UserHandle.getCallingUserId(); int uid = Binder.getCallingUid(); rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); rootCertificateAlias = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); // Always set active alias to the argument of the last call to initRecoveryService method, // even if cert file is incorrect. Loading Loading @@ -216,7 +220,8 @@ public class RecoverableKeyStoreManager { // Randomly choose and validate an endpoint certificate from the list CertPath certPath; X509Certificate rootCert = getRootCertificate(rootCertificateAlias); X509Certificate rootCert = mTestCertHelper.getRootCertificate(rootCertificateAlias); try { Log.d(TAG, "Getting and validating a random endpoint certificate"); certPath = certXml.getRandomEndpointCert(rootCert); Loading Loading @@ -265,7 +270,8 @@ public class RecoverableKeyStoreManager { @NonNull byte[] recoveryServiceSigFile) throws RemoteException { checkRecoverKeyStorePermission(); rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); rootCertificateAlias = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); Preconditions.checkNotNull(recoveryServiceCertFile, "recoveryServiceCertFile is null"); Preconditions.checkNotNull(recoveryServiceSigFile, "recoveryServiceSigFile is null"); Loading @@ -279,7 +285,8 @@ public class RecoverableKeyStoreManager { ERROR_BAD_CERTIFICATE_FORMAT, "Failed to parse the sig file."); } X509Certificate rootCert = getRootCertificate(rootCertificateAlias); X509Certificate rootCert = mTestCertHelper.getRootCertificate(rootCertificateAlias); try { sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile); } catch (CertValidationException e) { Loading Loading @@ -519,7 +526,8 @@ public class RecoverableKeyStoreManager { @NonNull List<KeyChainProtectionParams> secrets) throws RemoteException { checkRecoverKeyStorePermission(); rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); rootCertificateAlias = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); Preconditions.checkNotNull(sessionId, "invalid session"); Preconditions.checkNotNull(verifierCertPath, "verifierCertPath is null"); Preconditions.checkNotNull(vaultParams, "vaultParams is null"); Loading @@ -534,7 +542,8 @@ public class RecoverableKeyStoreManager { } try { CertUtils.validateCertPath(getRootCertificate(rootCertificateAlias), certPath); CertUtils.validateCertPath( mTestCertHelper.getRootCertificate(rootCertificateAlias), certPath); } catch (CertValidationException e) { Log.e(TAG, "Failed to validate the given cert path", e); throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); Loading Loading @@ -960,27 +969,6 @@ public class RecoverableKeyStoreManager { } } private X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException { rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); X509Certificate rootCertificate = TrustedRootCertificates.getRootCertificate(rootCertificateAlias); if (rootCertificate == null) { throw new ServiceSpecificException( ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid"); } return rootCertificate; } private @NonNull String replaceEmptyValueWithSecureDefault( @Nullable String rootCertificateAlias) { if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) { Log.e(TAG, "rootCertificateAlias is null or empty"); // Use the default Google Key Vault Service CA certificate if the alias is not provided rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; } return rootCertificateAlias; } private void checkRecoverKeyStorePermission() { mContext.enforceCallingOrSelfPermission( Manifest.permission.RECOVER_KEYSTORE, Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java 0 → 100644 +103 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE; import com.android.internal.widget.LockPatternUtils; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.security.keystore.recovery.TrustedRootCertificates; import android.util.Log; import java.util.HashMap; import java.security.cert.X509Certificate; import java.util.Map; import javax.crypto.SecretKey; /** * The class provides helper methods to support end-to-end test with insecure certificate. */ public class TestOnlyInsecureCertificateHelper { private static final String TAG = "TestCertHelper"; /** * Constructor for the helper class. */ public TestOnlyInsecureCertificateHelper() { } /** * Returns a root certificate installed in the system for given alias. * Returns default secure certificate if alias is empty or null. * Can return insecure certificate for its alias. */ public @NonNull X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException { rootCertificateAlias = getDefaultCertificateAliasIfEmpty(rootCertificateAlias); if (isTestOnlyCertificate(rootCertificateAlias)) { return TrustedRootCertificates.getTestOnlyInsecureCertificate(); } X509Certificate rootCertificate = TrustedRootCertificates.getRootCertificate(rootCertificateAlias); if (rootCertificate == null) { throw new ServiceSpecificException( ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid"); } return rootCertificate; } public @NonNull String getDefaultCertificateAliasIfEmpty( @Nullable String rootCertificateAlias) { if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) { Log.e(TAG, "rootCertificateAlias is null or empty - use secure default value"); // Use the default Google Key Vault Service CA certificate if the alias is not provided rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; } return rootCertificateAlias; } public boolean isTestOnlyCertificate(String rootCertificateAlias) { return TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS .equals(rootCertificateAlias); } public boolean doesCredentailSupportInsecureMode(int credentialType, String credential) { return (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) && (credential != null) && credential.startsWith(TrustedRootCertificates.INSECURE_PASSWORD_PREFIX); } public Map<String, SecretKey> keepOnlyWhitelistedInsecureKeys(Map<String, SecretKey> rawKeys) { if (rawKeys == null) { return null; } Map<String, SecretKey> filteredKeys = new HashMap<>(); for (Map.Entry<String, SecretKey> entry : rawKeys.entrySet()) { String alias = entry.getKey(); if (alias != null && alias.startsWith(TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX)) { filteredKeys.put(entry.getKey(), entry.getValue()); Log.d(TAG, "adding key with insecure alias " + alias + " to the recovery snapshot"); } } return filteredKeys; } }
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +112 −6 File changed.Preview size limit exceeded, changes collapsed. Show changes