blob: 567eaaa5d1596551d5ee43509274428c97717769 [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;
Bo Zhu76973432018-04-03 00:37:51 -070023import android.security.Scrypt;
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080024import android.security.keystore.recovery.KeyChainProtectionParams;
25import android.security.keystore.recovery.KeyChainSnapshot;
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -070026import android.security.keystore.recovery.KeyDerivationParams;
Robert Berry81ee34b2018-01-23 11:59:59 +000027import android.security.keystore.recovery.WrappedApplicationKey;
Robert Berry4a534ec2017-12-21 15:44:02 +000028import android.util.Log;
29
30import com.android.internal.annotations.VisibleForTesting;
Dmitry Dementyev122bfe12018-01-10 18:56:36 -080031import com.android.internal.util.ArrayUtils;
Robert Berry4a534ec2017-12-21 15:44:02 +000032import com.android.internal.widget.LockPatternUtils;
33import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
Robert Berrybd086f12017-12-27 13:29:39 +000034import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000035
36import java.nio.ByteBuffer;
37import java.nio.ByteOrder;
38import java.nio.charset.StandardCharsets;
Robert Berryf0a4bea2017-12-22 13:17:32 +000039import java.security.GeneralSecurityException;
Robert Berry26cbb6b2018-01-22 21:59:30 +000040import java.security.InvalidAlgorithmParameterException;
Robert Berry4a534ec2017-12-21 15:44:02 +000041import java.security.InvalidKeyException;
42import java.security.KeyStoreException;
43import java.security.MessageDigest;
44import java.security.NoSuchAlgorithmException;
45import java.security.PublicKey;
46import java.security.SecureRandom;
Robert Berryf0a4bea2017-12-22 13:17:32 +000047import java.security.UnrecoverableKeyException;
Bo Zhu14d993d2018-02-03 21:38:48 -080048import java.security.cert.CertPath;
Bo Zhu63610802018-03-09 12:32:13 -080049import java.security.cert.CertificateException;
Robert Berrybd086f12017-12-27 13:29:39 +000050import java.util.ArrayList;
51import java.util.List;
Robert Berryf0a4bea2017-12-22 13:17:32 +000052import java.util.Map;
Robert Berry4a534ec2017-12-21 15:44:02 +000053
54import javax.crypto.KeyGenerator;
Robert Berryf0a4bea2017-12-22 13:17:32 +000055import javax.crypto.NoSuchPaddingException;
Robert Berry4a534ec2017-12-21 15:44:02 +000056import javax.crypto.SecretKey;
57
58/**
59 * Task to sync application keys to a remote vault service.
60 *
Robert Berryf0a4bea2017-12-22 13:17:32 +000061 * @hide
Robert Berry4a534ec2017-12-21 15:44:02 +000062 */
63public class KeySyncTask implements Runnable {
64 private static final String TAG = "KeySyncTask";
65
66 private static final String RECOVERY_KEY_ALGORITHM = "AES";
67 private static final int RECOVERY_KEY_SIZE_BITS = 256;
68 private static final int SALT_LENGTH_BYTES = 16;
69 private static final int LENGTH_PREFIX_BYTES = Integer.BYTES;
70 private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256";
Robert Berry94ea4e42017-12-28 12:08:30 +000071 private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10;
Robert Berry4a534ec2017-12-21 15:44:02 +000072
Bo Zhu76973432018-04-03 00:37:51 -070073 // TODO: Reduce the minimal length once all other components are updated
74 private static final int MIN_CREDENTIAL_LEN_TO_USE_SCRYPT = 24;
75 @VisibleForTesting
76 static final int SCRYPT_PARAM_N = 4096;
77 @VisibleForTesting
78 static final int SCRYPT_PARAM_R = 8;
79 @VisibleForTesting
80 static final int SCRYPT_PARAM_P = 1;
81 @VisibleForTesting
82 static final int SCRYPT_PARAM_OUTLEN_BYTES = 32;
83
Robert Berry4a534ec2017-12-21 15:44:02 +000084 private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
85 private final int mUserId;
86 private final int mCredentialType;
87 private final String mCredential;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080088 private final boolean mCredentialUpdated;
Dmitry Dementyev6e167242018-01-25 15:29:50 -080089 private final PlatformKeyManager mPlatformKeyManager;
Robert Berrybd086f12017-12-27 13:29:39 +000090 private final RecoverySnapshotStorage mRecoverySnapshotStorage;
Robert Berry91044042017-12-27 12:05:58 +000091 private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -070092 private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
Bo Zhu76973432018-04-03 00:37:51 -070093 private final Scrypt mScrypt;
Robert Berry4a534ec2017-12-21 15:44:02 +000094
95 public static KeySyncTask newInstance(
96 Context context,
97 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +000098 RecoverySnapshotStorage snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +000099 RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +0000100 int userId,
101 int credentialType,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800102 String credential,
103 boolean credentialUpdated
Robert Berry4a534ec2017-12-21 15:44:02 +0000104 ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
105 return new KeySyncTask(
Robert Berry4a534ec2017-12-21 15:44:02 +0000106 recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +0000107 snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +0000108 recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +0000109 userId,
110 credentialType,
Robert Berryf0a4bea2017-12-22 13:17:32 +0000111 credential,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800112 credentialUpdated,
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700113 PlatformKeyManager.getInstance(context, recoverableKeyStoreDb),
Bo Zhu76973432018-04-03 00:37:51 -0700114 new TestOnlyInsecureCertificateHelper(),
115 new Scrypt());
Robert Berry4a534ec2017-12-21 15:44:02 +0000116 }
117
118 /**
119 * A new task.
120 *
121 * @param recoverableKeyStoreDb Database where the keys are stored.
122 * @param userId The uid of the user whose profile has been unlocked.
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800123 * @param credentialType The type of credential as defined in {@code LockPatternUtils}
Robert Berry4a534ec2017-12-21 15:44:02 +0000124 * @param credential The credential, encoded as a {@link String}.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800125 * @param credentialUpdated signals weather credentials were updated.
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800126 * @param platformKeyManager platform key manager
Bo Zhu76973432018-04-03 00:37:51 -0700127 * @param testOnlyInsecureCertificateHelper utility class used for end-to-end tests
Robert Berry4a534ec2017-12-21 15:44:02 +0000128 */
129 @VisibleForTesting
130 KeySyncTask(
Robert Berry4a534ec2017-12-21 15:44:02 +0000131 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +0000132 RecoverySnapshotStorage snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +0000133 RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +0000134 int userId,
135 int credentialType,
Robert Berryf0a4bea2017-12-22 13:17:32 +0000136 String credential,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800137 boolean credentialUpdated,
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700138 PlatformKeyManager platformKeyManager,
Bo Zhu76973432018-04-03 00:37:51 -0700139 TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper,
140 Scrypt scrypt) {
Robert Berry91044042017-12-27 12:05:58 +0000141 mSnapshotListenersStorage = recoverySnapshotListenersStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000142 mRecoverableKeyStoreDb = recoverableKeyStoreDb;
143 mUserId = userId;
144 mCredentialType = credentialType;
145 mCredential = credential;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800146 mCredentialUpdated = credentialUpdated;
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800147 mPlatformKeyManager = platformKeyManager;
Robert Berrybd086f12017-12-27 13:29:39 +0000148 mRecoverySnapshotStorage = snapshotStorage;
Bo Zhu76973432018-04-03 00:37:51 -0700149 mTestOnlyInsecureCertificateHelper = testOnlyInsecureCertificateHelper;
150 mScrypt = scrypt;
Robert Berry4a534ec2017-12-21 15:44:02 +0000151 }
152
153 @Override
154 public void run() {
155 try {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800156 // Only one task is active If user unlocks phone many times in a short time interval.
157 synchronized(KeySyncTask.class) {
158 syncKeys();
159 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000160 } catch (Exception e) {
161 Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
162 }
163 }
164
165 private void syncKeys() {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800166 if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
167 // Application keys for the user will not be available for sync.
168 Log.w(TAG, "Credentials are not set for user " + mUserId);
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800169 int generation = mPlatformKeyManager.getGenerationId(mUserId);
170 mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800171 return;
172 }
Aseem Kumar3326da52018-03-12 18:05:16 -0700173 if (isCustomLockScreen()) {
174 Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId);
175 mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
176 return;
177 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800178
179 List<Integer> recoveryAgents = mRecoverableKeyStoreDb.getRecoveryAgents(mUserId);
180 for (int uid : recoveryAgents) {
181 syncKeysForAgent(uid);
182 }
183 if (recoveryAgents.isEmpty()) {
184 Log.w(TAG, "No recovery agent initialized for user " + mUserId);
185 }
186 }
187
Aseem Kumar3326da52018-03-12 18:05:16 -0700188 private boolean isCustomLockScreen() {
189 return mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE
190 && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PATTERN
191 && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
192 }
193
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800194 private void syncKeysForAgent(int recoveryAgentUid) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800195 boolean recreateCurrentVersion = false;
Robert Berry2fd4b592018-03-15 15:28:05 +0000196 if (!shouldCreateSnapshot(recoveryAgentUid)) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800197 recreateCurrentVersion =
198 (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null)
199 && (mRecoverySnapshotStorage.get(recoveryAgentUid) == null);
200 if (recreateCurrentVersion) {
201 Log.d(TAG, "Recreating most recent snapshot");
202 } else {
203 Log.d(TAG, "Key sync not needed.");
204 return;
205 }
Robert Berryf0a4bea2017-12-22 13:17:32 +0000206 }
207
Bo Zhu14d993d2018-02-03 21:38:48 -0800208 PublicKey publicKey;
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700209 String rootCertAlias =
210 mRecoverableKeyStoreDb.getActiveRootOfTrust(mUserId, recoveryAgentUid);
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700211 rootCertAlias = mTestOnlyInsecureCertificateHelper
212 .getDefaultCertificateAliasIfEmpty(rootCertAlias);
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700213
Bo Zhu14d993d2018-02-03 21:38:48 -0800214 CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId,
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700215 recoveryAgentUid, rootCertAlias);
Bo Zhu14d993d2018-02-03 21:38:48 -0800216 if (certPath != null) {
217 Log.d(TAG, "Using the public key in stored CertPath for syncing");
218 publicKey = certPath.getCertificates().get(0).getPublicKey();
219 } else {
220 Log.d(TAG, "Using the stored raw public key for syncing");
221 publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
222 recoveryAgentUid);
223 }
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000224 if (publicKey == null) {
225 Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task.");
226 return;
227 }
228
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800229 byte[] vaultHandle = mRecoverableKeyStoreDb.getServerParams(mUserId, recoveryAgentUid);
230 if (vaultHandle == null) {
Robert Berry94ea4e42017-12-28 12:08:30 +0000231 Log.w(TAG, "No device ID set for user " + mUserId);
232 return;
233 }
234
Bo Zhu0b8c82e2018-03-30 11:31:53 -0700235 if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(rootCertAlias)) {
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700236 Log.w(TAG, "Insecure root certificate is used by recovery agent "
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700237 + recoveryAgentUid);
Bo Zhu0b8c82e2018-03-30 11:31:53 -0700238 if (mTestOnlyInsecureCertificateHelper.doesCredentialSupportInsecureMode(
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700239 mCredentialType, mCredential)) {
240 Log.w(TAG, "Whitelisted credential is used to generate snapshot by "
241 + "recovery agent "+ recoveryAgentUid);
242 } else {
243 Log.w(TAG, "Non whitelisted credential is used to generate recovery snapshot by "
244 + recoveryAgentUid + " - ignore attempt.");
245 return; // User secret will not be used.
246 }
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700247 }
248
Bo Zhu76973432018-04-03 00:37:51 -0700249 boolean useScryptToHashCredential = shouldUseScryptToHashCredential(rootCertAlias);
Robert Berry4a534ec2017-12-21 15:44:02 +0000250 byte[] salt = generateSalt();
Bo Zhu76973432018-04-03 00:37:51 -0700251 byte[] localLskfHash;
252 if (useScryptToHashCredential) {
253 localLskfHash = hashCredentialsByScrypt(salt, mCredential);
254 } else {
255 localLskfHash = hashCredentialsBySaltedSha256(salt, mCredential);
256 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000257
Robert Berryf0a4bea2017-12-22 13:17:32 +0000258 Map<String, SecretKey> rawKeys;
259 try {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800260 rawKeys = getKeysToSync(recoveryAgentUid);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000261 } catch (GeneralSecurityException e) {
262 Log.e(TAG, "Failed to load recoverable keys for sync", e);
263 return;
264 } catch (InsecureUserException e) {
265 Log.wtf(TAG, "A screen unlock triggered the key sync flow, so user must have "
266 + "lock screen. This should be impossible.", e);
267 return;
268 } catch (BadPlatformKeyException e) {
269 Log.wtf(TAG, "Loaded keys for same generation ID as platform key, so "
270 + "BadPlatformKeyException should be impossible.", e);
271 return;
272 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000273
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700274 // Only include insecure key material for test
Bo Zhu0b8c82e2018-03-30 11:31:53 -0700275 if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(rootCertAlias)) {
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700276 rawKeys = mTestOnlyInsecureCertificateHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
277 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000278 SecretKey recoveryKey;
279 try {
280 recoveryKey = generateRecoveryKey();
281 } catch (NoSuchAlgorithmException e) {
282 Log.wtf("AES should never be unavailable", e);
283 return;
284 }
285
Robert Berryf0a4bea2017-12-22 13:17:32 +0000286 Map<String, byte[]> encryptedApplicationKeys;
287 try {
288 encryptedApplicationKeys = KeySyncUtils.encryptKeysWithRecoveryKey(
289 recoveryKey, rawKeys);
290 } catch (InvalidKeyException | NoSuchAlgorithmException e) {
291 Log.wtf(TAG,
292 "Should be impossible: could not encrypt application keys with random key",
293 e);
294 return;
295 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000296
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800297 Long counterId;
298 // counter id is generated exactly once for each credentials value.
299 if (mCredentialUpdated) {
300 counterId = generateAndStoreCounterId(recoveryAgentUid);
301 } else {
302 counterId = mRecoverableKeyStoreDb.getCounterId(mUserId, recoveryAgentUid);
303 if (counterId == null) {
304 counterId = generateAndStoreCounterId(recoveryAgentUid);
305 }
306 }
Dmitry Dementyevae6ec6d2018-01-18 14:29:49 -0800307
Robert Berry94ea4e42017-12-28 12:08:30 +0000308 byte[] vaultParams = KeySyncUtils.packVaultParams(
309 publicKey,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800310 counterId,
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800311 TRUSTED_HARDWARE_MAX_ATTEMPTS,
312 vaultHandle);
Robert Berry4a534ec2017-12-21 15:44:02 +0000313
Robert Berryf0a4bea2017-12-22 13:17:32 +0000314 byte[] encryptedRecoveryKey;
Robert Berry4a534ec2017-12-21 15:44:02 +0000315 try {
Robert Berryf0a4bea2017-12-22 13:17:32 +0000316 encryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey(
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000317 publicKey,
Robert Berry4a534ec2017-12-21 15:44:02 +0000318 localLskfHash,
319 vaultParams,
320 recoveryKey);
321 } catch (NoSuchAlgorithmException e) {
322 Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e);
323 return;
324 } catch (InvalidKeyException e) {
325 Log.e(TAG,"Could not encrypt with recovery key", e);
326 return;
327 }
Bo Zhu76973432018-04-03 00:37:51 -0700328 KeyDerivationParams keyDerivationParams;
329 if (useScryptToHashCredential) {
330 keyDerivationParams = KeyDerivationParams.createScryptParams(
331 salt, /*memoryDifficulty=*/ SCRYPT_PARAM_N);
332 } else {
333 keyDerivationParams = KeyDerivationParams.createSha256Params(salt);
334 }
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800335 KeyChainProtectionParams metadata = new KeyChainProtectionParams.Builder()
336 .setUserSecretType(TYPE_LOCKSCREEN)
337 .setLockScreenUiFormat(getUiFormat(mCredentialType, mCredential))
Bo Zhu76973432018-04-03 00:37:51 -0700338 .setKeyDerivationParams(keyDerivationParams)
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800339 .setSecret(new byte[0])
340 .build();
341
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800342 ArrayList<KeyChainProtectionParams> metadataList = new ArrayList<>();
Robert Berrybd086f12017-12-27 13:29:39 +0000343 metadataList.add(metadata);
344
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800345 // If application keys are not updated, snapshot will not be created on next unlock.
346 mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);
347
Bo Zhu63610802018-03-09 12:32:13 -0800348 KeyChainSnapshot.Builder keyChainSnapshotBuilder = new KeyChainSnapshot.Builder()
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800349 .setSnapshotVersion(getSnapshotVersion(recoveryAgentUid, recreateCurrentVersion))
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800350 .setMaxAttempts(TRUSTED_HARDWARE_MAX_ATTEMPTS)
351 .setCounterId(counterId)
352 .setTrustedHardwarePublicKey(SecureBox.encodePublicKey(publicKey))
353 .setServerParams(vaultHandle)
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800354 .setKeyChainProtectionParams(metadataList)
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800355 .setWrappedApplicationKeys(createApplicationKeyEntries(encryptedApplicationKeys))
Bo Zhu63610802018-03-09 12:32:13 -0800356 .setEncryptedRecoveryKeyBlob(encryptedRecoveryKey);
357 try {
358 keyChainSnapshotBuilder.setTrustedHardwareCertPath(certPath);
359 } catch(CertificateException e) {
360 // Should not happen, as it's just deserialized from bytes stored in the db
361 Log.wtf(TAG, "Cannot serialize CertPath when calling setTrustedHardwareCertPath", e);
362 return;
363 }
364 mRecoverySnapshotStorage.put(recoveryAgentUid, keyChainSnapshotBuilder.build());
Robert Berry91044042017-12-27 12:05:58 +0000365 mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
Robert Berry4a534ec2017-12-21 15:44:02 +0000366 }
367
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800368 @VisibleForTesting
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800369 int getSnapshotVersion(int recoveryAgentUid, boolean recreateCurrentVersion) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800370 Long snapshotVersion = mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid);
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800371 if (recreateCurrentVersion) {
372 // version shouldn't be null at this moment.
373 snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion;
374 } else {
375 snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion + 1;
376 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800377 mRecoverableKeyStoreDb.setSnapshotVersion(mUserId, recoveryAgentUid, snapshotVersion);
378
379 return snapshotVersion.intValue();
380 }
381
382 private long generateAndStoreCounterId(int recoveryAgentUid) {
383 long counter = new SecureRandom().nextLong();
384 mRecoverableKeyStoreDb.setCounterId(mUserId, recoveryAgentUid, counter);
385 return counter;
Robert Berry4a534ec2017-12-21 15:44:02 +0000386 }
387
388 /**
Robert Berryf0a4bea2017-12-22 13:17:32 +0000389 * Returns all of the recoverable keys for the user.
390 */
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800391 private Map<String, SecretKey> getKeysToSync(int recoveryAgentUid)
Robert Berryf0a4bea2017-12-22 13:17:32 +0000392 throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
Robert Berry26cbb6b2018-01-22 21:59:30 +0000393 NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException,
394 InvalidKeyException, InvalidAlgorithmParameterException {
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800395 PlatformDecryptionKey decryptKey = mPlatformKeyManager.getDecryptKey(mUserId);;
Robert Berryf0a4bea2017-12-22 13:17:32 +0000396 Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800397 mUserId, recoveryAgentUid, decryptKey.getGenerationId());
Robert Berryf0a4bea2017-12-22 13:17:32 +0000398 return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
399 }
400
401 /**
402 * Returns {@code true} if a sync is pending.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800403 * @param recoveryAgentUid uid of the recovery agent.
Robert Berryf0a4bea2017-12-22 13:17:32 +0000404 */
Robert Berry2fd4b592018-03-15 15:28:05 +0000405 private boolean shouldCreateSnapshot(int recoveryAgentUid) {
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800406 int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid);
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800407 if (!ArrayUtils.contains(types, KeyChainProtectionParams.TYPE_LOCKSCREEN)) {
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800408 // Only lockscreen type is supported.
409 // We will need to pass extra argument to KeySyncTask to support custom pass phrase.
410 return false;
411 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800412 if (mCredentialUpdated) {
413 // Sync credential if at least one snapshot was created.
414 if (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null) {
415 mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, true);
416 return true;
417 }
418 }
419
420 return mRecoverableKeyStoreDb.getShouldCreateSnapshot(mUserId, recoveryAgentUid);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000421 }
422
423 /**
Robert Berry4a534ec2017-12-21 15:44:02 +0000424 * The UI best suited to entering the given lock screen. This is synced with the vault so the
425 * user can be shown the same UI when recovering the vault on another device.
426 *
427 * @return The format - either pattern, pin, or password.
428 */
429 @VisibleForTesting
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800430 @KeyChainProtectionParams.LockScreenUiFormat static int getUiFormat(
Robert Berry4a534ec2017-12-21 15:44:02 +0000431 int credentialType, String credential) {
432 if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800433 return KeyChainProtectionParams.UI_FORMAT_PATTERN;
Robert Berry4a534ec2017-12-21 15:44:02 +0000434 } else if (isPin(credential)) {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800435 return KeyChainProtectionParams.UI_FORMAT_PIN;
Robert Berry4a534ec2017-12-21 15:44:02 +0000436 } else {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800437 return KeyChainProtectionParams.UI_FORMAT_PASSWORD;
Robert Berry4a534ec2017-12-21 15:44:02 +0000438 }
439 }
440
441 /**
442 * Generates a salt to include with the lock screen hash.
443 *
444 * @return The salt.
445 */
446 private byte[] generateSalt() {
447 byte[] salt = new byte[SALT_LENGTH_BYTES];
448 new SecureRandom().nextBytes(salt);
449 return salt;
450 }
451
452 /**
453 * Returns {@code true} if {@code credential} looks like a pin.
454 */
455 @VisibleForTesting
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800456 static boolean isPin(@Nullable String credential) {
457 if (credential == null) {
458 return false;
459 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000460 int length = credential.length();
461 for (int i = 0; i < length; i++) {
462 if (!Character.isDigit(credential.charAt(i))) {
463 return false;
464 }
465 }
466 return true;
467 }
468
469 /**
470 * Hashes {@code credentials} with the given {@code salt}.
471 *
472 * @return The SHA-256 hash.
473 */
474 @VisibleForTesting
Bo Zhu76973432018-04-03 00:37:51 -0700475 static byte[] hashCredentialsBySaltedSha256(byte[] salt, String credentials) {
Robert Berry4a534ec2017-12-21 15:44:02 +0000476 byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
477 ByteBuffer byteBuffer = ByteBuffer.allocate(
478 salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2);
479 byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
480 byteBuffer.putInt(salt.length);
481 byteBuffer.put(salt);
482 byteBuffer.putInt(credentialsBytes.length);
483 byteBuffer.put(credentialsBytes);
484 byte[] bytes = byteBuffer.array();
485
486 try {
487 return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
488 } catch (NoSuchAlgorithmException e) {
489 // Impossible, SHA-256 must be supported on Android.
490 throw new RuntimeException(e);
491 }
492 }
493
Bo Zhu76973432018-04-03 00:37:51 -0700494 private byte[] hashCredentialsByScrypt(byte[] salt, String credentials) {
495 return mScrypt.scrypt(
496 credentials.getBytes(StandardCharsets.UTF_8), salt,
497 SCRYPT_PARAM_N, SCRYPT_PARAM_R, SCRYPT_PARAM_P, SCRYPT_PARAM_OUTLEN_BYTES);
498 }
499
Robert Berry4a534ec2017-12-21 15:44:02 +0000500 private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
501 KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
502 keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
503 return keyGenerator.generateKey();
504 }
Robert Berryf0a4bea2017-12-22 13:17:32 +0000505
Robert Berry5f138702018-01-17 15:18:05 +0000506 private static List<WrappedApplicationKey> createApplicationKeyEntries(
Robert Berrybd086f12017-12-27 13:29:39 +0000507 Map<String, byte[]> encryptedApplicationKeys) {
Robert Berry5f138702018-01-17 15:18:05 +0000508 ArrayList<WrappedApplicationKey> keyEntries = new ArrayList<>();
Robert Berrybd086f12017-12-27 13:29:39 +0000509 for (String alias : encryptedApplicationKeys.keySet()) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800510 keyEntries.add(new WrappedApplicationKey.Builder()
511 .setAlias(alias)
512 .setEncryptedKeyMaterial(encryptedApplicationKeys.get(alias))
513 .build());
Robert Berrybd086f12017-12-27 13:29:39 +0000514 }
515 return keyEntries;
Robert Berryf0a4bea2017-12-22 13:17:32 +0000516 }
Bo Zhu76973432018-04-03 00:37:51 -0700517
518 private boolean shouldUseScryptToHashCredential(String rootCertAlias) {
519 return mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
520 && mCredential.length() >= MIN_CREDENTIAL_LEN_TO_USE_SCRYPT
521 // TODO: Remove the test cert check once all other components are updated
522 && mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(rootCertAlias);
523 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000524}