Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4e2d883e authored by Bo Zhu's avatar Bo Zhu Committed by android-build-merger
Browse files

Merge "Use Scrypt to hash long passwords in RecoverableKeyStore" into pi-dev

am: e73074e9

Change-Id: I50e3ee0083badc478dd9be5c647b351bfd2a840c
parents 30f3e957 e73074e9
Loading
Loading
Loading
Loading
+49 −7
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_L


import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Context;
import android.security.Scrypt;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.KeyDerivationParams;
@@ -69,6 +70,17 @@ public class KeySyncTask implements Runnable {
    private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256";
    private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256";
    private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10;
    private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10;


    // TODO: Reduce the minimal length once all other components are updated
    private static final int MIN_CREDENTIAL_LEN_TO_USE_SCRYPT = 24;
    @VisibleForTesting
    static final int SCRYPT_PARAM_N = 4096;
    @VisibleForTesting
    static final int SCRYPT_PARAM_R = 8;
    @VisibleForTesting
    static final int SCRYPT_PARAM_P = 1;
    @VisibleForTesting
    static final int SCRYPT_PARAM_OUTLEN_BYTES = 32;

    private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
    private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
    private final int mUserId;
    private final int mUserId;
    private final int mCredentialType;
    private final int mCredentialType;
@@ -78,6 +90,7 @@ public class KeySyncTask implements Runnable {
    private final RecoverySnapshotStorage mRecoverySnapshotStorage;
    private final RecoverySnapshotStorage mRecoverySnapshotStorage;
    private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
    private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
    private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
    private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
    private final Scrypt mScrypt;


    public static KeySyncTask newInstance(
    public static KeySyncTask newInstance(
            Context context,
            Context context,
@@ -98,7 +111,8 @@ public class KeySyncTask implements Runnable {
                credential,
                credential,
                credentialUpdated,
                credentialUpdated,
                PlatformKeyManager.getInstance(context, recoverableKeyStoreDb),
                PlatformKeyManager.getInstance(context, recoverableKeyStoreDb),
                new TestOnlyInsecureCertificateHelper());
                new TestOnlyInsecureCertificateHelper(),
                new Scrypt());
    }
    }


    /**
    /**
@@ -110,7 +124,7 @@ public class KeySyncTask implements Runnable {
     * @param credential The credential, encoded as a {@link String}.
     * @param credential The credential, encoded as a {@link String}.
     * @param credentialUpdated signals weather credentials were updated.
     * @param credentialUpdated signals weather credentials were updated.
     * @param platformKeyManager platform key manager
     * @param platformKeyManager platform key manager
     * @param TestOnlyInsecureCertificateHelper utility class used for end-to-end tests
     * @param testOnlyInsecureCertificateHelper utility class used for end-to-end tests
     */
     */
    @VisibleForTesting
    @VisibleForTesting
    KeySyncTask(
    KeySyncTask(
@@ -122,7 +136,8 @@ public class KeySyncTask implements Runnable {
            String credential,
            String credential,
            boolean credentialUpdated,
            boolean credentialUpdated,
            PlatformKeyManager platformKeyManager,
            PlatformKeyManager platformKeyManager,
            TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) {
            TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper,
            Scrypt scrypt) {
        mSnapshotListenersStorage = recoverySnapshotListenersStorage;
        mSnapshotListenersStorage = recoverySnapshotListenersStorage;
        mRecoverableKeyStoreDb = recoverableKeyStoreDb;
        mRecoverableKeyStoreDb = recoverableKeyStoreDb;
        mUserId = userId;
        mUserId = userId;
@@ -131,7 +146,8 @@ public class KeySyncTask implements Runnable {
        mCredentialUpdated = credentialUpdated;
        mCredentialUpdated = credentialUpdated;
        mPlatformKeyManager = platformKeyManager;
        mPlatformKeyManager = platformKeyManager;
        mRecoverySnapshotStorage = snapshotStorage;
        mRecoverySnapshotStorage = snapshotStorage;
        mTestOnlyInsecureCertificateHelper = TestOnlyInsecureCertificateHelper;
        mTestOnlyInsecureCertificateHelper = testOnlyInsecureCertificateHelper;
        mScrypt = scrypt;
    }
    }


    @Override
    @Override
@@ -230,8 +246,14 @@ public class KeySyncTask implements Runnable {
            }
            }
        }
        }


        boolean useScryptToHashCredential = shouldUseScryptToHashCredential(rootCertAlias);
        byte[] salt = generateSalt();
        byte[] salt = generateSalt();
        byte[] localLskfHash = hashCredentials(salt, mCredential);
        byte[] localLskfHash;
        if (useScryptToHashCredential) {
            localLskfHash = hashCredentialsByScrypt(salt, mCredential);
        } else {
            localLskfHash = hashCredentialsBySaltedSha256(salt, mCredential);
        }


        Map<String, SecretKey> rawKeys;
        Map<String, SecretKey> rawKeys;
        try {
        try {
@@ -303,10 +325,17 @@ public class KeySyncTask implements Runnable {
            Log.e(TAG,"Could not encrypt with recovery key", e);
            Log.e(TAG,"Could not encrypt with recovery key", e);
            return;
            return;
        }
        }
        KeyDerivationParams keyDerivationParams;
        if (useScryptToHashCredential) {
            keyDerivationParams = KeyDerivationParams.createScryptParams(
                    salt, /*memoryDifficulty=*/ SCRYPT_PARAM_N);
        } else {
            keyDerivationParams = KeyDerivationParams.createSha256Params(salt);
        }
        KeyChainProtectionParams metadata = new KeyChainProtectionParams.Builder()
        KeyChainProtectionParams metadata = new KeyChainProtectionParams.Builder()
                .setUserSecretType(TYPE_LOCKSCREEN)
                .setUserSecretType(TYPE_LOCKSCREEN)
                .setLockScreenUiFormat(getUiFormat(mCredentialType, mCredential))
                .setLockScreenUiFormat(getUiFormat(mCredentialType, mCredential))
                .setKeyDerivationParams(KeyDerivationParams.createSha256Params(salt))
                .setKeyDerivationParams(keyDerivationParams)
                .setSecret(new byte[0])
                .setSecret(new byte[0])
                .build();
                .build();


@@ -443,7 +472,7 @@ public class KeySyncTask implements Runnable {
     * @return The SHA-256 hash.
     * @return The SHA-256 hash.
     */
     */
    @VisibleForTesting
    @VisibleForTesting
    static byte[] hashCredentials(byte[] salt, String credentials) {
    static byte[] hashCredentialsBySaltedSha256(byte[] salt, String credentials) {
        byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
        byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
        ByteBuffer byteBuffer = ByteBuffer.allocate(
        ByteBuffer byteBuffer = ByteBuffer.allocate(
                salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2);
                salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2);
@@ -462,6 +491,12 @@ public class KeySyncTask implements Runnable {
        }
        }
    }
    }


    private byte[] hashCredentialsByScrypt(byte[] salt, String credentials) {
        return mScrypt.scrypt(
                credentials.getBytes(StandardCharsets.UTF_8), salt,
                SCRYPT_PARAM_N, SCRYPT_PARAM_R, SCRYPT_PARAM_P, SCRYPT_PARAM_OUTLEN_BYTES);
    }

    private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
    private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
        KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
        keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
        keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
@@ -479,4 +514,11 @@ public class KeySyncTask implements Runnable {
        }
        }
        return keyEntries;
        return keyEntries;
    }
    }

    private boolean shouldUseScryptToHashCredential(String rootCertAlias) {
        return mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
                && mCredential.length() >= MIN_CREDENTIAL_LEN_TO_USE_SCRYPT
                // TODO: Remove the test cert check once all other components are updated
                && mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(rootCertAlias);
    }
}
}
+181 −19
Original line number Original line Diff line number Diff line
@@ -50,6 +50,7 @@ import android.security.keystore.KeyProperties;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.RecoveryController;
import android.security.keystore.recovery.RecoveryController;
import android.security.keystore.recovery.TrustedRootCertificates;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.support.test.InstrumentationRegistry;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.filters.SmallTest;
@@ -101,6 +102,7 @@ public class KeySyncTaskTest {
    @Mock private PlatformKeyManager mPlatformKeyManager;
    @Mock private PlatformKeyManager mPlatformKeyManager;
    @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage;
    @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage;
    @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
    @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
    @Spy private MockScrypt mMockScrypt;


    private RecoverySnapshotStorage mRecoverySnapshotStorage;
    private RecoverySnapshotStorage mRecoverySnapshotStorage;
    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -138,7 +140,8 @@ public class KeySyncTaskTest {
                TEST_CREDENTIAL,
                TEST_CREDENTIAL,
                /*credentialUpdated=*/ false,
                /*credentialUpdated=*/ false,
                mPlatformKeyManager,
                mPlatformKeyManager,
                mTestOnlyInsecureCertificateHelper);
                mTestOnlyInsecureCertificateHelper,
                mMockScrypt);


        mWrappingKey = generateAndroidKeyStoreKey();
        mWrappingKey = generateAndroidKeyStoreKey();
        mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey);
        mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey);
