blob: 5d71cc7c46b836bda9bfda493ca03bfb6db0c9d6 [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 @VisibleForTesting
74 static final int SCRYPT_PARAM_N = 4096;
75 @VisibleForTesting
76 static final int SCRYPT_PARAM_R = 8;
77 @VisibleForTesting
78 static final int SCRYPT_PARAM_P = 1;
79 @VisibleForTesting
80 static final int SCRYPT_PARAM_OUTLEN_BYTES = 32;
81
Robert Berry4a534ec2017-12-21 15:44:02 +000082 private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
83 private final int mUserId;
84 private final int mCredentialType;
85 private final String mCredential;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080086 private final boolean mCredentialUpdated;
Dmitry Dementyev6e167242018-01-25 15:29:50 -080087 private final PlatformKeyManager mPlatformKeyManager;
Robert Berrybd086f12017-12-27 13:29:39 +000088 private final RecoverySnapshotStorage mRecoverySnapshotStorage;
Robert Berry91044042017-12-27 12:05:58 +000089 private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -070090 private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
Bo Zhu76973432018-04-03 00:37:51 -070091 private final Scrypt mScrypt;
Robert Berry4a534ec2017-12-21 15:44:02 +000092
93 public static KeySyncTask newInstance(
94 Context context,
95 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +000096 RecoverySnapshotStorage snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +000097 RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +000098 int userId,
99 int credentialType,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800100 String credential,
101 boolean credentialUpdated
Robert Berry4a534ec2017-12-21 15:44:02 +0000102 ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
103 return new KeySyncTask(
Robert Berry4a534ec2017-12-21 15:44:02 +0000104 recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +0000105 snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +0000106 recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +0000107 userId,
108 credentialType,
Robert Berryf0a4bea2017-12-22 13:17:32 +0000109 credential,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800110 credentialUpdated,
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700111 PlatformKeyManager.getInstance(context, recoverableKeyStoreDb),
Bo Zhu76973432018-04-03 00:37:51 -0700112 new TestOnlyInsecureCertificateHelper(),
113 new Scrypt());
Robert Berry4a534ec2017-12-21 15:44:02 +0000114 }
115
116 /**
117 * A new task.
118 *
119 * @param recoverableKeyStoreDb Database where the keys are stored.
120 * @param userId The uid of the user whose profile has been unlocked.
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800121 * @param credentialType The type of credential as defined in {@code LockPatternUtils}
Robert Berry4a534ec2017-12-21 15:44:02 +0000122 * @param credential The credential, encoded as a {@link String}.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800123 * @param credentialUpdated signals weather credentials were updated.
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800124 * @param platformKeyManager platform key manager
Bo Zhu76973432018-04-03 00:37:51 -0700125 * @param testOnlyInsecureCertificateHelper utility class used for end-to-end tests
Robert Berry4a534ec2017-12-21 15:44:02 +0000126 */
127 @VisibleForTesting
128 KeySyncTask(
Robert Berry4a534ec2017-12-21 15:44:02 +0000129 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +0000130 RecoverySnapshotStorage snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +0000131 RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +0000132 int userId,
133 int credentialType,
Robert Berryf0a4bea2017-12-22 13:17:32 +0000134 String credential,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800135 boolean credentialUpdated,
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700136 PlatformKeyManager platformKeyManager,
Bo Zhu76973432018-04-03 00:37:51 -0700137 TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper,
138 Scrypt scrypt) {
Robert Berry91044042017-12-27 12:05:58 +0000139 mSnapshotListenersStorage = recoverySnapshotListenersStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000140 mRecoverableKeyStoreDb = recoverableKeyStoreDb;
141 mUserId = userId;
142 mCredentialType = credentialType;
143 mCredential = credential;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800144 mCredentialUpdated = credentialUpdated;
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800145 mPlatformKeyManager = platformKeyManager;
Robert Berrybd086f12017-12-27 13:29:39 +0000146 mRecoverySnapshotStorage = snapshotStorage;
Bo Zhu76973432018-04-03 00:37:51 -0700147 mTestOnlyInsecureCertificateHelper = testOnlyInsecureCertificateHelper;
148 mScrypt = scrypt;
Robert Berry4a534ec2017-12-21 15:44:02 +0000149 }
150
151 @Override
152 public void run() {
153 try {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800154 // Only one task is active If user unlocks phone many times in a short time interval.
155 synchronized(KeySyncTask.class) {
156 syncKeys();
157 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000158 } catch (Exception e) {
159 Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
160 }
161 }
162
163 private void syncKeys() {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800164 if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
165 // Application keys for the user will not be available for sync.
166 Log.w(TAG, "Credentials are not set for user " + mUserId);
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800167 int generation = mPlatformKeyManager.getGenerationId(mUserId);
168 mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800169 return;
170 }
Aseem Kumar3326da52018-03-12 18:05:16 -0700171 if (isCustomLockScreen()) {
172 Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId);
173 mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
174 return;
175 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800176
177 List<Integer> recoveryAgents = mRecoverableKeyStoreDb.getRecoveryAgents(mUserId);
178 for (int uid : recoveryAgents) {
179 syncKeysForAgent(uid);
180 }
181 if (recoveryAgents.isEmpty()) {
182 Log.w(TAG, "No recovery agent initialized for user " + mUserId);
183 }
184 }
185
Aseem Kumar3326da52018-03-12 18:05:16 -0700186 private boolean isCustomLockScreen() {
187 return mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE
188 && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PATTERN
189 && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
190 }
191
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800192 private void syncKeysForAgent(int recoveryAgentUid) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800193 boolean recreateCurrentVersion = false;
Robert Berry2fd4b592018-03-15 15:28:05 +0000194 if (!shouldCreateSnapshot(recoveryAgentUid)) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800195 recreateCurrentVersion =
196 (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null)
197 && (mRecoverySnapshotStorage.get(recoveryAgentUid) == null);
198 if (recreateCurrentVersion) {
199 Log.d(TAG, "Recreating most recent snapshot");
200 } else {
201 Log.d(TAG, "Key sync not needed.");
202 return;
203 }
Robert Berryf0a4bea2017-12-22 13:17:32 +0000204 }
205
Bo Zhu14d993d2018-02-03 21:38:48 -0800206 PublicKey publicKey;
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700207 String rootCertAlias =
208 mRecoverableKeyStoreDb.getActiveRootOfTrust(mUserId, recoveryAgentUid);
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700209 rootCertAlias = mTestOnlyInsecureCertificateHelper
210 .getDefaultCertificateAliasIfEmpty(rootCertAlias);
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700211
Bo Zhu14d993d2018-02-03 21:38:48 -0800212 CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId,
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700213 recoveryAgentUid, rootCertAlias);
Bo Zhu14d993d2018-02-03 21:38:48 -0800214 if (certPath != null) {
215 Log.d(TAG, "Using the public key in stored CertPath for syncing");
216 publicKey = certPath.getCertificates().get(0).getPublicKey();
217 } else {
218 Log.d(TAG, "Using the stored raw public key for syncing");
219 publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
220 recoveryAgentUid);
221 }
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000222 if (publicKey == null) {
223 Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task.");
224 return;
225 }
226
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800227 byte[] vaultHandle = mRecoverableKeyStoreDb.getServerParams(mUserId, recoveryAgentUid);
228 if (vaultHandle == null) {
Robert Berry94ea4e42017-12-28 12:08:30 +0000229 Log.w(TAG, "No device ID set for user " + mUserId);
230 return;
231 }
232
Bo Zhu0b8c82e2018-03-30 11:31:53 -0700233 if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(rootCertAlias)) {
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700234 Log.w(TAG, "Insecure root certificate is used by recovery agent "
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700235 + recoveryAgentUid);
Bo Zhu0b8c82e2018-03-30 11:31:53 -0700236 if (mTestOnlyInsecureCertificateHelper.doesCredentialSupportInsecureMode(
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700237 mCredentialType, mCredential)) {
238 Log.w(TAG, "Whitelisted credential is used to generate snapshot by "
239 + "recovery agent "+ recoveryAgentUid);
240 } else {
241 Log.w(TAG, "Non whitelisted credential is used to generate recovery snapshot by "
242 + recoveryAgentUid + " - ignore attempt.");
243 return; // User secret will not be used.
244 }
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700245 }
246
Bo Zhuc3aefbd2018-04-06 09:57:02 -0700247 boolean useScryptToHashCredential = shouldUseScryptToHashCredential();
Robert Berry4a534ec2017-12-21 15:44:02 +0000248 byte[] salt = generateSalt();
Bo Zhu76973432018-04-03 00:37:51 -0700249 byte[] localLskfHash;
250 if (useScryptToHashCredential) {
251 localLskfHash = hashCredentialsByScrypt(salt, mCredential);
252 } else {
253 localLskfHash = hashCredentialsBySaltedSha256(salt, mCredential);
254 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000255
Robert Berryf0a4bea2017-12-22 13:17:32 +0000256 Map<String, SecretKey> rawKeys;
257 try {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800258 rawKeys = getKeysToSync(recoveryAgentUid);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000259 } catch (GeneralSecurityException e) {
260 Log.e(TAG, "Failed to load recoverable keys for sync", e);
261 return;
262 } catch (InsecureUserException e) {
263 Log.wtf(TAG, "A screen unlock triggered the key sync flow, so user must have "
264 + "lock screen. This should be impossible.", e);
265 return;
266 } catch (BadPlatformKeyException e) {
267 Log.wtf(TAG, "Loaded keys for same generation ID as platform key, so "
268 + "BadPlatformKeyException should be impossible.", e);
269 return;
270 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000271
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700272 // Only include insecure key material for test
Bo Zhu0b8c82e2018-03-30 11:31:53 -0700273 if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(rootCertAlias)) {
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700274 rawKeys = mTestOnlyInsecureCertificateHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
275 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000276 SecretKey recoveryKey;
277 try {
278 recoveryKey = generateRecoveryKey();
279 } catch (NoSuchAlgorithmException e) {
280 Log.wtf("AES should never be unavailable", e);
281 return;
282 }
283
Robert Berryf0a4bea2017-12-22 13:17:32 +0000284 Map<String, byte[]> encryptedApplicationKeys;
285 try {
286 encryptedApplicationKeys = KeySyncUtils.encryptKeysWithRecoveryKey(
287 recoveryKey, rawKeys);
288 } catch (InvalidKeyException | NoSuchAlgorithmException e) {
289 Log.wtf(TAG,
290 "Should be impossible: could not encrypt application keys with random key",
291 e);
292 return;
293 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000294
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800295 Long counterId;
296 // counter id is generated exactly once for each credentials value.
297 if (mCredentialUpdated) {
298 counterId = generateAndStoreCounterId(recoveryAgentUid);
299 } else {
300 counterId = mRecoverableKeyStoreDb.getCounterId(mUserId, recoveryAgentUid);
301 if (counterId == null) {
302 counterId = generateAndStoreCounterId(recoveryAgentUid);
303 }
304 }
Dmitry Dementyevae6ec6d2018-01-18 14:29:49 -0800305
Robert Berry94ea4e42017-12-28 12:08:30 +0000306 byte[] vaultParams = KeySyncUtils.packVaultParams(
307 publicKey,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800308 counterId,
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800309 TRUSTED_HARDWARE_MAX_ATTEMPTS,
310 vaultHandle);
Robert Berry4a534ec2017-12-21 15:44:02 +0000311
Robert Berryf0a4bea2017-12-22 13:17:32 +0000312 byte[] encryptedRecoveryKey;
Robert Berry4a534ec2017-12-21 15:44:02 +0000313 try {
Robert Berryf0a4bea2017-12-22 13:17:32 +0000314 encryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey(
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000315 publicKey,
Robert Berry4a534ec2017-12-21 15:44:02 +0000316 localLskfHash,
317 vaultParams,
318 recoveryKey);
319 } catch (NoSuchAlgorithmException e) {
320 Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e);
321 return;
322 } catch (InvalidKeyException e) {
323 Log.e(TAG,"Could not encrypt with recovery key", e);
324 return;
325 }
Bo Zhu76973432018-04-03 00:37:51 -0700326 KeyDerivationParams keyDerivationParams;
327 if (useScryptToHashCredential) {
328 keyDerivationParams = KeyDerivationParams.createScryptParams(
329 salt, /*memoryDifficulty=*/ SCRYPT_PARAM_N);
330 } else {
331 keyDerivationParams = KeyDerivationParams.createSha256Params(salt);
332 }
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800333 KeyChainProtectionParams metadata = new KeyChainProtectionParams.Builder()
334 .setUserSecretType(TYPE_LOCKSCREEN)
335 .setLockScreenUiFormat(getUiFormat(mCredentialType, mCredential))
Bo Zhu76973432018-04-03 00:37:51 -0700336 .setKeyDerivationParams(keyDerivationParams)
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800337 .setSecret(new byte[0])
338 .build();
339
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800340 ArrayList<KeyChainProtectionParams> metadataList = new ArrayList<>();
Robert Berrybd086f12017-12-27 13:29:39 +0000341 metadataList.add(metadata);
342
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800343 // If application keys are not updated, snapshot will not be created on next unlock.
344 mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);
345
Bo Zhu63610802018-03-09 12:32:13 -0800346 KeyChainSnapshot.Builder keyChainSnapshotBuilder = new KeyChainSnapshot.Builder()
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800347 .setSnapshotVersion(getSnapshotVersion(recoveryAgentUid, recreateCurrentVersion))
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800348 .setMaxAttempts(TRUSTED_HARDWARE_MAX_ATTEMPTS)
349 .setCounterId(counterId)
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800350 .setServerParams(vaultHandle)
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800351 .setKeyChainProtectionParams(metadataList)
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800352 .setWrappedApplicationKeys(createApplicationKeyEntries(encryptedApplicationKeys))
Bo Zhu63610802018-03-09 12:32:13 -0800353 .setEncryptedRecoveryKeyBlob(encryptedRecoveryKey);
354 try {
355 keyChainSnapshotBuilder.setTrustedHardwareCertPath(certPath);
356 } catch(CertificateException e) {
357 // Should not happen, as it's just deserialized from bytes stored in the db
358 Log.wtf(TAG, "Cannot serialize CertPath when calling setTrustedHardwareCertPath", e);
359 return;
360 }
361 mRecoverySnapshotStorage.put(recoveryAgentUid, keyChainSnapshotBuilder.build());
Robert Berry91044042017-12-27 12:05:58 +0000362 mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
Robert Berry4a534ec2017-12-21 15:44:02 +0000363 }
364
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800365 @VisibleForTesting
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800366 int getSnapshotVersion(int recoveryAgentUid, boolean recreateCurrentVersion) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800367 Long snapshotVersion = mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid);
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800368 if (recreateCurrentVersion) {
369 // version shouldn't be null at this moment.
370 snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion;
371 } else {
372 snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion + 1;
373 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800374 mRecoverableKeyStoreDb.setSnapshotVersion(mUserId, recoveryAgentUid, snapshotVersion);
375
376 return snapshotVersion.intValue();
377 }
378
379 private long generateAndStoreCounterId(int recoveryAgentUid) {
380 long counter = new SecureRandom().nextLong();
381 mRecoverableKeyStoreDb.setCounterId(mUserId, recoveryAgentUid, counter);
382 return counter;
Robert Berry4a534ec2017-12-21 15:44:02 +0000383 }
384
385 /**
Robert Berryf0a4bea2017-12-22 13:17:32 +0000386 * Returns all of the recoverable keys for the user.
387 */
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800388 private Map<String, SecretKey> getKeysToSync(int recoveryAgentUid)
Robert Berryf0a4bea2017-12-22 13:17:32 +0000389 throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
Robert Berry26cbb6b2018-01-22 21:59:30 +0000390 NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException,
391 InvalidKeyException, InvalidAlgorithmParameterException {
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800392 PlatformDecryptionKey decryptKey = mPlatformKeyManager.getDecryptKey(mUserId);;
Robert Berryf0a4bea2017-12-22 13:17:32 +0000393 Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800394 mUserId, recoveryAgentUid, decryptKey.getGenerationId());
Robert Berryf0a4bea2017-12-22 13:17:32 +0000395 return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
396 }
397
398 /**
399 * Returns {@code true} if a sync is pending.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800400 * @param recoveryAgentUid uid of the recovery agent.
Robert Berryf0a4bea2017-12-22 13:17:32 +0000401 */
Robert Berry2fd4b592018-03-15 15:28:05 +0000402 private boolean shouldCreateSnapshot(int recoveryAgentUid) {
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800403 int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid);
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800404 if (!ArrayUtils.contains(types, KeyChainProtectionParams.TYPE_LOCKSCREEN)) {
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800405 // Only lockscreen type is supported.
406 // We will need to pass extra argument to KeySyncTask to support custom pass phrase.
407 return false;
408 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800409 if (mCredentialUpdated) {
410 // Sync credential if at least one snapshot was created.
411 if (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null) {
412 mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, true);
413 return true;
414 }
415 }
416
417 return mRecoverableKeyStoreDb.getShouldCreateSnapshot(mUserId, recoveryAgentUid);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000418 }
419
420 /**
Robert Berry4a534ec2017-12-21 15:44:02 +0000421 * The UI best suited to entering the given lock screen. This is synced with the vault so the
422 * user can be shown the same UI when recovering the vault on another device.
423 *
424 * @return The format - either pattern, pin, or password.
425 */
426 @VisibleForTesting
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800427 @KeyChainProtectionParams.LockScreenUiFormat static int getUiFormat(
Robert Berry4a534ec2017-12-21 15:44:02 +0000428 int credentialType, String credential) {
429 if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800430 return KeyChainProtectionParams.UI_FORMAT_PATTERN;
Robert Berry4a534ec2017-12-21 15:44:02 +0000431 } else if (isPin(credential)) {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800432 return KeyChainProtectionParams.UI_FORMAT_PIN;
Robert Berry4a534ec2017-12-21 15:44:02 +0000433 } else {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800434 return KeyChainProtectionParams.UI_FORMAT_PASSWORD;
Robert Berry4a534ec2017-12-21 15:44:02 +0000435 }
436 }
437
438 /**
439 * Generates a salt to include with the lock screen hash.
440 *
441 * @return The salt.
442 */
443 private byte[] generateSalt() {
444 byte[] salt = new byte[SALT_LENGTH_BYTES];
445 new SecureRandom().nextBytes(salt);
446 return salt;
447 }
448
449 /**
450 * Returns {@code true} if {@code credential} looks like a pin.
451 */
452 @VisibleForTesting
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800453 static boolean isPin(@Nullable String credential) {
454 if (credential == null) {
455 return false;
456 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000457 int length = credential.length();
458 for (int i = 0; i < length; i++) {
459 if (!Character.isDigit(credential.charAt(i))) {
460 return false;
461 }
462 }
463 return true;
464 }
465
466 /**
467 * Hashes {@code credentials} with the given {@code salt}.
468 *
469 * @return The SHA-256 hash.
470 */
471 @VisibleForTesting
Bo Zhu76973432018-04-03 00:37:51 -0700472 static byte[] hashCredentialsBySaltedSha256(byte[] salt, String credentials) {
Robert Berry4a534ec2017-12-21 15:44:02 +0000473 byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
474 ByteBuffer byteBuffer = ByteBuffer.allocate(
475 salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2);
476 byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
477 byteBuffer.putInt(salt.length);
478 byteBuffer.put(salt);
479 byteBuffer.putInt(credentialsBytes.length);
480 byteBuffer.put(credentialsBytes);
481 byte[] bytes = byteBuffer.array();
482
483 try {
484 return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
485 } catch (NoSuchAlgorithmException e) {
486 // Impossible, SHA-256 must be supported on Android.
487 throw new RuntimeException(e);
488 }
489 }
490
Bo Zhu76973432018-04-03 00:37:51 -0700491 private byte[] hashCredentialsByScrypt(byte[] salt, String credentials) {
492 return mScrypt.scrypt(
493 credentials.getBytes(StandardCharsets.UTF_8), salt,
494 SCRYPT_PARAM_N, SCRYPT_PARAM_R, SCRYPT_PARAM_P, SCRYPT_PARAM_OUTLEN_BYTES);
495 }
496
Robert Berry4a534ec2017-12-21 15:44:02 +0000497 private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
498 KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
499 keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
500 return keyGenerator.generateKey();
501 }
Robert Berryf0a4bea2017-12-22 13:17:32 +0000502
Robert Berry5f138702018-01-17 15:18:05 +0000503 private static List<WrappedApplicationKey> createApplicationKeyEntries(
Robert Berrybd086f12017-12-27 13:29:39 +0000504 Map<String, byte[]> encryptedApplicationKeys) {
Robert Berry5f138702018-01-17 15:18:05 +0000505 ArrayList<WrappedApplicationKey> keyEntries = new ArrayList<>();
Robert Berrybd086f12017-12-27 13:29:39 +0000506 for (String alias : encryptedApplicationKeys.keySet()) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800507 keyEntries.add(new WrappedApplicationKey.Builder()
508 .setAlias(alias)
509 .setEncryptedKeyMaterial(encryptedApplicationKeys.get(alias))
510 .build());
Robert Berrybd086f12017-12-27 13:29:39 +0000511 }
512 return keyEntries;
Robert Berryf0a4bea2017-12-22 13:17:32 +0000513 }
Bo Zhu76973432018-04-03 00:37:51 -0700514
Bo Zhuc3aefbd2018-04-06 09:57:02 -0700515 private boolean shouldUseScryptToHashCredential() {
516 return mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
Bo Zhu76973432018-04-03 00:37:51 -0700517 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000518}