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

Commit e73074e9 authored by Bo Zhu's avatar Bo Zhu Committed by Android (Google) Code Review
Browse files

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

parents e269a5ac 76973437
Loading
Loading
Loading
Loading
+49 −7
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_L

import android.annotation.Nullable;
import android.content.Context;
import android.security.Scrypt;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
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 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 int mUserId;
    private final int mCredentialType;
@@ -78,6 +90,7 @@ public class KeySyncTask implements Runnable {
    private final RecoverySnapshotStorage mRecoverySnapshotStorage;
    private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
    private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
    private final Scrypt mScrypt;

    public static KeySyncTask newInstance(
            Context context,
@@ -98,7 +111,8 @@ public class KeySyncTask implements Runnable {
                credential,
                credentialUpdated,
                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 credentialUpdated signals weather credentials were updated.
     * @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
    KeySyncTask(
@@ -122,7 +136,8 @@ public class KeySyncTask implements Runnable {
            String credential,
            boolean credentialUpdated,
            PlatformKeyManager platformKeyManager,
            TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) {
            TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper,
            Scrypt scrypt) {
        mSnapshotListenersStorage = recoverySnapshotListenersStorage;
        mRecoverableKeyStoreDb = recoverableKeyStoreDb;
        mUserId = userId;
@@ -131,7 +146,8 @@ public class KeySyncTask implements Runnable {
        mCredentialUpdated = credentialUpdated;
        mPlatformKeyManager = platformKeyManager;
        mRecoverySnapshotStorage = snapshotStorage;
        mTestOnlyInsecureCertificateHelper = TestOnlyInsecureCertificateHelper;
        mTestOnlyInsecureCertificateHelper = testOnlyInsecureCertificateHelper;
        mScrypt = scrypt;
    }

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

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

@@ -443,7 +472,7 @@ public class KeySyncTask implements Runnable {
     * @return The SHA-256 hash.
     */
    @VisibleForTesting
    static byte[] hashCredentials(byte[] salt, String credentials) {
    static byte[] hashCredentialsBySaltedSha256(byte[] salt, String credentials) {
        byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
        ByteBuffer byteBuffer = ByteBuffer.allocate(
                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 {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
        keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
@@ -479,4 +514,11 @@ public class KeySyncTask implements Runnable {
        }
        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 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.KeyDerivationParams;
import android.security.keystore.recovery.RecoveryController;
import android.security.keystore.recovery.TrustedRootCertificates;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -101,6 +102,7 @@ public class KeySyncTaskTest {
    @Mock private PlatformKeyManager mPlatformKeyManager;
    @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage;
    @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
    @Spy private MockScrypt mMockScrypt;

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

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

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

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

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

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

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

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

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

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

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

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

        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
                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().get(0).getLockScreenUiFormat()).
                isEqualTo(UI_FORMAT_PASSWORD);
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
    }

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

        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
                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.
        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                isEqualTo(UI_FORMAT_PIN);
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
    }

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

        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
                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().get(0).getLockScreenUiFormat()).
                isEqualTo(UI_FORMAT_PATTERN);
        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
    }

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

      addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);

@@ -654,6 +815,7 @@ public class KeySyncTaskTest {
          .getStatusForAllKeys(TEST_RECOVERY_AGENT_UID)
          .get(TEST_APP_KEY_ALIAS);
      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)
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.locksettings.recoverablekeystore;

import static 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);
        }
    }
}