blob: 050c1f4a6141c30db5e1f65b1318f772d4bfd5d5 [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;
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -070022import android.annotation.NonNull;
Robert Berry4a534ec2017-12-21 15:44:02 +000023import android.content.Context;
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;
27import android.security.keystore.recovery.TrustedRootCertificates;
Robert Berry81ee34b2018-01-23 11:59:59 +000028import android.security.keystore.recovery.WrappedApplicationKey;
Robert Berry4a534ec2017-12-21 15:44:02 +000029import android.util.Log;
30
31import com.android.internal.annotations.VisibleForTesting;
Dmitry Dementyev122bfe12018-01-10 18:56:36 -080032import com.android.internal.util.ArrayUtils;
Robert Berry4a534ec2017-12-21 15:44:02 +000033import com.android.internal.widget.LockPatternUtils;
34import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
Robert Berrybd086f12017-12-27 13:29:39 +000035import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000036
37import java.nio.ByteBuffer;
38import java.nio.ByteOrder;
39import java.nio.charset.StandardCharsets;
Robert Berryf0a4bea2017-12-22 13:17:32 +000040import java.security.GeneralSecurityException;
Robert Berry26cbb6b2018-01-22 21:59:30 +000041import java.security.InvalidAlgorithmParameterException;
Robert Berry4a534ec2017-12-21 15:44:02 +000042import java.security.InvalidKeyException;
43import java.security.KeyStoreException;
44import java.security.MessageDigest;
45import java.security.NoSuchAlgorithmException;
46import java.security.PublicKey;
47import java.security.SecureRandom;
Robert Berryf0a4bea2017-12-22 13:17:32 +000048import java.security.UnrecoverableKeyException;
Bo Zhu14d993d2018-02-03 21:38:48 -080049import java.security.cert.CertPath;
Bo Zhu63610802018-03-09 12:32:13 -080050import java.security.cert.CertificateException;
Robert Berrybd086f12017-12-27 13:29:39 +000051import java.util.ArrayList;
52import java.util.List;
Robert Berryf0a4bea2017-12-22 13:17:32 +000053import java.util.Map;
Robert Berry4a534ec2017-12-21 15:44:02 +000054
55import javax.crypto.KeyGenerator;
Robert Berryf0a4bea2017-12-22 13:17:32 +000056import javax.crypto.NoSuchPaddingException;
Robert Berry4a534ec2017-12-21 15:44:02 +000057import javax.crypto.SecretKey;
58
59/**
60 * Task to sync application keys to a remote vault service.
61 *
Robert Berryf0a4bea2017-12-22 13:17:32 +000062 * @hide
Robert Berry4a534ec2017-12-21 15:44:02 +000063 */
64public 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";
Robert Berry94ea4e42017-12-28 12:08:30 +000072 private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10;
Robert Berry4a534ec2017-12-21 15:44:02 +000073
Robert Berry4a534ec2017-12-21 15:44:02 +000074 private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
75 private final int mUserId;
76 private final int mCredentialType;
77 private final String mCredential;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080078 private final boolean mCredentialUpdated;
Dmitry Dementyev6e167242018-01-25 15:29:50 -080079 private final PlatformKeyManager mPlatformKeyManager;
Robert Berrybd086f12017-12-27 13:29:39 +000080 private final RecoverySnapshotStorage mRecoverySnapshotStorage;
Robert Berry91044042017-12-27 12:05:58 +000081 private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000082
83 public static KeySyncTask newInstance(
84 Context context,
85 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +000086 RecoverySnapshotStorage snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +000087 RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +000088 int userId,
89 int credentialType,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080090 String credential,
91 boolean credentialUpdated
Robert Berry4a534ec2017-12-21 15:44:02 +000092 ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
93 return new KeySyncTask(
Robert Berry4a534ec2017-12-21 15:44:02 +000094 recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +000095 snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +000096 recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +000097 userId,
98 credentialType,
Robert Berryf0a4bea2017-12-22 13:17:32 +000099 credential,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800100 credentialUpdated,
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800101 PlatformKeyManager.getInstance(context, recoverableKeyStoreDb));
Robert Berry4a534ec2017-12-21 15:44:02 +0000102 }
103
104 /**
105 * A new task.
106 *
107 * @param recoverableKeyStoreDb Database where the keys are stored.
108 * @param userId The uid of the user whose profile has been unlocked.
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800109 * @param credentialType The type of credential as defined in {@code LockPatternUtils}
Robert Berry4a534ec2017-12-21 15:44:02 +0000110 * @param credential The credential, encoded as a {@link String}.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800111 * @param credentialUpdated signals weather credentials were updated.
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800112 * @param platformKeyManager platform key manager
Robert Berry4a534ec2017-12-21 15:44:02 +0000113 */
114 @VisibleForTesting
115 KeySyncTask(
Robert Berry4a534ec2017-12-21 15:44:02 +0000116 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +0000117 RecoverySnapshotStorage snapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +0000118 RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
Robert Berry4a534ec2017-12-21 15:44:02 +0000119 int userId,
120 int credentialType,
Robert Berryf0a4bea2017-12-22 13:17:32 +0000121 String credential,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800122 boolean credentialUpdated,
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800123 PlatformKeyManager platformKeyManager) {
Robert Berry91044042017-12-27 12:05:58 +0000124 mSnapshotListenersStorage = recoverySnapshotListenersStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000125 mRecoverableKeyStoreDb = recoverableKeyStoreDb;
126 mUserId = userId;
127 mCredentialType = credentialType;
128 mCredential = credential;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800129 mCredentialUpdated = credentialUpdated;
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800130 mPlatformKeyManager = platformKeyManager;
Robert Berrybd086f12017-12-27 13:29:39 +0000131 mRecoverySnapshotStorage = snapshotStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000132 }
133
134 @Override
135 public void run() {
136 try {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800137 // Only one task is active If user unlocks phone many times in a short time interval.
138 synchronized(KeySyncTask.class) {
139 syncKeys();
140 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000141 } catch (Exception e) {
142 Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
143 }
144 }
145
146 private void syncKeys() {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800147 if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
148 // Application keys for the user will not be available for sync.
149 Log.w(TAG, "Credentials are not set for user " + mUserId);
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800150 int generation = mPlatformKeyManager.getGenerationId(mUserId);
151 mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800152 return;
153 }
Aseem Kumar3326da52018-03-12 18:05:16 -0700154 if (isCustomLockScreen()) {
155 Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId);
156 mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
157 return;
158 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800159
160 List<Integer> recoveryAgents = mRecoverableKeyStoreDb.getRecoveryAgents(mUserId);
161 for (int uid : recoveryAgents) {
162 syncKeysForAgent(uid);
163 }
164 if (recoveryAgents.isEmpty()) {
165 Log.w(TAG, "No recovery agent initialized for user " + mUserId);
166 }
167 }
168
Aseem Kumar3326da52018-03-12 18:05:16 -0700169 private boolean isCustomLockScreen() {
170 return mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE
171 && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PATTERN
172 && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
173 }
174
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800175 private void syncKeysForAgent(int recoveryAgentUid) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800176 boolean recreateCurrentVersion = false;
Robert Berry2fd4b592018-03-15 15:28:05 +0000177 if (!shouldCreateSnapshot(recoveryAgentUid)) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800178 recreateCurrentVersion =
179 (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null)
180 && (mRecoverySnapshotStorage.get(recoveryAgentUid) == null);
181 if (recreateCurrentVersion) {
182 Log.d(TAG, "Recreating most recent snapshot");
183 } else {
184 Log.d(TAG, "Key sync not needed.");
185 return;
186 }
Robert Berryf0a4bea2017-12-22 13:17:32 +0000187 }
188
Bo Zhu14d993d2018-02-03 21:38:48 -0800189 PublicKey publicKey;
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700190 String rootCertAlias =
191 mRecoverableKeyStoreDb.getActiveRootOfTrust(mUserId, recoveryAgentUid);
192
193 rootCertAlias = replaceEmptyValueWithSecureDefault(rootCertAlias);
Bo Zhu14d993d2018-02-03 21:38:48 -0800194 CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId,
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700195 recoveryAgentUid, rootCertAlias);
Bo Zhu14d993d2018-02-03 21:38:48 -0800196 if (certPath != null) {
197 Log.d(TAG, "Using the public key in stored CertPath for syncing");
198 publicKey = certPath.getCertificates().get(0).getPublicKey();
199 } else {
200 Log.d(TAG, "Using the stored raw public key for syncing");
201 publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
202 recoveryAgentUid);
203 }
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000204 if (publicKey == null) {
205 Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task.");
206 return;
207 }
208
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800209 byte[] vaultHandle = mRecoverableKeyStoreDb.getServerParams(mUserId, recoveryAgentUid);
210 if (vaultHandle == null) {
Robert Berry94ea4e42017-12-28 12:08:30 +0000211 Log.w(TAG, "No device ID set for user " + mUserId);
212 return;
213 }
214
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700215 // The only place in this class which uses credential value
216 if (!TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS.equals(
217 rootCertAlias)) {
218 // TODO: allow only whitelisted LSKF usage
219 Log.w(TAG, "Untrusted root certificate is used by recovery agent "
220 + recoveryAgentUid);
221 }
222
Robert Berry4a534ec2017-12-21 15:44:02 +0000223 byte[] salt = generateSalt();
224 byte[] localLskfHash = hashCredentials(salt, mCredential);
225
Robert Berryf0a4bea2017-12-22 13:17:32 +0000226 Map<String, SecretKey> rawKeys;
227 try {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800228 rawKeys = getKeysToSync(recoveryAgentUid);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000229 } catch (GeneralSecurityException e) {
230 Log.e(TAG, "Failed to load recoverable keys for sync", e);
231 return;
232 } catch (InsecureUserException e) {
233 Log.wtf(TAG, "A screen unlock triggered the key sync flow, so user must have "
234 + "lock screen. This should be impossible.", e);
235 return;
236 } catch (BadPlatformKeyException e) {
237 Log.wtf(TAG, "Loaded keys for same generation ID as platform key, so "
238 + "BadPlatformKeyException should be impossible.", e);
239 return;
240 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000241
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700242 // TODO: filter raw keys based on the root of trust.
243 // It is the only place in the class where raw key material is used.
Robert Berry4a534ec2017-12-21 15:44:02 +0000244 SecretKey recoveryKey;
245 try {
246 recoveryKey = generateRecoveryKey();
247 } catch (NoSuchAlgorithmException e) {
248 Log.wtf("AES should never be unavailable", e);
249 return;
250 }
251
Robert Berryf0a4bea2017-12-22 13:17:32 +0000252 Map<String, byte[]> encryptedApplicationKeys;
253 try {
254 encryptedApplicationKeys = KeySyncUtils.encryptKeysWithRecoveryKey(
255 recoveryKey, rawKeys);
256 } catch (InvalidKeyException | NoSuchAlgorithmException e) {
257 Log.wtf(TAG,
258 "Should be impossible: could not encrypt application keys with random key",
259 e);
260 return;
261 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000262
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800263 Long counterId;
264 // counter id is generated exactly once for each credentials value.
265 if (mCredentialUpdated) {
266 counterId = generateAndStoreCounterId(recoveryAgentUid);
267 } else {
268 counterId = mRecoverableKeyStoreDb.getCounterId(mUserId, recoveryAgentUid);
269 if (counterId == null) {
270 counterId = generateAndStoreCounterId(recoveryAgentUid);
271 }
272 }
Dmitry Dementyevae6ec6d2018-01-18 14:29:49 -0800273
Robert Berry94ea4e42017-12-28 12:08:30 +0000274 byte[] vaultParams = KeySyncUtils.packVaultParams(
275 publicKey,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800276 counterId,
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800277 TRUSTED_HARDWARE_MAX_ATTEMPTS,
278 vaultHandle);
Robert Berry4a534ec2017-12-21 15:44:02 +0000279
Robert Berryf0a4bea2017-12-22 13:17:32 +0000280 byte[] encryptedRecoveryKey;
Robert Berry4a534ec2017-12-21 15:44:02 +0000281 try {
Robert Berryf0a4bea2017-12-22 13:17:32 +0000282 encryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey(
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000283 publicKey,
Robert Berry4a534ec2017-12-21 15:44:02 +0000284 localLskfHash,
285 vaultParams,
286 recoveryKey);
287 } catch (NoSuchAlgorithmException e) {
288 Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e);
289 return;
290 } catch (InvalidKeyException e) {
291 Log.e(TAG,"Could not encrypt with recovery key", e);
292 return;
293 }
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800294 KeyChainProtectionParams metadata = new KeyChainProtectionParams.Builder()
295 .setUserSecretType(TYPE_LOCKSCREEN)
296 .setLockScreenUiFormat(getUiFormat(mCredentialType, mCredential))
297 .setKeyDerivationParams(KeyDerivationParams.createSha256Params(salt))
298 .setSecret(new byte[0])
299 .build();
300
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800301 ArrayList<KeyChainProtectionParams> metadataList = new ArrayList<>();
Robert Berrybd086f12017-12-27 13:29:39 +0000302 metadataList.add(metadata);
303
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800304 // If application keys are not updated, snapshot will not be created on next unlock.
305 mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);
306
Bo Zhu63610802018-03-09 12:32:13 -0800307 KeyChainSnapshot.Builder keyChainSnapshotBuilder = new KeyChainSnapshot.Builder()
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800308 .setSnapshotVersion(getSnapshotVersion(recoveryAgentUid, recreateCurrentVersion))
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800309 .setMaxAttempts(TRUSTED_HARDWARE_MAX_ATTEMPTS)
310 .setCounterId(counterId)
311 .setTrustedHardwarePublicKey(SecureBox.encodePublicKey(publicKey))
312 .setServerParams(vaultHandle)
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800313 .setKeyChainProtectionParams(metadataList)
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800314 .setWrappedApplicationKeys(createApplicationKeyEntries(encryptedApplicationKeys))
Bo Zhu63610802018-03-09 12:32:13 -0800315 .setEncryptedRecoveryKeyBlob(encryptedRecoveryKey);
316 try {
317 keyChainSnapshotBuilder.setTrustedHardwareCertPath(certPath);
318 } catch(CertificateException e) {
319 // Should not happen, as it's just deserialized from bytes stored in the db
320 Log.wtf(TAG, "Cannot serialize CertPath when calling setTrustedHardwareCertPath", e);
321 return;
322 }
323 mRecoverySnapshotStorage.put(recoveryAgentUid, keyChainSnapshotBuilder.build());
Robert Berry91044042017-12-27 12:05:58 +0000324 mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
Robert Berry4a534ec2017-12-21 15:44:02 +0000325 }
326
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800327 @VisibleForTesting
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800328 int getSnapshotVersion(int recoveryAgentUid, boolean recreateCurrentVersion) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800329 Long snapshotVersion = mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid);
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800330 if (recreateCurrentVersion) {
331 // version shouldn't be null at this moment.
332 snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion;
333 } else {
334 snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion + 1;
335 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800336 mRecoverableKeyStoreDb.setSnapshotVersion(mUserId, recoveryAgentUid, snapshotVersion);
337
338 return snapshotVersion.intValue();
339 }
340
341 private long generateAndStoreCounterId(int recoveryAgentUid) {
342 long counter = new SecureRandom().nextLong();
343 mRecoverableKeyStoreDb.setCounterId(mUserId, recoveryAgentUid, counter);
344 return counter;
Robert Berry4a534ec2017-12-21 15:44:02 +0000345 }
346
347 /**
Robert Berryf0a4bea2017-12-22 13:17:32 +0000348 * Returns all of the recoverable keys for the user.
349 */
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800350 private Map<String, SecretKey> getKeysToSync(int recoveryAgentUid)
Robert Berryf0a4bea2017-12-22 13:17:32 +0000351 throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
Robert Berry26cbb6b2018-01-22 21:59:30 +0000352 NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException,
353 InvalidKeyException, InvalidAlgorithmParameterException {
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800354 PlatformDecryptionKey decryptKey = mPlatformKeyManager.getDecryptKey(mUserId);;
Robert Berryf0a4bea2017-12-22 13:17:32 +0000355 Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800356 mUserId, recoveryAgentUid, decryptKey.getGenerationId());
Robert Berryf0a4bea2017-12-22 13:17:32 +0000357 return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
358 }
359
360 /**
361 * Returns {@code true} if a sync is pending.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800362 * @param recoveryAgentUid uid of the recovery agent.
Robert Berryf0a4bea2017-12-22 13:17:32 +0000363 */
Robert Berry2fd4b592018-03-15 15:28:05 +0000364 private boolean shouldCreateSnapshot(int recoveryAgentUid) {
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800365 int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid);
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800366 if (!ArrayUtils.contains(types, KeyChainProtectionParams.TYPE_LOCKSCREEN)) {
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800367 // Only lockscreen type is supported.
368 // We will need to pass extra argument to KeySyncTask to support custom pass phrase.
369 return false;
370 }
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800371 if (mCredentialUpdated) {
372 // Sync credential if at least one snapshot was created.
373 if (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null) {
374 mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, true);
375 return true;
376 }
377 }
378
379 return mRecoverableKeyStoreDb.getShouldCreateSnapshot(mUserId, recoveryAgentUid);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000380 }
381
382 /**
Robert Berry4a534ec2017-12-21 15:44:02 +0000383 * The UI best suited to entering the given lock screen. This is synced with the vault so the
384 * user can be shown the same UI when recovering the vault on another device.
385 *
386 * @return The format - either pattern, pin, or password.
387 */
388 @VisibleForTesting
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800389 @KeyChainProtectionParams.LockScreenUiFormat static int getUiFormat(
Robert Berry4a534ec2017-12-21 15:44:02 +0000390 int credentialType, String credential) {
391 if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800392 return KeyChainProtectionParams.UI_FORMAT_PATTERN;
Robert Berry4a534ec2017-12-21 15:44:02 +0000393 } else if (isPin(credential)) {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800394 return KeyChainProtectionParams.UI_FORMAT_PIN;
Robert Berry4a534ec2017-12-21 15:44:02 +0000395 } else {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800396 return KeyChainProtectionParams.UI_FORMAT_PASSWORD;
Robert Berry4a534ec2017-12-21 15:44:02 +0000397 }
398 }
399
400 /**
401 * Generates a salt to include with the lock screen hash.
402 *
403 * @return The salt.
404 */
405 private byte[] generateSalt() {
406 byte[] salt = new byte[SALT_LENGTH_BYTES];
407 new SecureRandom().nextBytes(salt);
408 return salt;
409 }
410
411 /**
412 * Returns {@code true} if {@code credential} looks like a pin.
413 */
414 @VisibleForTesting
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800415 static boolean isPin(@Nullable String credential) {
416 if (credential == null) {
417 return false;
418 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000419 int length = credential.length();
420 for (int i = 0; i < length; i++) {
421 if (!Character.isDigit(credential.charAt(i))) {
422 return false;
423 }
424 }
425 return true;
426 }
427
428 /**
429 * Hashes {@code credentials} with the given {@code salt}.
430 *
431 * @return The SHA-256 hash.
432 */
433 @VisibleForTesting
434 static byte[] hashCredentials(byte[] salt, String credentials) {
435 byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
436 ByteBuffer byteBuffer = ByteBuffer.allocate(
437 salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2);
438 byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
439 byteBuffer.putInt(salt.length);
440 byteBuffer.put(salt);
441 byteBuffer.putInt(credentialsBytes.length);
442 byteBuffer.put(credentialsBytes);
443 byte[] bytes = byteBuffer.array();
444
445 try {
446 return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
447 } catch (NoSuchAlgorithmException e) {
448 // Impossible, SHA-256 must be supported on Android.
449 throw new RuntimeException(e);
450 }
451 }
452
453 private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
454 KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
455 keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
456 return keyGenerator.generateKey();
457 }
Robert Berryf0a4bea2017-12-22 13:17:32 +0000458
Robert Berry5f138702018-01-17 15:18:05 +0000459 private static List<WrappedApplicationKey> createApplicationKeyEntries(
Robert Berrybd086f12017-12-27 13:29:39 +0000460 Map<String, byte[]> encryptedApplicationKeys) {
Robert Berry5f138702018-01-17 15:18:05 +0000461 ArrayList<WrappedApplicationKey> keyEntries = new ArrayList<>();
Robert Berrybd086f12017-12-27 13:29:39 +0000462 for (String alias : encryptedApplicationKeys.keySet()) {
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800463 keyEntries.add(new WrappedApplicationKey.Builder()
464 .setAlias(alias)
465 .setEncryptedKeyMaterial(encryptedApplicationKeys.get(alias))
466 .build());
Robert Berrybd086f12017-12-27 13:29:39 +0000467 }
468 return keyEntries;
Robert Berryf0a4bea2017-12-22 13:17:32 +0000469 }
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700470
471 private @NonNull String replaceEmptyValueWithSecureDefault(
472 @Nullable String rootCertificateAlias) {
473 if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
474 Log.e(TAG, "rootCertificateAlias is null or empty");
475 // Use the default Google Key Vault Service CA certificate if the alias is not provided
476 rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
477 }
478 return rootCertificateAlias;
479 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000480}