@@ -172,41 +175,41 @@ public class KeySyncTaskTest {
    }
    }


    @Test
    @Test
    public void hashCredentials_returnsSameHashForSameCredentialsAndSalt() {
    public void hashCredentialsBySaltedSha256_returnsSameHashForSameCredentialsAndSalt() {
        String credentials = "password1234";
        String credentials = "password1234";
        byte[] salt = randomBytes(16);
        byte[] salt = randomBytes(16);


        assertArrayEquals(
        assertArrayEquals(
                KeySyncTask.hashCredentials(salt, credentials),
                KeySyncTask.hashCredentialsBySaltedSha256(salt, credentials),
                KeySyncTask.hashCredentials(salt, credentials));
                KeySyncTask.hashCredentialsBySaltedSha256(salt, credentials));
    }
    }


    @Test
    @Test
    public void hashCredentials_returnsDifferentHashForDifferentCredentials() {
    public void hashCredentialsBySaltedSha256_returnsDifferentHashForDifferentCredentials() {
        byte[] salt = randomBytes(16);
        byte[] salt = randomBytes(16);


        assertFalse(
        assertFalse(
                Arrays.equals(
                Arrays.equals(
                    KeySyncTask.hashCredentials(salt, "password1234"),
                    KeySyncTask.hashCredentialsBySaltedSha256(salt, "password1234"),
                    KeySyncTask.hashCredentials(salt, "password12345")));
                    KeySyncTask.hashCredentialsBySaltedSha256(salt, "password12345")));
    }
    }


    @Test
    @Test
    public void hashCredentials_returnsDifferentHashForDifferentSalt() {
    public void hashCredentialsBySaltedSha256_returnsDifferentHashForDifferentSalt() {
        String credentials = "wowmuch";
        String credentials = "wowmuch";


        assertFalse(
        assertFalse(
                Arrays.equals(
                Arrays.equals(
                        KeySyncTask.hashCredentials(randomBytes(64), credentials),
                        KeySyncTask.hashCredentialsBySaltedSha256(randomBytes(64), credentials),
                        KeySyncTask.hashCredentials(randomBytes(64), credentials)));
                        KeySyncTask.hashCredentialsBySaltedSha256(randomBytes(64), credentials)));
    }
    }


    @Test
    @Test
    public void hashCredentials_returnsDifferentHashEvenIfConcatIsSame() {
    public void hashCredentialsBySaltedSha256_returnsDifferentHashEvenIfConcatIsSame() {
        assertFalse(
        assertFalse(
                Arrays.equals(
                Arrays.equals(
                        KeySyncTask.hashCredentials(utf8Bytes("123"), "4567"),
                        KeySyncTask.hashCredentialsBySaltedSha256(utf8Bytes("123"), "4567"),
                        KeySyncTask.hashCredentials(utf8Bytes("1234"), "567")));
                        KeySyncTask.hashCredentialsBySaltedSha256(utf8Bytes("1234"), "567")));
    }
    }


    @Test
    @Test
