From 57ca3da24f26164104aecbcebf345cfcfac17a66 Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Wed, 28 Mar 2018 12:36:45 -0700 Subject: [PATCH] Add support for testing mode root certificate. 1) Add Certificate 2) Helper class for end-to-end tests 3) Only create snapshot for passwords with special prefix in test mode 4) Sync only keys with insecure prefix in test mode. Bug: 76433465 Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner Change-Id: I6edc8c4716c3a034b6b79c7aa6f4b8478e9a3c9e --- .../recovery/TrustedRootCertificates.java | 70 +++++++++- .../recoverablekeystore/KeySyncTask.java | 44 +++--- .../RecoverableKeyStoreManager.java | 48 +++---- .../TestOnlyInsecureCertificateHelper.java | 103 ++++++++++++++ .../recoverablekeystore/KeySyncTaskTest.java | 118 +++++++++++++++- .../RecoverableKeyStoreManagerTest.java | 108 ++++++++++++++- .../recoverablekeystore/TestData.java | 10 ++ ...TestOnlyInsecureCertificateHelperTest.java | 128 ++++++++++++++++++ 8 files changed, 567 insertions(+), 62 deletions(-) create mode 100644 services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java create mode 100644 services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java diff --git a/core/java/android/security/keystore/recovery/TrustedRootCertificates.java b/core/java/android/security/keystore/recovery/TrustedRootCertificates.java index 383af424989c4..63faac3d525d3 100644 --- a/core/java/android/security/keystore/recovery/TrustedRootCertificates.java +++ b/core/java/android/security/keystore/recovery/TrustedRootCertificates.java @@ -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" @@ -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; @@ -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); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java index 050c1f4a6141c..b0afac2c62ddc 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -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, @@ -98,7 +99,8 @@ public class KeySyncTask implements Runnable { credentialType, credential, credentialUpdated, - PlatformKeyManager.getInstance(context, recoverableKeyStoreDb)); + PlatformKeyManager.getInstance(context, recoverableKeyStoreDb), + new TestOnlyInsecureCertificateHelper()); } /** @@ -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( @@ -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; @@ -129,6 +133,7 @@ public class KeySyncTask implements Runnable { mCredentialUpdated = credentialUpdated; mPlatformKeyManager = platformKeyManager; mRecoverySnapshotStorage = snapshotStorage; + mTestOnlyInsecureCertificateHelper = TestOnlyInsecureCertificateHelper; } @Override @@ -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) { @@ -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(); @@ -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(); @@ -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; - } } 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 77d7c3cfdbf2f..1dab5920288b4 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -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; @@ -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. @@ -130,7 +130,8 @@ public class RecoverableKeyStoreManager { RecoverySnapshotStorage.newInstance(), new RecoverySnapshotListenersStorage(), platformKeyManager, - applicationKeyStorage); + applicationKeyStorage, + new TestOnlyInsecureCertificateHelper()); } return mInstance; } @@ -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; @@ -153,6 +155,7 @@ public class RecoverableKeyStoreManager { mSnapshotStorage = snapshotStorage; mPlatformKeyManager = platformKeyManager; mApplicationKeyStorage = applicationKeyStorage; + mTestCertHelper = TestOnlyInsecureCertificateHelper; try { mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase); @@ -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. @@ -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); @@ -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"); @@ -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) { @@ -519,7 +526,8 @@ public class RecoverableKeyStoreManager { @NonNull List 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"); @@ -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()); @@ -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, diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java new file mode 100644 index 0000000000000..490f733f149f0 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java @@ -0,0 +1,103 @@ +/* + * 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 keepOnlyWhitelistedInsecureKeys(Map rawKeys) { + if (rawKeys == null) { + return null; + } + Map filteredKeys = new HashMap<>(); + for (Map.Entry 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; + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java index 81a73efd94add..b8d2c3e3d5b08 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java @@ -33,6 +33,11 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -42,8 +47,8 @@ import android.os.FileUtils; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; -import android.security.keystore.recovery.KeyDerivationParams; import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyDerivationParams; import android.security.keystore.recovery.RecoveryController; import android.security.keystore.recovery.WrappedApplicationKey; import android.support.test.InstrumentationRegistry; @@ -59,6 +64,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.io.File; import java.nio.charset.StandardCharsets; @@ -94,6 +100,7 @@ public class KeySyncTaskTest { @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage; + @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; private RecoverySnapshotStorage mRecoverySnapshotStorage; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; @@ -130,7 +137,8 @@ public class KeySyncTaskTest { TEST_CREDENTIAL_TYPE, TEST_CREDENTIAL, /*credentialUpdated=*/ false, - mPlatformKeyManager); + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper); mWrappingKey = generateAndroidKeyStoreKey(); mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey); @@ -283,6 +291,100 @@ public class KeySyncTaskTest { assertNotNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID)); } + @Test + public void run_InTestModeWithWhitelistedCredentials() throws Exception { + mRecoverableKeyStoreDb.setServerParams( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); + mRecoverableKeyStoreDb.setRecoveryServiceCertPath( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); + + // Enter test mode with whitelisted credentials + when(mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(any())).thenReturn(true); + when(mTestOnlyInsecureCertificateHelper.doesCredentailSupportInsecureMode(anyInt(), any())) + .thenReturn(true); + mKeySyncTask.run(); + + verify(mTestOnlyInsecureCertificateHelper) + .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS)); + + // run whitelist checks + verify(mTestOnlyInsecureCertificateHelper) + .doesCredentailSupportInsecureMode(anyInt(), any()); + verify(mTestOnlyInsecureCertificateHelper) + .keepOnlyWhitelistedInsecureKeys(any()); + + KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); + assertNotNull(keyChainSnapshot); // created snapshot + List applicationKeys = keyChainSnapshot.getWrappedApplicationKeys(); + assertThat(applicationKeys).hasSize(0); // non whitelisted key is not included + } + + @Test + public void run_InTestModeWithNonWhitelistedCredentials() throws Exception { + mRecoverableKeyStoreDb.setServerParams( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); + mRecoverableKeyStoreDb.setRecoveryServiceCertPath( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); + + // Enter test mode with non whitelisted credentials + when(mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(any())).thenReturn(true); + when(mTestOnlyInsecureCertificateHelper.doesCredentailSupportInsecureMode(anyInt(), any())) + .thenReturn(false); + mKeySyncTask.run(); + + assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID)); // not created + verify(mTestOnlyInsecureCertificateHelper) + .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS)); + verify(mTestOnlyInsecureCertificateHelper) + .doesCredentailSupportInsecureMode(anyInt(), any()); + } + + @Test + public void run_doesNotFilterCredentialsAndAliasesInProd() throws Exception { + mRecoverableKeyStoreDb.setServerParams( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); + mRecoverableKeyStoreDb.setRecoveryServiceCertPath( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); + + mKeySyncTask.run(); + assertNotNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID)); + + verify(mTestOnlyInsecureCertificateHelper) + .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS)); + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .isTestOnlyCertificate(eq(TEST_ROOT_CERT_ALIAS)); + + // no whitelists check + verify(mTestOnlyInsecureCertificateHelper, never()) + .doesCredentailSupportInsecureMode(anyInt(), any()); + verify(mTestOnlyInsecureCertificateHelper, never()) + .keepOnlyWhitelistedInsecureKeys(any()); + } + + @Test + public void run_replacesNullActiveRootAliasWithDefaultValue() throws Exception { + mRecoverableKeyStoreDb.setServerParams( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); + mRecoverableKeyStoreDb.setRecoveryServiceCertPath( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); + mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, + /*alias=*/ null); + + when(mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(null)) + .thenReturn(TEST_ROOT_CERT_ALIAS); // override default. + mKeySyncTask.run(); + + verify(mTestOnlyInsecureCertificateHelper).getDefaultCertificateAliasIfEmpty(null); + } + @Test public void run_sendsEncryptedKeysIfAvailableToSync_withRawPublicKey() throws Exception { mRecoverableKeyStoreDb.setRecoveryServiceCertPath( @@ -398,7 +500,8 @@ public class KeySyncTaskTest { CREDENTIAL_TYPE_PASSWORD, "password", /*credentialUpdated=*/ false, - mPlatformKeyManager); + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper); mRecoverableKeyStoreDb.setRecoveryServiceCertPath( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); @@ -424,7 +527,8 @@ public class KeySyncTaskTest { CREDENTIAL_TYPE_PASSWORD, /*credential=*/ "1234", /*credentialUpdated=*/ false, - mPlatformKeyManager); + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper); mRecoverableKeyStoreDb.setRecoveryServiceCertPath( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); @@ -451,7 +555,8 @@ public class KeySyncTaskTest { CREDENTIAL_TYPE_PATTERN, "12345", /*credentialUpdated=*/ false, - mPlatformKeyManager); + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper); mRecoverableKeyStoreDb.setRecoveryServiceCertPath( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); @@ -532,7 +637,8 @@ public class KeySyncTaskTest { /*credentialType=*/ 3, "12345", /*credentialUpdated=*/ false, - mPlatformKeyManager); + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper); addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); 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 18a3885aea6ad..a98e29137fb6e 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 @@ -27,6 +27,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -67,6 +68,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.io.File; import java.nio.charset.StandardCharsets; @@ -93,6 +95,8 @@ public class RecoverableKeyStoreManagerTest { private static final String ROOT_CERTIFICATE_ALIAS = ""; private static final String DEFAULT_ROOT_CERT_ALIAS = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; + private static final String INSECURE_CERTIFICATE_ALIAS = + TrustedRootCertificates.TEST_ONLY_INSECURE_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, @@ -160,6 +164,7 @@ public class RecoverableKeyStoreManagerTest { @Mock private KeyguardManager mKeyguardManager; @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private ApplicationKeyStorage mApplicationKeyStorage; + @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; private File mDatabaseFile; @@ -195,7 +200,8 @@ public class RecoverableKeyStoreManagerTest { mRecoverySnapshotStorage, mMockListenersStorage, mPlatformKeyManager, - mApplicationKeyStorage); + mApplicationKeyStorage, + mTestOnlyInsecureCertificateHelper); } @After @@ -300,6 +306,9 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, TestData.getCertXmlWithSerial(certSerial)); + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getDefaultCertificateAliasIfEmpty(ROOT_CERTIFICATE_ALIAS); + assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1); @@ -308,6 +317,67 @@ public class RecoverableKeyStoreManagerTest { assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); } + @Test + public void initRecoveryService_triesToFilterRootAlias() throws Exception { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + long certSerial = 1000L; + mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); + + mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, + TestData.getCertXmlWithSerial(certSerial)); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getDefaultCertificateAliasIfEmpty(eq(ROOT_CERTIFICATE_ALIAS)); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); + + String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid); + assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); + + } + + @Test + public void initRecoveryService_usesProdCertificateForEmptyRootAlias() throws Exception { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + long certSerial = 1000L; + mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); + + mRecoverableKeyStoreManager.initRecoveryService(/*rootCertificateAlias=*/ "", + TestData.getCertXmlWithSerial(certSerial)); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getDefaultCertificateAliasIfEmpty(eq("")); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); + + String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid); + assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); + } + + @Test + public void initRecoveryService_usesProdCertificateForNullRootAlias() throws Exception { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + long certSerial = 1000L; + mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); + + mRecoverableKeyStoreManager.initRecoveryService(/*rootCertificateAlias=*/ null, + TestData.getCertXmlWithSerial(certSerial)); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getDefaultCertificateAliasIfEmpty(null); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); + + String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid); + assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); + } + @Test public void initRecoveryService_regeneratesCounterId() throws Exception { int uid = Binder.getCallingUid(); @@ -416,6 +486,24 @@ public class RecoverableKeyStoreManagerTest { assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); } + @Test + public void initRecoveryServiceWithSigFile_usesProdCertificateForNullRootAlias() + throws Exception { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + long certSerial = 1000L; + mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); + + mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( + /*rootCertificateAlias=*/null, TestData.getCertXml(), TestData.getSigXml()); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getDefaultCertificateAliasIfEmpty(null); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); + } + @Test public void initRecoveryServiceWithSigFile_throwsIfNullCertFile() throws Exception { try { @@ -452,6 +540,18 @@ public class RecoverableKeyStoreManagerTest { } } + @Test + public void initRecoveryServiceWithSigFile_throwsIfTestAliasUsedWithProdCert() + throws Exception { + try { + mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( + INSECURE_CERTIFICATE_ALIAS, TestData.getCertXml(), TestData.getSigXml()); + fail("should have thrown"); + } catch (ServiceSpecificException e) { + assertThat(e.getMessage()).contains("signature over the cert file is invalid"); + } + } + @Test public void initRecoveryServiceWithSigFile_throwsIfInvalidFileSignature() throws Exception { byte[] modifiedCertXml = TestData.getCertXml(); @@ -712,7 +812,8 @@ public class RecoverableKeyStoreManagerTest { } @Test - public void recoverKeyChainSnapshot_throwsIfFailedToDecryptAllApplicationKeys() throws Exception { + public void recoverKeyChainSnapshot_throwsIfFailedToDecryptAllApplicationKeys() + throws Exception { mRecoverableKeyStoreManager.startRecoverySession( TEST_SESSION_ID, TEST_PUBLIC_KEY, @@ -792,7 +893,8 @@ public class RecoverableKeyStoreManagerTest { } @Test - public void recoverKeyChainSnapshot_worksOnOtherApplicationKeysIfOneDecryptionFails() throws Exception { + public void recoverKeyChainSnapshot_worksOnOtherApplicationKeysIfOneDecryptionFails() + throws Exception { mRecoverableKeyStoreManager.startRecoverySession( TEST_SESSION_ID, TEST_PUBLIC_KEY, diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java index 4b059c665b54f..9b2c853d6b6fb 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java @@ -14,8 +14,12 @@ import java.security.cert.CertificateFactory; import java.security.cert.CertPath; import java.security.spec.ECPrivateKeySpec; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + public final class TestData { + private static final String KEY_ALGORITHM = "AES"; private static final long DEFAULT_SERIAL = 1000; private static final String CERT_PATH_ENCODING = "PkiPath"; @@ -308,4 +312,10 @@ public final class TestData { KeyFactory keyFactory = KeyFactory.getInstance("EC"); return keyFactory.generatePrivate(new ECPrivateKeySpec(priv, SecureBox.EC_PARAM_SPEC)); } + + public static SecretKey generateKey() throws Exception { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); + keyGenerator.init(/*keySize=*/ 256); + return keyGenerator.generateKey(); + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java new file mode 100644 index 0000000000000..bc50c9e487e8f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java @@ -0,0 +1,128 @@ +package com.android.server.locksettings.recoverablekeystore; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.security.keystore.recovery.TrustedRootCertificates; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; + +import com.android.internal.widget.LockPatternUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.security.cert.X509Certificate; +import java.util.Map; +import javax.crypto.SecretKey; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TestOnlyInsecureCertificateHelperTest { + private final TestOnlyInsecureCertificateHelper mHelper + = new TestOnlyInsecureCertificateHelper(); + + @Test + public void testDoesCredentailSupportInsecureMode_forNonWhitelistedPassword() throws Exception { + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, "secret12345")).isFalse(); + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, "1234")).isFalse(); + } + + @Test + public void testDoesCredentailSupportInsecureMode_forWhitelistedPassword() throws Exception { + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + TrustedRootCertificates.INSECURE_PASSWORD_PREFIX)).isTrue(); + + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + TrustedRootCertificates.INSECURE_PASSWORD_PREFIX + "12")).isTrue(); + } + + @Test + public void testDoesCredentailSupportInsecureMode_Pattern() throws Exception { + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_PATTERN, + TrustedRootCertificates.INSECURE_PASSWORD_PREFIX)).isFalse(); + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_NONE, + TrustedRootCertificates.INSECURE_PASSWORD_PREFIX)).isFalse(); + } + + @Test + public void testIsTestOnlyCertificate() throws Exception { + assertThat(mHelper.isTestOnlyCertificate( + TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS)).isFalse(); + assertThat(mHelper.isTestOnlyCertificate( + TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS)).isTrue(); + assertThat(mHelper.isTestOnlyCertificate( + "UNKNOWN_ALIAS")).isFalse(); + } + + @Test + public void testKeepOnlyWhitelistedInsecureKeys_emptyKeysList() throws Exception { + Map rawKeys = new HashMap<>(); + Map expectedResult = new HashMap<>(); + + Map filteredKeys = + mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); + assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet()); + assertThat(filteredKeys.entrySet()).containsAllIn(rawKeys.entrySet()); + } + + @Test + public void testKeepOnlyWhitelistedInsecureKeys_singleNonWhitelistedKey() throws Exception { + Map rawKeys = new HashMap<>(); + Map expectedResult = new HashMap<>(); + + String alias = "secureAlias"; + rawKeys.put(alias, TestData.generateKey()); + + Map filteredKeys = + mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); + assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet()); + assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet()); + } + + @Test + public void testKeepOnlyWhitelistedInsecureKeys_singleWhitelistedKey() throws Exception { + Map rawKeys = new HashMap<>(); + Map expectedResult = new HashMap<>(); + + String alias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX; + rawKeys.put(alias, TestData.generateKey()); + expectedResult.put(alias, rawKeys.get(alias)); + + Map filteredKeys = + mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); + assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet()); + assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet()); + } + + @Test + public void testKeepOnlyWhitelistedInsecureKeys() throws Exception { + Map rawKeys = new HashMap<>(); + Map expectedResult = new HashMap<>(); + + String alias = "SECURE_ALIAS" + TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX; + rawKeys.put(alias, TestData.generateKey()); + + alias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX + "1"; + rawKeys.put(alias, TestData.generateKey()); + expectedResult.put(alias, rawKeys.get(alias)); + + alias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX + "2"; + rawKeys.put(alias, TestData.generateKey()); + expectedResult.put(alias, rawKeys.get(alias)); + + Map filteredKeys = + mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); + assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet()); + assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet()); + } +}