Home | History | Annotate | Download | only in recoverablekeystore
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.locksettings.recoverablekeystore;
     18 
     19 import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;
     20 
     21 import android.annotation.Nullable;
     22 import android.content.Context;
     23 import android.security.Scrypt;
     24 import android.security.keystore.recovery.KeyChainProtectionParams;
     25 import android.security.keystore.recovery.KeyChainSnapshot;
     26 import android.security.keystore.recovery.KeyDerivationParams;
     27 import android.security.keystore.recovery.WrappedApplicationKey;
     28 import android.util.Log;
     29 
     30 import com.android.internal.annotations.VisibleForTesting;
     31 import com.android.internal.util.ArrayUtils;
     32 import com.android.internal.widget.LockPatternUtils;
     33 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
     34 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
     35 
     36 import java.io.IOException;
     37 import java.nio.ByteBuffer;
     38 import java.nio.ByteOrder;
     39 import java.nio.charset.StandardCharsets;
     40 import java.security.GeneralSecurityException;
     41 import java.security.InvalidAlgorithmParameterException;
     42 import java.security.InvalidKeyException;
     43 import java.security.KeyStoreException;
     44 import java.security.MessageDigest;
     45 import java.security.NoSuchAlgorithmException;
     46 import java.security.PublicKey;
     47 import java.security.SecureRandom;
     48 import java.security.UnrecoverableKeyException;
     49 import java.security.cert.CertPath;
     50 import java.security.cert.CertificateException;
     51 import java.util.ArrayList;
     52 import java.util.List;
     53 import java.util.Map;
     54 
     55 import javax.crypto.KeyGenerator;
     56 import javax.crypto.NoSuchPaddingException;
     57 import javax.crypto.SecretKey;
     58 
     59 /**
     60  * Task to sync application keys to a remote vault service.
     61  *
     62  * @hide
     63  */
     64 public class KeySyncTask implements Runnable {
     65     private static final String TAG = "KeySyncTask";
     66 
     67     private static final String RECOVERY_KEY_ALGORITHM = "AES";
     68     private static final int RECOVERY_KEY_SIZE_BITS = 256;
     69     private static final int SALT_LENGTH_BYTES = 16;
     70     private static final int LENGTH_PREFIX_BYTES = Integer.BYTES;
     71     private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256";
     72     private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10;
     73 
     74     @VisibleForTesting
     75     static final int SCRYPT_PARAM_N = 4096;
     76     @VisibleForTesting
     77     static final int SCRYPT_PARAM_R = 8;
     78     @VisibleForTesting
     79     static final int SCRYPT_PARAM_P = 1;
     80     @VisibleForTesting
     81     static final int SCRYPT_PARAM_OUTLEN_BYTES = 32;
     82 
     83     private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
     84     private final int mUserId;
     85     private final int mCredentialType;
     86     private final String mCredential;
     87     private final boolean mCredentialUpdated;
     88     private final PlatformKeyManager mPlatformKeyManager;
     89     private final RecoverySnapshotStorage mRecoverySnapshotStorage;
     90     private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
     91     private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
     92     private final Scrypt mScrypt;
     93 
     94     public static KeySyncTask newInstance(
     95             Context context,
     96             RecoverableKeyStoreDb recoverableKeyStoreDb,
     97             RecoverySnapshotStorage snapshotStorage,
     98             RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
     99             int userId,
    100             int credentialType,
    101             String credential,
    102             boolean credentialUpdated
    103     ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
    104         return new KeySyncTask(
    105                 recoverableKeyStoreDb,
    106                 snapshotStorage,
    107                 recoverySnapshotListenersStorage,
    108                 userId,
    109                 credentialType,
    110                 credential,
    111                 credentialUpdated,
    112                 PlatformKeyManager.getInstance(context, recoverableKeyStoreDb),
    113                 new TestOnlyInsecureCertificateHelper(),
    114                 new Scrypt());
    115     }
    116 
    117     /**
    118      * A new task.
    119      *
    120      * @param recoverableKeyStoreDb Database where the keys are stored.
    121      * @param userId The uid of the user whose profile has been unlocked.
    122      * @param credentialType The type of credential as defined in {@code LockPatternUtils}
    123      * @param credential The credential, encoded as a {@link String}.
    124      * @param credentialUpdated signals weather credentials were updated.
    125      * @param platformKeyManager platform key manager
    126      * @param testOnlyInsecureCertificateHelper utility class used for end-to-end tests
    127      */
    128     @VisibleForTesting
    129     KeySyncTask(
    130             RecoverableKeyStoreDb recoverableKeyStoreDb,
    131             RecoverySnapshotStorage snapshotStorage,
    132             RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
    133             int userId,
    134             int credentialType,
    135             String credential,
    136             boolean credentialUpdated,
    137             PlatformKeyManager platformKeyManager,
    138             TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper,
    139             Scrypt scrypt) {
    140         mSnapshotListenersStorage = recoverySnapshotListenersStorage;
    141         mRecoverableKeyStoreDb = recoverableKeyStoreDb;
    142         mUserId = userId;
    143         mCredentialType = credentialType;
    144         mCredential = credential;
    145         mCredentialUpdated = credentialUpdated;
    146         mPlatformKeyManager = platformKeyManager;
    147         mRecoverySnapshotStorage = snapshotStorage;
    148         mTestOnlyInsecureCertificateHelper = testOnlyInsecureCertificateHelper;
    149         mScrypt = scrypt;
    150     }
    151 
    152     @Override
    153     public void run() {
    154         try {
    155             // Only one task is active If user unlocks phone many times in a short time interval.
    156             synchronized(KeySyncTask.class) {
    157                 syncKeys();
    158             }
    159         } catch (Exception e) {
    160             Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
    161         }
    162     }
    163 
    164     private void syncKeys() {
    165         if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
    166             // Application keys for the user will not be available for sync.
    167             Log.w(TAG, "Credentials are not set for user " + mUserId);
    168             int generation = mPlatformKeyManager.getGenerationId(mUserId);
    169             mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
    170             return;
    171         }
    172         if (isCustomLockScreen()) {
    173             Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId);
    174             mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
    175             return;
    176         }
    177 
    178         List<Integer> recoveryAgents = mRecoverableKeyStoreDb.getRecoveryAgents(mUserId);
    179         for (int uid : recoveryAgents) {
    180             try {
    181               syncKeysForAgent(uid);
    182             } catch (IOException e) {
    183                 Log.e(TAG, "IOException during sync for agent " + uid, e);
    184             }
    185         }
    186         if (recoveryAgents.isEmpty()) {
    187             Log.w(TAG, "No recovery agent initialized for user " + mUserId);
    188         }
    189     }
    190 
    191     private boolean isCustomLockScreen() {
    192         return mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE
    193             && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PATTERN
    194             && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
    195     }
    196 
    197     private void syncKeysForAgent(int recoveryAgentUid) throws IOException {
    198         boolean shouldRecreateCurrentVersion = false;
    199         if (!shouldCreateSnapshot(recoveryAgentUid)) {
    200             shouldRecreateCurrentVersion =
    201                     (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null)
    202                     && (mRecoverySnapshotStorage.get(recoveryAgentUid) == null);
    203             if (shouldRecreateCurrentVersion) {
    204                 Log.d(TAG, "Recreating most recent snapshot");
    205             } else {
    206                 Log.d(TAG, "Key sync not needed.");
    207                 return;
    208             }
    209         }
    210 
    211         String rootCertAlias =
    212                 mRecoverableKeyStoreDb.getActiveRootOfTrust(mUserId, recoveryAgentUid);
    213         rootCertAlias = mTestOnlyInsecureCertificateHelper
    214                 .getDefaultCertificateAliasIfEmpty(rootCertAlias);
    215 
    216         PublicKey publicKey;
    217         CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId,
    218                 recoveryAgentUid, rootCertAlias);
    219         if (certPath != null) {
    220             Log.d(TAG, "Using the public key in stored CertPath for syncing");
    221             publicKey = certPath.getCertificates().get(0).getPublicKey();
    222         } else {
    223             Log.d(TAG, "Using the stored raw public key for syncing");
    224             publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
    225                     recoveryAgentUid);
    226         }
    227         if (publicKey == null) {
    228             Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task.");
    229             return;
    230         }
    231 
    232         byte[] vaultHandle = mRecoverableKeyStoreDb.getServerParams(mUserId, recoveryAgentUid);
    233         if (vaultHandle == null) {
    234             Log.w(TAG, "No device ID set for user " + mUserId);
    235             return;
    236         }
    237 
    238         if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(rootCertAlias)) {
    239             Log.w(TAG, "Insecure root certificate is used by recovery agent "
    240                     + recoveryAgentUid);
    241             if (mTestOnlyInsecureCertificateHelper.doesCredentialSupportInsecureMode(
    242                     mCredentialType, mCredential)) {
    243                 Log.w(TAG, "Whitelisted credential is used to generate snapshot by "
    244                         + "recovery agent "+ recoveryAgentUid);
    245             } else {
    246                 Log.w(TAG, "Non whitelisted credential is used to generate recovery snapshot by "
    247                         + recoveryAgentUid + " - ignore attempt.");
    248                 return; // User secret will not be used.
    249             }
    250         }
    251 
    252         boolean useScryptToHashCredential = shouldUseScryptToHashCredential();
    253         byte[] salt = generateSalt();
    254         byte[] localLskfHash;
    255         if (useScryptToHashCredential) {
    256             localLskfHash = hashCredentialsByScrypt(salt, mCredential);
    257         } else {
    258             localLskfHash = hashCredentialsBySaltedSha256(salt, mCredential);
    259         }
    260 
    261         Map<String, SecretKey> rawKeys;
    262         try {
    263             rawKeys = getKeysToSync(recoveryAgentUid);
    264         } catch (GeneralSecurityException e) {
    265             Log.e(TAG, "Failed to load recoverable keys for sync", e);
    266             return;
    267         } catch (InsecureUserException e) {
    268             Log.e(TAG, "A screen unlock triggered the key sync flow, so user must have "
    269                     + "lock screen. This should be impossible.", e);
    270             return;
    271         } catch (BadPlatformKeyException e) {
    272             Log.e(TAG, "Loaded keys for same generation ID as platform key, so "
    273                     + "BadPlatformKeyException should be impossible.", e);
    274             return;
    275         } catch (IOException e) {
    276             Log.e(TAG, "Local database error.", e);
    277             return;
    278         }
    279         // Only include insecure key material for test
    280         if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(rootCertAlias)) {
    281             rawKeys = mTestOnlyInsecureCertificateHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
    282         }
    283 
    284         SecretKey recoveryKey;
    285         try {
    286             recoveryKey = generateRecoveryKey();
    287         } catch (NoSuchAlgorithmException e) {
    288             Log.wtf("AES should never be unavailable", e);
    289             return;
    290         }
    291 
    292         Map<String, byte[]> encryptedApplicationKeys;
    293         try {
    294             encryptedApplicationKeys = KeySyncUtils.encryptKeysWithRecoveryKey(
    295                     recoveryKey, rawKeys);
    296         } catch (InvalidKeyException | NoSuchAlgorithmException e) {
    297             Log.wtf(TAG,
    298                     "Should be impossible: could not encrypt application keys with random key",
    299                     e);
    300             return;
    301         }
    302 
    303         Long counterId;
    304         // counter id is generated exactly once for each credentials value.
    305         if (mCredentialUpdated) {
    306             counterId = generateAndStoreCounterId(recoveryAgentUid);
    307         } else {
    308             counterId = mRecoverableKeyStoreDb.getCounterId(mUserId, recoveryAgentUid);
    309             if (counterId == null) {
    310                 counterId = generateAndStoreCounterId(recoveryAgentUid);
    311             }
    312         }
    313 
    314         byte[] vaultParams = KeySyncUtils.packVaultParams(
    315                 publicKey,
    316                 counterId,
    317                 TRUSTED_HARDWARE_MAX_ATTEMPTS,
    318                 vaultHandle);
    319 
    320         byte[] encryptedRecoveryKey;
    321         try {
    322             encryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey(
    323                     publicKey,
    324                     localLskfHash,
    325                     vaultParams,
    326                     recoveryKey);
    327         } catch (NoSuchAlgorithmException e) {
    328             Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e);
    329             return;
    330         } catch (InvalidKeyException e) {
    331             Log.e(TAG,"Could not encrypt with recovery key", e);
    332             return;
    333         }
    334 
    335         KeyDerivationParams keyDerivationParams;
    336         if (useScryptToHashCredential) {
    337             keyDerivationParams = KeyDerivationParams.createScryptParams(
    338                     salt, /*memoryDifficulty=*/ SCRYPT_PARAM_N);
    339         } else {
    340             keyDerivationParams = KeyDerivationParams.createSha256Params(salt);
    341         }
    342         KeyChainProtectionParams keyChainProtectionParams = new KeyChainProtectionParams.Builder()
    343                 .setUserSecretType(TYPE_LOCKSCREEN)
    344                 .setLockScreenUiFormat(getUiFormat(mCredentialType, mCredential))
    345                 .setKeyDerivationParams(keyDerivationParams)
    346                 .setSecret(new byte[0])
    347                 .build();
    348 
    349         ArrayList<KeyChainProtectionParams> metadataList = new ArrayList<>();
    350         metadataList.add(keyChainProtectionParams);
    351 
    352         KeyChainSnapshot.Builder keyChainSnapshotBuilder = new KeyChainSnapshot.Builder()
    353                 .setSnapshotVersion(
    354                         getSnapshotVersion(recoveryAgentUid, shouldRecreateCurrentVersion))
    355                 .setMaxAttempts(TRUSTED_HARDWARE_MAX_ATTEMPTS)
    356                 .setCounterId(counterId)
    357                 .setServerParams(vaultHandle)
    358                 .setKeyChainProtectionParams(metadataList)
    359                 .setWrappedApplicationKeys(createApplicationKeyEntries(encryptedApplicationKeys))
    360                 .setEncryptedRecoveryKeyBlob(encryptedRecoveryKey);
    361         try {
    362             keyChainSnapshotBuilder.setTrustedHardwareCertPath(certPath);
    363         } catch(CertificateException e) {
    364             // Should not happen, as it's just deserialized from bytes stored in the db
    365             Log.wtf(TAG, "Cannot serialize CertPath when calling setTrustedHardwareCertPath", e);
    366             return;
    367         }
    368         mRecoverySnapshotStorage.put(recoveryAgentUid, keyChainSnapshotBuilder.build());
    369         mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
    370 
    371         mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);
    372     }
    373 
    374     @VisibleForTesting
    375     int getSnapshotVersion(int recoveryAgentUid, boolean shouldRecreateCurrentVersion)
    376             throws IOException {
    377         Long snapshotVersion = mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid);
    378         if (shouldRecreateCurrentVersion) {
    379             // version shouldn't be null at this moment.
    380             snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion;
    381         } else {
    382             snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion + 1;
    383         }
    384 
    385         long updatedRows = mRecoverableKeyStoreDb.setSnapshotVersion(mUserId, recoveryAgentUid,
    386                 snapshotVersion);
    387         if (updatedRows < 0) {
    388             Log.e(TAG, "Failed to set the snapshot version in the local DB.");
    389             throw new IOException("Failed to set the snapshot version in the local DB.");
    390         }
    391 
    392         return snapshotVersion.intValue();
    393     }
    394 
    395     private long generateAndStoreCounterId(int recoveryAgentUid) throws IOException {
    396         long counter = new SecureRandom().nextLong();
    397         long updatedRows = mRecoverableKeyStoreDb.setCounterId(mUserId, recoveryAgentUid, counter);
    398         if (updatedRows < 0) {
    399             Log.e(TAG, "Failed to set the snapshot version in the local DB.");
    400             throw new IOException("Failed to set counterId in the local DB.");
    401         }
    402         return counter;
    403     }
    404 
    405     /**
    406      * Returns all of the recoverable keys for the user.
    407      */
    408     private Map<String, SecretKey> getKeysToSync(int recoveryAgentUid)
    409             throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
    410             NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException,
    411             InvalidKeyException, InvalidAlgorithmParameterException, IOException {
    412         PlatformDecryptionKey decryptKey = mPlatformKeyManager.getDecryptKey(mUserId);;
    413         Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
    414                 mUserId, recoveryAgentUid, decryptKey.getGenerationId());
    415         return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
    416     }
    417 
    418     /**
    419      * Returns {@code true} if a sync is pending.
    420      * @param recoveryAgentUid uid of the recovery agent.
    421      */
    422     private boolean shouldCreateSnapshot(int recoveryAgentUid) {
    423         int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid);
    424         if (!ArrayUtils.contains(types, KeyChainProtectionParams.TYPE_LOCKSCREEN)) {
    425             // Only lockscreen type is supported.
    426             // We will need to pass extra argument to KeySyncTask to support custom pass phrase.
    427             return false;
    428         }
    429         if (mCredentialUpdated) {
    430             // Sync credential if at least one snapshot was created.
    431             if (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null) {
    432                 mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, true);
    433                 return true;
    434             }
    435         }
    436 
    437         return mRecoverableKeyStoreDb.getShouldCreateSnapshot(mUserId, recoveryAgentUid);
    438     }
    439 
    440     /**
    441      * The UI best suited to entering the given lock screen. This is synced with the vault so the
    442      * user can be shown the same UI when recovering the vault on another device.
    443      *
    444      * @return The format - either pattern, pin, or password.
    445      */
    446     @VisibleForTesting
    447     @KeyChainProtectionParams.LockScreenUiFormat static int getUiFormat(
    448             int credentialType, String credential) {
    449         if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
    450             return KeyChainProtectionParams.UI_FORMAT_PATTERN;
    451         } else if (isPin(credential)) {
    452             return KeyChainProtectionParams.UI_FORMAT_PIN;
    453         } else {
    454             return KeyChainProtectionParams.UI_FORMAT_PASSWORD;
    455         }
    456     }
    457 
    458     /**
    459      * Generates a salt to include with the lock screen hash.
    460      *
    461      * @return The salt.
    462      */
    463     private static byte[] generateSalt() {
    464         byte[] salt = new byte[SALT_LENGTH_BYTES];
    465         new SecureRandom().nextBytes(salt);
    466         return salt;
    467     }
    468 
    469     /**
    470      * Returns {@code true} if {@code credential} looks like a pin.
    471      */
    472     @VisibleForTesting
    473     static boolean isPin(@Nullable String credential) {
    474         if (credential == null) {
    475             return false;
    476         }
    477         int length = credential.length();
    478         for (int i = 0; i < length; i++) {
    479             if (!Character.isDigit(credential.charAt(i))) {
    480                 return false;
    481             }
    482         }
    483         return true;
    484     }
    485 
    486     /**
    487      * Hashes {@code credentials} with the given {@code salt}.
    488      *
    489      * @return The SHA-256 hash.
    490      */
    491     @VisibleForTesting
    492     static byte[] hashCredentialsBySaltedSha256(byte[] salt, String credentials) {
    493         byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
    494         ByteBuffer byteBuffer = ByteBuffer.allocate(
    495                 salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2);
    496         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
    497         byteBuffer.putInt(salt.length);
    498         byteBuffer.put(salt);
    499         byteBuffer.putInt(credentialsBytes.length);
    500         byteBuffer.put(credentialsBytes);
    501         byte[] bytes = byteBuffer.array();
    502 
    503         try {
    504             return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
    505         } catch (NoSuchAlgorithmException e) {
    506             // Impossible, SHA-256 must be supported on Android.
    507             throw new RuntimeException(e);
    508         }
    509     }
    510 
    511     private byte[] hashCredentialsByScrypt(byte[] salt, String credentials) {
    512         return mScrypt.scrypt(
    513                 credentials.getBytes(StandardCharsets.UTF_8), salt,
    514                 SCRYPT_PARAM_N, SCRYPT_PARAM_R, SCRYPT_PARAM_P, SCRYPT_PARAM_OUTLEN_BYTES);
    515     }
    516 
    517     private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
    518         KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
    519         keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
    520         return keyGenerator.generateKey();
    521     }
    522 
    523     private static List<WrappedApplicationKey> createApplicationKeyEntries(
    524             Map<String, byte[]> encryptedApplicationKeys) {
    525         ArrayList<WrappedApplicationKey> keyEntries = new ArrayList<>();
    526         for (String alias : encryptedApplicationKeys.keySet()) {
    527             keyEntries.add(new WrappedApplicationKey.Builder()
    528                     .setAlias(alias)
    529                     .setEncryptedKeyMaterial(encryptedApplicationKeys.get(alias))
    530                     .build());
    531         }
    532         return keyEntries;
    533     }
    534 
    535     private boolean shouldUseScryptToHashCredential() {
    536         return mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
    537     }
    538 }
    539