@@ -276,6 +279,155 @@ public class KeySyncTaskTest {
        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
    }
    }


    @Test
    public void run_useScryptToHashLongPasswordInTestMode() throws Exception {
        String longPassword = TrustedRootCertificates.INSECURE_PASSWORD_PREFIX + "0123456789";
        String appKeyAlias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX + "alias";
        mKeySyncTask = new KeySyncTask(
                mRecoverableKeyStoreDb,
                mRecoverySnapshotStorage,
                mSnapshotListenersStorage,
                TEST_USER_ID,
                CREDENTIAL_TYPE_PASSWORD,
                /*credential=*/ longPassword,
                /*credentialUpdated=*/ false,
                mPlatformKeyManager,
                mTestOnlyInsecureCertificateHelper,
                mMockScrypt);
        mRecoverableKeyStoreDb.setServerParams(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
        mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
                TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS);
        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
                TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS,
                TestData.getInsecureCertPathForEndpoint1());
        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, appKeyAlias);

        mKeySyncTask.run();

        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                isEqualTo(UI_FORMAT_PASSWORD);
        verify(mMockScrypt).scrypt(eq(longPassword.getBytes()), any(),
                eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
                eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
        KeyDerivationParams keyDerivationParams =
                keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams();
        assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
                KeyDerivationParams.ALGORITHM_SCRYPT);
        assertThat(keyDerivationParams.getMemoryDifficulty()).isEqualTo(KeySyncTask.SCRYPT_PARAM_N);
    }

    @Test
    public void run_useSha256ToHashShortPasswordInTestMode() throws Exception {
        String shortPassword = TrustedRootCertificates.INSECURE_PASSWORD_PREFIX + "012345678";
        String appKeyAlias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX + "alias";
        mKeySyncTask = new KeySyncTask(
                mRecoverableKeyStoreDb,
                mRecoverySnapshotStorage,
                mSnapshotListenersStorage,
                TEST_USER_ID,
                CREDENTIAL_TYPE_PASSWORD,
                /*credential=*/ shortPassword,
                /*credentialUpdated=*/ false,
                mPlatformKeyManager,
                mTestOnlyInsecureCertificateHelper,
                mMockScrypt);
        mRecoverableKeyStoreDb.setServerParams(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
        mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
                TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS);
        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
                TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS,
                TestData.getInsecureCertPathForEndpoint1());
        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, appKeyAlias);

        mKeySyncTask.run();

        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                isEqualTo(UI_FORMAT_PASSWORD);
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
        KeyDerivationParams keyDerivationParams =
                keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams();
        assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
                KeyDerivationParams.ALGORITHM_SHA256);
    }

    @Test
    public void run_useSha256ToHashShortPasswordInProdMode() throws Exception {
        String shortPassword = "01234567890123456789abc";  // 23 chars
        mKeySyncTask = new KeySyncTask(
                mRecoverableKeyStoreDb,
                mRecoverySnapshotStorage,
                mSnapshotListenersStorage,
                TEST_USER_ID,
                CREDENTIAL_TYPE_PASSWORD,
                /*credential=*/ shortPassword,
                /*credentialUpdated=*/ false,
                mPlatformKeyManager,
                mTestOnlyInsecureCertificateHelper,
                mMockScrypt);
        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();

        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                isEqualTo(UI_FORMAT_PASSWORD);
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
        KeyDerivationParams keyDerivationParams =
                keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams();
        assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
                KeyDerivationParams.ALGORITHM_SHA256);
    }

    @Test
    public void run_useSha256ToHashLongPasswordInProdMode() throws Exception {
        String longPassword = "01234567890123456789abcd";  // 24 chars
        mKeySyncTask = new KeySyncTask(
                mRecoverableKeyStoreDb,
                mRecoverySnapshotStorage,
                mSnapshotListenersStorage,
                TEST_USER_ID,
                CREDENTIAL_TYPE_PASSWORD,
                /*credential=*/ longPassword,
                /*credentialUpdated=*/ false,
                mPlatformKeyManager,
                mTestOnlyInsecureCertificateHelper,
                mMockScrypt);
        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();

        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                isEqualTo(UI_FORMAT_PASSWORD);
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
        KeyDerivationParams keyDerivationParams =
                keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams();
        assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
                KeyDerivationParams.ALGORITHM_SHA256);
    }

    @Test
    @Test
    public void run_stillCreatesSnapshotIfNoRecoveryAgentPendingIntentRegistered()
    public void run_stillCreatesSnapshotIfNoRecoveryAgentPendingIntentRegistered()
            throws Exception {
            throws Exception {
@@ -319,6 +471,7 @@ public class KeySyncTaskTest {
        assertNotNull(keyChainSnapshot); // created snapshot
        assertNotNull(keyChainSnapshot); // created snapshot
        List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys();
        List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys();
        assertThat(applicationKeys).hasSize(0); // non whitelisted key is not included
        assertThat(applicationKeys).hasSize(0); // non whitelisted key is not included
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
    }
    }


    @Test
    @Test
@@ -341,6 +494,7 @@ public class KeySyncTaskTest {
                .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS));
                .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS));
        verify(mTestOnlyInsecureCertificateHelper)
        verify(mTestOnlyInsecureCertificateHelper)
                .doesCredentialSupportInsecureMode(anyInt(), any());
                .doesCredentialSupportInsecureMode(anyInt(), any());
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
    }
    }


    @Test
    @Test
