Merge "Add support for testing mode root certificate." into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
2d3e4b7437
@@ -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 = ""
|
||||
+ "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";
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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");
|
||||
@@ -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,
|
||||
|
||||
@@ -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<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;
|
||||
}
|
||||
}
|
||||
@@ -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<WrappedApplicationKey> 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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, SecretKey> rawKeys = new HashMap<>();
|
||||
Map<String, SecretKey> expectedResult = new HashMap<>();
|
||||
|
||||
Map<String, SecretKey> filteredKeys =
|
||||
mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
|
||||
assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
|
||||
assertThat(filteredKeys.entrySet()).containsAllIn(rawKeys.entrySet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeepOnlyWhitelistedInsecureKeys_singleNonWhitelistedKey() throws Exception {
|
||||
Map<String, SecretKey> rawKeys = new HashMap<>();
|
||||
Map<String, SecretKey> expectedResult = new HashMap<>();
|
||||
|
||||
String alias = "secureAlias";
|
||||
rawKeys.put(alias, TestData.generateKey());
|
||||
|
||||
Map<String, SecretKey> filteredKeys =
|
||||
mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
|
||||
assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
|
||||
assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeepOnlyWhitelistedInsecureKeys_singleWhitelistedKey() throws Exception {
|
||||
Map<String, SecretKey> rawKeys = new HashMap<>();
|
||||
Map<String, SecretKey> expectedResult = new HashMap<>();
|
||||
|
||||
String alias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX;
|
||||
rawKeys.put(alias, TestData.generateKey());
|
||||
expectedResult.put(alias, rawKeys.get(alias));
|
||||
|
||||
Map<String, SecretKey> filteredKeys =
|
||||
mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
|
||||
assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
|
||||
assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeepOnlyWhitelistedInsecureKeys() throws Exception {
|
||||
Map<String, SecretKey> rawKeys = new HashMap<>();
|
||||
Map<String, SecretKey> 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<String, SecretKey> filteredKeys =
|
||||
mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
|
||||
assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
|
||||
assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user