blob: a87adbde31e6af379dfc95ca095b5f86b027b130 [file] [log] [blame]
Robert Berry4a534ec2017-12-21 15:44:02 +00001/*
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
17package com.android.server.locksettings.recoverablekeystore;
18
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080019import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;
Robert Berrybd086f12017-12-27 13:29:39 +000020
Dmitry Dementyevabd713c2018-01-09 15:08:13 -080021import android.annotation.Nullable;
Robert Berry4a534ec2017-12-21 15:44:02 +000022import android.content.Context;
Robert Berry81ee34b2018-01-23 11:59:59 +000023import android.security.keystore.recovery.KeyDerivationParams;
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080024import android.security.keystore.recovery.KeyChainProtectionParams;
25import android.security.keystore.recovery.KeyChainSnapshot;
Robert Berry81ee34b2018-01-23 11:59:59 +000026import android.security.keystore.recovery.WrappedApplicationKey;
Robert Berry4a534ec2017-12-21 15:44:02 +000027import android.util.Log;
28
29import com.android.internal.annotations.VisibleForTesting;
Dmitry Dementyev122bfe12018-01-10 18:56:36 -080030import com.android.internal.util.ArrayUtils;
Robert Berry4a534ec2017-12-21 15:44:02 +000031import com.android.internal.widget.LockPatternUtils;
32import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
Robert Berrybd086f12017-12-27 13:29:39 +000033import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000034
35import java.nio.ByteBuffer;
36import java.nio.ByteOrder;
37import java.nio.charset.StandardCharsets;
Robert Berryf0a4bea2017-12-22 13:17:32 +000038import java.security.GeneralSecurityException;
Robert Berry26cbb6b2018-01-22 21:59:30 +000039import java.security.InvalidAlgorithmParameterException;
Robert Berry4a534ec2017-12-21 15:44:02 +000040import java.security.InvalidKeyException;
41import java.security.KeyStoreException;
42import java.security.MessageDigest;
43import java.security.NoSuchAlgorithmException;
44import java.security.PublicKey;
45import java.security.SecureRandom;
Robert Berryf0a4bea2017-12-22 13:17:32 +000046import java.security.UnrecoverableKeyException;
Bo Zhu14d993d2018-02-03 21:38:48 -080047import java.security.cert.CertPath;
Bo Zhu63610802018-03-09 12:32:13 -080048import java.security.cert.CertificateException;
Robert Berrybd086f12017-12-27 13:29:39 +000049import java.util.ArrayList;
50import java.util.List;
Robert Berryf0a4bea2017-12-22 13:17:32 +000051import java.util.Map;
Robert Berry4a534ec2017-12-21 15:44:02 +000052
53import javax.crypto.KeyGenerator;
Robert Berryf0a4bea2017-12-22 13:17:32 +000054import javax.crypto.NoSuchPaddingException;
Robert Berry4a534ec2017-12-21 15:44:02 +000055import javax.crypto.SecretKey;
56
57/**
58 * Task to sync application keys to a remote vault service.
59 *
Robert Berryf0a4bea2017-12-22 13:17:32 +000060 * @hide
Robert Berry4a534ec2017-12-21 15:44:02 +000061 */
62public class KeySyncTask implements Runnable {
63 private static final String TAG = "KeySyncTask";
64
65 private static final String RECOVERY_KEY_ALGORITHM = "AES";
66 private static final int RECOVERY_KEY_SIZE_BITS = 256;
67 private static final int SALT_LENGTH_BYTES = 16;
68 private static final int LENGTH_PREFIX_BYTES = Integer.BYTES;
69 private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256";
Robert Berry94ea4e42017-12-28 12:08:30 +000070 private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10;
Robert Berry4a534ec2017-12-21 15:44:02 +000071
Robert Berry4a534ec2017-12-21 15:44:02 +000072 private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
73 private final int mUserId;
74 private final int mCredentialType;
75 private final String mCredential;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080076 private final boolean mCredentialUpdated;
Dmitry Dementyev6e167242018-01-25 15:29:50 -080077 private final PlatformKeyManager mPlatformKeyManager;
Robert Berrybd086f12017-12-27 13:29:39 +000078 private final RecoverySnapshotStorage mRecoverySnapshotStorage;
Robert Berry91044042017-12-27 12:05:58 +000079 private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000080
81 public static KeySyncTask newInstance(
82 Context context,
83 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +000084 RecoverySnapshotStorage snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +000085 RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +000086 int userId,
87 int credentialType,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080088 String credential,
89 boolean credentialUpdated
Robert Berry4a534ec2017-12-21 15:44:02 +000090 ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
91 return new KeySyncTask(
Robert Berry4a534ec2017-12-21 15:44:02 +000092 recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +000093 snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +000094 recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +000095 userId,
96 credentialType,
Robert Berryf0a4bea2017-12-22 13:17:32 +000097 credential,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080098 credentialUpdated,
Dmitry Dementyev6e167242018-01-25 15:29:50 -080099 PlatformKeyManager.getInstance(context, recoverableKeyStoreDb));
Robert Berry4a534ec2017-12-21 15:44:02 +0000100 }
101
102 /**
103 * A new task.
104 *
105 * @param recoverableKeyStoreDb Database where the keys are stored.
106 * @param userId The uid of the user whose profile has been unlocked.
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800107 * @param credentialType The type of credential as defined in {@code LockPatternUtils}
Robert Berry4a534ec2017-12-21 15:44:02 +0000108 * @param credential The credential, encoded as a {@link String}.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800109 * @param credentialUpdated signals weather credentials were updated.
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800110 * @param platformKeyManager platform key manager
Robert Berry4a534ec2017-12-21 15:44:02 +0000111 */
112 @VisibleForTesting
113 KeySyncTask(
Robert Berry4a534ec2017-12-21 15:44:02 +0000114 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +0000115 RecoverySnapshotStorage snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +0000116 RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +0000117 int userId,
118 int credentialType,
Robert Berryf0a4bea2017-12-22 13:17:32 +0000119 String credential,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800120 boolean credentialUpdated,
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800121 PlatformKeyManager platformKeyManager) {
Robert Berry91044042017-12-27 12:05:58 +0000122 mSnapshotListenersStorage = recoverySnapshotListenersStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000123 mRecoverableKeyStoreDb = recoverableKeyStoreDb;
124 mUserId = userId;
125 mCredentialType = credentialType;
126 mCredential = credential;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800127 mCredentialUpdated = credentialUpdated;
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800128 mPlatformKeyManager = platformKeyManager;
Robert Berrybd086f12017-12-27 13:29:39 +0000129 mRecoverySnapshotStorage = snapshotStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000130 }
131
132 @Override
133 public void run() {
134 try {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800135 // Only one task is active If user unlocks phone many times in a short time interval.
136 synchronized(KeySyncTask.class) {
137 syncKeys();
138 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000139 } catch (Exception e) {
140 Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
141 }
142 }
143
144 private void syncKeys() {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800145 if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
146 // Application keys for the user will not be available for sync.
147 Log.w(TAG, "Credentials are not set for user " + mUserId);
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800148 int generation = mPlatformKeyManager.getGenerationId(mUserId);
149 mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800150 return;
151 }
Aseem Kumar3326da52018-03-12 18:05:16 -0700152 if (isCustomLockScreen()) {
153 Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId);
154 mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
155 return;
156 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800157
158 List<Integer> recoveryAgents = mRecoverableKeyStoreDb.getRecoveryAgents(mUserId);
159 for (int uid : recoveryAgents) {
160 syncKeysForAgent(uid);
161 }
162 if (recoveryAgents.isEmpty()) {
163 Log.w(TAG, "No recovery agent initialized for user " + mUserId);
164 }
165 }
166
Aseem Kumar3326da52018-03-12 18:05:16 -0700167 private boolean isCustomLockScreen() {
168 return mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE
169 && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PATTERN
170 && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
171 }
172
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800173 private void syncKeysForAgent(int recoveryAgentUid) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800174 boolean recreateCurrentVersion = false;
Robert Berry2fd4b592018-03-15 15:28:05 +0000175 if (!shouldCreateSnapshot(recoveryAgentUid)) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800176 recreateCurrentVersion =
177 (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null)
178 && (mRecoverySnapshotStorage.get(recoveryAgentUid) == null);
179 if (recreateCurrentVersion) {
180 Log.d(TAG, "Recreating most recent snapshot");
181 } else {
182 Log.d(TAG, "Key sync not needed.");
183 return;
184 }
Robert Berryf0a4bea2017-12-22 13:17:32 +0000185 }
186
Bo Zhu14d993d2018-02-03 21:38:48 -0800187 PublicKey publicKey;
188 CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800189 recoveryAgentUid);
Bo Zhu14d993d2018-02-03 21:38:48 -0800190 if (certPath != null) {
191 Log.d(TAG, "Using the public key in stored CertPath for syncing");
192 publicKey = certPath.getCertificates().get(0).getPublicKey();
193 } else {
194 Log.d(TAG, "Using the stored raw public key for syncing");
195 publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
196 recoveryAgentUid);
197 }
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000198 if (publicKey == null) {
199 Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task.");
200 return;
201 }
202
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800203 byte[] vaultHandle = mRecoverableKeyStoreDb.getServerParams(mUserId, recoveryAgentUid);
204 if (vaultHandle == null) {
Robert Berry94ea4e42017-12-28 12:08:30 +0000205 Log.w(TAG, "No device ID set for user " + mUserId);
206 return;
207 }
208
Robert Berry4a534ec2017-12-21 15:44:02 +0000209 byte[] salt = generateSalt();
210 byte[] localLskfHash = hashCredentials(salt, mCredential);
211
Robert Berryf0a4bea2017-12-22 13:17:32 +0000212 Map<String, SecretKey> rawKeys;
213 try {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800214 rawKeys = getKeysToSync(recoveryAgentUid);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000215 } catch (GeneralSecurityException e) {
216 Log.e(TAG, "Failed to load recoverable keys for sync", e);
217 return;
218 } catch (InsecureUserException e) {
219 Log.wtf(TAG, "A screen unlock triggered the key sync flow, so user must have "
220 + "lock screen. This should be impossible.", e);
221 return;
222 } catch (BadPlatformKeyException e) {
223 Log.wtf(TAG, "Loaded keys for same generation ID as platform key, so "
224 + "BadPlatformKeyException should be impossible.", e);
225 return;
226 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000227
228 SecretKey recoveryKey;
229 try {
230 recoveryKey = generateRecoveryKey();
231 } catch (NoSuchAlgorithmException e) {
232 Log.wtf("AES should never be unavailable", e);
233 return;
234 }
235
Robert Berryf0a4bea2017-12-22 13:17:32 +0000236 Map<String, byte[]> encryptedApplicationKeys;
237 try {
238 encryptedApplicationKeys = KeySyncUtils.encryptKeysWithRecoveryKey(
239 recoveryKey, rawKeys);
240 } catch (InvalidKeyException | NoSuchAlgorithmException e) {
241 Log.wtf(TAG,
242 "Should be impossible: could not encrypt application keys with random key",
243 e);
244 return;
245 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000246
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800247 Long counterId;
248 // counter id is generated exactly once for each credentials value.
249 if (mCredentialUpdated) {
250 counterId = generateAndStoreCounterId(recoveryAgentUid);
251 } else {
252 counterId = mRecoverableKeyStoreDb.getCounterId(mUserId, recoveryAgentUid);
253 if (counterId == null) {
254 counterId = generateAndStoreCounterId(recoveryAgentUid);
255 }
256 }
Dmitry Dementyevae6ec6d2018-01-18 14:29:49 -0800257
Robert Berry94ea4e42017-12-28 12:08:30 +0000258 byte[] vaultParams = KeySyncUtils.packVaultParams(
259 publicKey,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800260 counterId,
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800261 TRUSTED_HARDWARE_MAX_ATTEMPTS,
262 vaultHandle);
Robert Berry4a534ec2017-12-21 15:44:02 +0000263
Robert Berryf0a4bea2017-12-22 13:17:32 +0000264 byte[] encryptedRecoveryKey;
Robert Berry4a534ec2017-12-21 15:44:02 +0000265 try {
Robert Berryf0a4bea2017-12-22 13:17:32 +0000266 encryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey(
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000267 publicKey,
Robert Berry4a534ec2017-12-21 15:44:02 +0000268 localLskfHash,
269 vaultParams,
270 recoveryKey);
271 } catch (NoSuchAlgorithmException e) {
272 Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e);
273 return;
274 } catch (InvalidKeyException e) {
275 Log.e(TAG,"Could not encrypt with recovery key", e);
276 return;
277 }
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800278 KeyChainProtectionParams metadata = new KeyChainProtectionParams.Builder()
279 .setUserSecretType(TYPE_LOCKSCREEN)
280 .setLockScreenUiFormat(getUiFormat(mCredentialType, mCredential))
281 .setKeyDerivationParams(KeyDerivationParams.createSha256Params(salt))
282 .setSecret(new byte[0])
283 .build();
284
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800285 ArrayList<KeyChainProtectionParams> metadataList = new ArrayList<>();
Robert Berrybd086f12017-12-27 13:29:39 +0000286 metadataList.add(metadata);
287
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800288 // If application keys are not updated, snapshot will not be created on next unlock.
289 mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);
290
Bo Zhu63610802018-03-09 12:32:13 -0800291 KeyChainSnapshot.Builder keyChainSnapshotBuilder = new KeyChainSnapshot.Builder()
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800292 .setSnapshotVersion(getSnapshotVersion(recoveryAgentUid, recreateCurrentVersion))
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800293 .setMaxAttempts(TRUSTED_HARDWARE_MAX_ATTEMPTS)
294 .setCounterId(counterId)
295 .setTrustedHardwarePublicKey(SecureBox.encodePublicKey(publicKey))
296 .setServerParams(vaultHandle)
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800297 .setKeyChainProtectionParams(metadataList)
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800298 .setWrappedApplicationKeys(createApplicationKeyEntries(encryptedApplicationKeys))
Bo Zhu63610802018-03-09 12:32:13 -0800299 .setEncryptedRecoveryKeyBlob(encryptedRecoveryKey);
300 try {
301 keyChainSnapshotBuilder.setTrustedHardwareCertPath(certPath);
302 } catch(CertificateException e) {
303 // Should not happen, as it's just deserialized from bytes stored in the db
304 Log.wtf(TAG, "Cannot serialize CertPath when calling setTrustedHardwareCertPath", e);
305 return;
306 }
307 mRecoverySnapshotStorage.put(recoveryAgentUid, keyChainSnapshotBuilder.build());
Robert Berry91044042017-12-27 12:05:58 +0000308 mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
Robert Berry4a534ec2017-12-21 15:44:02 +0000309 }
310
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800311 @VisibleForTesting
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800312 int getSnapshotVersion(int recoveryAgentUid, boolean recreateCurrentVersion) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800313 Long snapshotVersion = mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid);
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800314 if (recreateCurrentVersion) {
315 // version shouldn't be null at this moment.
316 snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion;
317 } else {
318 snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion + 1;
319 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800320 mRecoverableKeyStoreDb.setSnapshotVersion(mUserId, recoveryAgentUid, snapshotVersion);
321
322 return snapshotVersion.intValue();
323 }
324
325 private long generateAndStoreCounterId(int recoveryAgentUid) {
326 long counter = new SecureRandom().nextLong();
327 mRecoverableKeyStoreDb.setCounterId(mUserId, recoveryAgentUid, counter);
328 return counter;
Robert Berry4a534ec2017-12-21 15:44:02 +0000329 }
330
331 /**
Robert Berryf0a4bea2017-12-22 13:17:32 +0000332 * Returns all of the recoverable keys for the user.
333 */
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800334 private Map<String, SecretKey> getKeysToSync(int recoveryAgentUid)
Robert Berryf0a4bea2017-12-22 13:17:32 +0000335 throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
Robert Berry26cbb6b2018-01-22 21:59:30 +0000336 NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException,
337 InvalidKeyException, InvalidAlgorithmParameterException {
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800338 PlatformDecryptionKey decryptKey = mPlatformKeyManager.getDecryptKey(mUserId);;
Robert Berryf0a4bea2017-12-22 13:17:32 +0000339 Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800340 mUserId, recoveryAgentUid, decryptKey.getGenerationId());
Robert Berryf0a4bea2017-12-22 13:17:32 +0000341 return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
342 }
343
344 /**
345 * Returns {@code true} if a sync is pending.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800346 * @param recoveryAgentUid uid of the recovery agent.
Robert Berryf0a4bea2017-12-22 13:17:32 +0000347 */
Robert Berry2fd4b592018-03-15 15:28:05 +0000348 private boolean shouldCreateSnapshot(int recoveryAgentUid) {
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800349 int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid);
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800350 if (!ArrayUtils.contains(types, KeyChainProtectionParams.TYPE_LOCKSCREEN)) {
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800351 // Only lockscreen type is supported.
352 // We will need to pass extra argument to KeySyncTask to support custom pass phrase.
353 return false;
354 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800355 if (mCredentialUpdated) {
356 // Sync credential if at least one snapshot was created.
357 if (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null) {
358 mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, true);
359 return true;
360 }
361 }
362
363 return mRecoverableKeyStoreDb.getShouldCreateSnapshot(mUserId, recoveryAgentUid);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000364 }
365
366 /**
Robert Berry4a534ec2017-12-21 15:44:02 +0000367 * The UI best suited to entering the given lock screen. This is synced with the vault so the
368 * user can be shown the same UI when recovering the vault on another device.
369 *
370 * @return The format - either pattern, pin, or password.
371 */
372 @VisibleForTesting
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800373 @KeyChainProtectionParams.LockScreenUiFormat static int getUiFormat(
Robert Berry4a534ec2017-12-21 15:44:02 +0000374 int credentialType, String credential) {
375 if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800376 return KeyChainProtectionParams.UI_FORMAT_PATTERN;
Robert Berry4a534ec2017-12-21 15:44:02 +0000377 } else if (isPin(credential)) {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800378 return KeyChainProtectionParams.UI_FORMAT_PIN;
Robert Berry4a534ec2017-12-21 15:44:02 +0000379 } else {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800380 return KeyChainProtectionParams.UI_FORMAT_PASSWORD;
Robert Berry4a534ec2017-12-21 15:44:02 +0000381 }
382 }
383
384 /**
385 * Generates a salt to include with the lock screen hash.
386 *
387 * @return The salt.
388 */
389 private byte[] generateSalt() {
390 byte[] salt = new byte[SALT_LENGTH_BYTES];
391 new SecureRandom().nextBytes(salt);
392 return salt;
393 }
394
395 /**
396 * Returns {@code true} if {@code credential} looks like a pin.
397 */
398 @VisibleForTesting
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800399 static boolean isPin(@Nullable String credential) {
400 if (credential == null) {
401 return false;
402 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000403 int length = credential.length();
404 for (int i = 0; i < length; i++) {
405 if (!Character.isDigit(credential.charAt(i))) {
406 return false;
407 }
408 }
409 return true;
410 }
411
412 /**
413 * Hashes {@code credentials} with the given {@code salt}.
414 *
415 * @return The SHA-256 hash.
416 */
417 @VisibleForTesting
418 static byte[] hashCredentials(byte[] salt, String credentials) {
419 byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
420 ByteBuffer byteBuffer = ByteBuffer.allocate(
421 salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2);
422 byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
423 byteBuffer.putInt(salt.length);
424 byteBuffer.put(salt);
425 byteBuffer.putInt(credentialsBytes.length);
426 byteBuffer.put(credentialsBytes);
427 byte[] bytes = byteBuffer.array();
428
429 try {
430 return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
431 } catch (NoSuchAlgorithmException e) {
432 // Impossible, SHA-256 must be supported on Android.
433 throw new RuntimeException(e);
434 }
435 }
436
437 private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
438 KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
439 keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
440 return keyGenerator.generateKey();
441 }
Robert Berryf0a4bea2017-12-22 13:17:32 +0000442
Robert Berry5f138702018-01-17 15:18:05 +0000443 private static List<WrappedApplicationKey> createApplicationKeyEntries(
Robert Berrybd086f12017-12-27 13:29:39 +0000444 Map<String, byte[]> encryptedApplicationKeys) {
Robert Berry5f138702018-01-17 15:18:05 +0000445 ArrayList<WrappedApplicationKey> keyEntries = new ArrayList<>();
Robert Berrybd086f12017-12-27 13:29:39 +0000446 for (String alias : encryptedApplicationKeys.keySet()) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800447 keyEntries.add(new WrappedApplicationKey.Builder()
448 .setAlias(alias)
449 .setEncryptedKeyMaterial(encryptedApplicationKeys.get(alias))
450 .build());
Robert Berrybd086f12017-12-27 13:29:39 +0000451 }
452 return keyEntries;
Robert Berryf0a4bea2017-12-22 13:17:32 +0000453 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000454}