@@ -404,7 +558,7 @@ public class KeySyncTaskTest {
        assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
        assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
                KeyDerivationParams.ALGORITHM_SHA256);
                KeyDerivationParams.ALGORITHM_SHA256);
        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
        byte[] lockScreenHash = KeySyncTask.hashCredentials(
        byte[] lockScreenHash = KeySyncTask.hashCredentialsBySaltedSha256(
                keyDerivationParams.getSalt(),
                keyDerivationParams.getSalt(),
                TEST_CREDENTIAL);
                TEST_CREDENTIAL);
        Long counterId = mRecoverableKeyStoreDb.getCounterId(TEST_USER_ID, TEST_RECOVERY_AGENT_UID);
        Long counterId = mRecoverableKeyStoreDb.getCounterId(TEST_USER_ID, TEST_RECOVERY_AGENT_UID);
@@ -501,7 +655,8 @@ public class KeySyncTaskTest {
                "password",
                "password",
                /*credentialUpdated=*/ false,
                /*credentialUpdated=*/ false,
                mPlatformKeyManager,
                mPlatformKeyManager,
                mTestOnlyInsecureCertificateHelper);
                mTestOnlyInsecureCertificateHelper,
                mMockScrypt);


        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
@@ -515,6 +670,7 @@ public class KeySyncTaskTest {
        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                isEqualTo(UI_FORMAT_PASSWORD);
                isEqualTo(UI_FORMAT_PASSWORD);
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
    }
    }


    @Test
    @Test
@@ -528,7 +684,8 @@ public class KeySyncTaskTest {
                /*credential=*/ "1234",
                /*credential=*/ "1234",
                /*credentialUpdated=*/ false,
                /*credentialUpdated=*/ false,
                mPlatformKeyManager,
                mPlatformKeyManager,
                mTestOnlyInsecureCertificateHelper);
                mTestOnlyInsecureCertificateHelper,
                mMockScrypt);


        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
@@ -543,6 +700,7 @@ public class KeySyncTaskTest {
        // Password with only digits is changed to pin.
        // Password with only digits is changed to pin.
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                isEqualTo(UI_FORMAT_PIN);
                isEqualTo(UI_FORMAT_PIN);
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
    }
    }


    @Test
    @Test
@@ -556,7 +714,8 @@ public class KeySyncTaskTest {
                "12345",
                "12345",
                /*credentialUpdated=*/ false,
                /*credentialUpdated=*/ false,
                mPlatformKeyManager,
                mPlatformKeyManager,
                mTestOnlyInsecureCertificateHelper);
                mTestOnlyInsecureCertificateHelper,
                mMockScrypt);


        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
@@ -570,6 +729,7 @@ public class KeySyncTaskTest {
        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                isEqualTo(UI_FORMAT_PATTERN);
                isEqualTo(UI_FORMAT_PATTERN);
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
    }
    }


    @Test
    @Test
@@ -638,7 +798,8 @@ public class KeySyncTaskTest {
          "12345",
          "12345",
          /*credentialUpdated=*/ false,
          /*credentialUpdated=*/ false,
          mPlatformKeyManager,
          mPlatformKeyManager,
          mTestOnlyInsecureCertificateHelper);
          mTestOnlyInsecureCertificateHelper,
          mMockScrypt);


      addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
      addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);


@@ -654,6 +815,7 @@ public class KeySyncTaskTest {
          .getStatusForAllKeys(TEST_RECOVERY_AGENT_UID)
          .getStatusForAllKeys(TEST_RECOVERY_AGENT_UID)
          .get(TEST_APP_KEY_ALIAS);
          .get(TEST_APP_KEY_ALIAS);
      assertEquals(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE, status);
      assertEquals(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE, status);
      verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
    }
    }


    private SecretKey addApplicationKey(int userId, int recoveryAgentUid, String alias)
    private SecretKey addApplicationKey(int userId, int recoveryAgentUid, String alias)
+53 −0
Original line number Original line 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 org.junit.Assert.assertEquals;

import android.security.Scrypt;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MockScrypt extends Scrypt {

    @Override
    public byte[] scrypt(byte[] password, byte[] salt, int n, int r, int p, int outLen) {
        assertEquals(32, outLen);

        ByteBuffer byteBuffer = ByteBuffer.allocate(
                password.length + salt.length + Integer.BYTES * 6);
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        byteBuffer.putInt(password.length);
        byteBuffer.put(password);
        byteBuffer.putInt(salt.length);
        byteBuffer.put(salt);
        byteBuffer.putInt(n);
        byteBuffer.putInt(r);
        byteBuffer.putInt(p);
        byteBuffer.putInt(outLen);

        try {
            return MessageDigest.getInstance("SHA-256").digest(byteBuffer.array());
        } catch (NoSuchAlgorithmException e) {
            // Should never happen
            throw new RuntimeException(e);
        }
    }
}