2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.locksettings;
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.Context;
23 import android.content.pm.UserInfo;
24 import android.hardware.weaver.V1_0.IWeaver;
25 import android.hardware.weaver.V1_0.WeaverConfig;
26 import android.hardware.weaver.V1_0.WeaverReadResponse;
27 import android.hardware.weaver.V1_0.WeaverReadStatus;
28 import android.hardware.weaver.V1_0.WeaverStatus;
29 import android.security.GateKeeper;
30 import android.os.RemoteException;
31 import android.os.UserManager;
32 import android.service.gatekeeper.GateKeeperResponse;
33 import android.service.gatekeeper.IGateKeeperService;
34 import android.util.ArrayMap;
35 import android.util.Log;
36 import android.util.Slog;
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.util.ArrayUtils;
40 import com.android.internal.widget.ICheckCredentialProgressCallback;
41 import com.android.internal.widget.LockPatternUtils;
42 import com.android.internal.widget.VerifyCredentialResponse;
43 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
45 import libcore.util.HexEncoding;
47 import java.nio.ByteBuffer;
48 import java.security.NoSuchAlgorithmException;
49 import java.security.SecureRandom;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collections;
53 import java.util.HashSet;
54 import java.util.List;
56 import java.util.NoSuchElementException;
61 * A class that maintains the wrapping of synthetic password by user credentials or escrow tokens.
62 * It's (mostly) a pure storage for synthetic passwords, providing APIs to creating and destroying
63 * synthetic password blobs which are wrapped by user credentials or escrow tokens.
65 * Here is the assumptions it makes:
66 * Each user has one single synthetic password at any time.
67 * The SP has an associated password handle, which binds to the SID for that user. The password
68 * handle is persisted by SyntheticPasswordManager internally.
69 * If the user credential is null, it's treated as if the credential is DEFAULT_PASSWORD
71 * Information persisted on disk:
72 * for each user (stored under DEFAULT_HANDLE):
73 * SP_HANDLE_NAME: GateKeeper password handle of synthetic password. Only available if user
74 * credential exists, cleared when user clears their credential.
75 * SP_E0_NAME, SP_P1_NAME: Secret to derive synthetic password when combined with escrow
76 * tokens. Destroyed when escrow support is turned off for the given user.
78 * for each SP blob under the user (stored under the corresponding handle):
79 * SP_BLOB_NAME: The encrypted synthetic password. Always exists.
80 * PASSWORD_DATA_NAME: Metadata about user credential. Only exists for password based SP.
81 * SECDISCARDABLE_NAME: Part of the necessary ingredient to decrypt SP_BLOB_NAME for the
82 * purpose of secure deletion. Exists if this is a non-weaver SP
83 * (both password and token based), or it's a token-based SP under weaver.
84 * WEAVER_SLOT: Metadata about the weaver slot used. Only exists if this is a SP under weaver.
88 public class SyntheticPasswordManager {
89 private static final String SP_BLOB_NAME = "spblob";
90 private static final String SP_E0_NAME = "e0";
91 private static final String SP_P1_NAME = "p1";
92 private static final String SP_HANDLE_NAME = "handle";
93 private static final String SECDISCARDABLE_NAME = "secdis";
94 private static final int SECDISCARDABLE_LENGTH = 16 * 1024;
95 private static final String PASSWORD_DATA_NAME = "pwd";
96 private static final String WEAVER_SLOT_NAME = "weaver";
98 public static final long DEFAULT_HANDLE = 0L;
99 private static final String DEFAULT_PASSWORD = "default-password";
101 private static final byte WEAVER_VERSION = 1;
102 private static final int INVALID_WEAVER_SLOT = -1;
104 private static final byte SYNTHETIC_PASSWORD_VERSION_V1 = 1;
105 private static final byte SYNTHETIC_PASSWORD_VERSION = 2;
106 private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
107 private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
109 // 256-bit synthetic password
110 private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
112 private static final int PASSWORD_SCRYPT_N = 11;
113 private static final int PASSWORD_SCRYPT_R = 3;
114 private static final int PASSWORD_SCRYPT_P = 1;
115 private static final int PASSWORD_SALT_LENGTH = 16;
116 private static final int PASSWORD_TOKEN_LENGTH = 32;
117 private static final String TAG = "SyntheticPasswordManager";
119 private static final byte[] PERSONALISATION_SECDISCARDABLE = "secdiscardable-transform".getBytes();
120 private static final byte[] PERSONALIZATION_KEY_STORE_PASSWORD = "keystore-password".getBytes();
121 private static final byte[] PERSONALIZATION_USER_GK_AUTH = "user-gk-authentication".getBytes();
122 private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
123 private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
124 private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
125 private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
126 private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
127 private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes();
128 private static final byte[] PERSONALISATION_WEAVER_TOKEN = "weaver-token".getBytes();
130 static class AuthenticationResult {
131 public AuthenticationToken authToken;
132 public VerifyCredentialResponse gkResponse;
133 public int credentialType;
136 static class AuthenticationToken {
138 * Here is the relationship between all three fields:
139 * P0 and P1 are two randomly-generated blocks. P1 is stored on disk but P0 is not.
140 * syntheticPassword = hash(P0 || P1)
141 * E0 = P0 encrypted under syntheticPassword, stored on disk.
143 private @Nullable byte[] E0;
144 private @Nullable byte[] P1;
145 private @NonNull String syntheticPassword;
147 public String deriveKeyStorePassword() {
148 return bytesToHex(SyntheticPasswordCrypto.personalisedHash(
149 PERSONALIZATION_KEY_STORE_PASSWORD, syntheticPassword.getBytes()));
152 public byte[] deriveGkPassword() {
153 return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_SP_GK_AUTH,
154 syntheticPassword.getBytes());
157 public byte[] deriveDiskEncryptionKey() {
158 return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_FBE_KEY,
159 syntheticPassword.getBytes());
162 private void initialize(byte[] P0, byte[] P1) {
164 this.syntheticPassword = String.valueOf(HexEncoding.encode(
165 SyntheticPasswordCrypto.personalisedHash(
166 PERSONALIZATION_SP_SPLIT, P0, P1)));
167 this.E0 = SyntheticPasswordCrypto.encrypt(this.syntheticPassword.getBytes(),
168 PERSONALIZATION_E0, P0);
171 public void recreate(byte[] secret) {
172 initialize(secret, this.P1);
175 protected static AuthenticationToken create() {
176 AuthenticationToken result = new AuthenticationToken();
177 result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH),
178 secureRandom(SYNTHETIC_PASSWORD_LENGTH));
182 public byte[] computeP0() {
186 return SyntheticPasswordCrypto.decrypt(syntheticPassword.getBytes(), PERSONALIZATION_E0,
191 static class PasswordData {
195 public int passwordType;
197 // For GateKeeper-based credential, this is the password handle returned by GK,
198 // for weaver-based credential, this is empty.
199 public byte[] passwordHandle;
201 public static PasswordData create(int passwordType) {
202 PasswordData result = new PasswordData();
203 result.scryptN = PASSWORD_SCRYPT_N;
204 result.scryptR = PASSWORD_SCRYPT_R;
205 result.scryptP = PASSWORD_SCRYPT_P;
206 result.passwordType = passwordType;
207 result.salt = secureRandom(PASSWORD_SALT_LENGTH);
211 public static PasswordData fromBytes(byte[] data) {
212 PasswordData result = new PasswordData();
213 ByteBuffer buffer = ByteBuffer.allocate(data.length);
214 buffer.put(data, 0, data.length);
216 result.passwordType = buffer.getInt();
217 result.scryptN = buffer.get();
218 result.scryptR = buffer.get();
219 result.scryptP = buffer.get();
220 int saltLen = buffer.getInt();
221 result.salt = new byte[saltLen];
222 buffer.get(result.salt);
223 int handleLen = buffer.getInt();
225 result.passwordHandle = new byte[handleLen];
226 buffer.get(result.passwordHandle);
228 result.passwordHandle = null;
233 public byte[] toBytes() {
235 ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES
236 + Integer.BYTES + salt.length + Integer.BYTES +
237 (passwordHandle != null ? passwordHandle.length : 0));
238 buffer.putInt(passwordType);
242 buffer.putInt(salt.length);
244 if (passwordHandle != null && passwordHandle.length > 0) {
245 buffer.putInt(passwordHandle.length);
246 buffer.put(passwordHandle);
250 return buffer.array();
254 static class TokenData {
255 byte[] secdiscardableOnDisk;
257 byte[] aggregatedSecret;
260 private final Context mContext;
261 private LockSettingsStorage mStorage;
262 private IWeaver mWeaver;
263 private WeaverConfig mWeaverConfig;
265 private final UserManager mUserManager;
267 public SyntheticPasswordManager(Context context, LockSettingsStorage storage,
268 UserManager userManager) {
271 mUserManager = userManager;
275 protected IWeaver getWeaverService() throws RemoteException {
277 return IWeaver.getService();
278 } catch (NoSuchElementException e) {
279 Slog.i(TAG, "Device does not support weaver");
284 public synchronized void initWeaverService() {
285 if (mWeaver != null) {
289 mWeaverConfig = null;
290 mWeaver = getWeaverService();
291 if (mWeaver != null) {
292 mWeaver.getConfig((int status, WeaverConfig config) -> {
293 if (status == WeaverStatus.OK && config.slots > 0) {
294 mWeaverConfig = config;
296 Slog.e(TAG, "Failed to get weaver config, status " + status
297 + " slots: " + config.slots);
302 } catch (RemoteException e) {
303 Slog.e(TAG, "Failed to get weaver service", e);
307 private synchronized boolean isWeaverAvailable() {
308 if (mWeaver == null) {
309 //Re-initializing weaver in case there was a transient error preventing access to it.
312 return mWeaver != null && mWeaverConfig.slots > 0;
316 * Enroll the given key value pair into the specified weaver slot. if the given key is null,
317 * a default all-zero key is used. If the value is not specified, a fresh random secret is
318 * generated as the value.
320 * @return the value stored in the weaver slot
321 * @throws RemoteException
323 private byte[] weaverEnroll(int slot, byte[] key, @Nullable byte[] value)
324 throws RemoteException {
325 if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) {
326 throw new RuntimeException("Invalid slot for weaver");
329 key = new byte[mWeaverConfig.keySize];
330 } else if (key.length != mWeaverConfig.keySize) {
331 throw new RuntimeException("Invalid key size for weaver");
334 value = secureRandom(mWeaverConfig.valueSize);
336 int writeStatus = mWeaver.write(slot, toByteArrayList(key), toByteArrayList(value));
337 if (writeStatus != WeaverStatus.OK) {
338 Log.e(TAG, "weaver write failed, slot: " + slot + " status: " + writeStatus);
345 * Verify the supplied key against a weaver slot, returning a response indicating whether
346 * the verification is successful, throttled or failed. If successful, the bound secret
348 * @throws RemoteException
350 private VerifyCredentialResponse weaverVerify(int slot, byte[] key) throws RemoteException {
351 if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) {
352 throw new RuntimeException("Invalid slot for weaver");
355 key = new byte[mWeaverConfig.keySize];
356 } else if (key.length != mWeaverConfig.keySize) {
357 throw new RuntimeException("Invalid key size for weaver");
359 final VerifyCredentialResponse[] response = new VerifyCredentialResponse[1];
360 mWeaver.read(slot, toByteArrayList(key), (int status, WeaverReadResponse readResponse) -> {
362 case WeaverReadStatus.OK:
363 response[0] = new VerifyCredentialResponse(
364 fromByteArrayList(readResponse.value));
366 case WeaverReadStatus.THROTTLE:
367 response[0] = new VerifyCredentialResponse(readResponse.timeout);
368 Log.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
370 case WeaverReadStatus.INCORRECT_KEY:
371 if (readResponse.timeout == 0) {
372 response[0] = VerifyCredentialResponse.ERROR;
373 Log.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
375 response[0] = new VerifyCredentialResponse(readResponse.timeout);
376 Log.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: " + slot);
379 case WeaverReadStatus.FAILED:
380 response[0] = VerifyCredentialResponse.ERROR;
381 Log.e(TAG, "weaver read failed (FAILED), slot: " + slot);
384 response[0] = VerifyCredentialResponse.ERROR;
385 Log.e(TAG, "weaver read unknown status " + status + ", slot: " + slot);
392 public void removeUser(int userId) {
393 if (isWeaverAvailable()) {
394 for (long handle : mStorage.listSyntheticPasswordHandlesForUser(WEAVER_SLOT_NAME,
396 destroyWeaverSlot(handle, userId);
401 public int getCredentialType(long handle, int userId) {
402 byte[] passwordData = loadState(PASSWORD_DATA_NAME, handle, userId);
403 if (passwordData == null) {
404 Log.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
405 return LockPatternUtils.CREDENTIAL_TYPE_NONE;
407 return PasswordData.fromBytes(passwordData).passwordType;
411 * Initializing a new Authentication token, possibly from an existing credential and hash.
413 * The authentication token would bear a randomly-generated synthetic password.
415 * This method has the side effect of rebinding the SID of the given user to the
416 * newly-generated SP.
418 * If the existing credential hash is non-null, the existing SID mill be migrated so
419 * the synthetic password in the authentication token will produce the same SID
420 * (the corresponding synthetic password handle is persisted by SyntheticPasswordManager
421 * in a per-user data storage.)
423 * If the existing credential hash is null, it means the given user should have no SID so
424 * SyntheticPasswordManager will nuke any SP handle previously persisted. In this case,
425 * the supplied credential parameter is also ignored.
427 * Also saves the escrow information necessary to re-generate the synthetic password under
428 * an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
429 * password escrow should be disabled completely on the given user.
432 public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
433 byte[] hash, String credential, int userId) throws RemoteException {
434 AuthenticationToken result = AuthenticationToken.create();
435 GateKeeperResponse response;
437 response = gatekeeper.enroll(userId, hash, credential.getBytes(),
438 result.deriveGkPassword());
439 if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
440 Log.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
441 clearSidForUser(userId);
443 saveSyntheticPasswordHandle(response.getPayload(), userId);
446 clearSidForUser(userId);
448 saveEscrowData(result, userId);
453 * Enroll a new password handle and SID for the given synthetic password and persist it on disk.
454 * Used when adding password to previously-unsecured devices.
456 public void newSidForUser(IGateKeeperService gatekeeper, AuthenticationToken authToken,
457 int userId) throws RemoteException {
458 GateKeeperResponse response = gatekeeper.enroll(userId, null, null,
459 authToken.deriveGkPassword());
460 if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
461 Log.e(TAG, "Fail to create new SID for user " + userId);
464 saveSyntheticPasswordHandle(response.getPayload(), userId);
467 // Nuke the SP handle (and as a result, its SID) for the given user.
468 public void clearSidForUser(int userId) {
469 destroyState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
472 public boolean hasSidForUser(int userId) {
473 return hasState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
476 // if null, it means there is no SID associated with the user
477 // This can happen if the user is migrated to SP but currently
478 // do not have a lockscreen password.
479 private byte[] loadSyntheticPasswordHandle(int userId) {
480 return loadState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
483 private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
484 saveState(SP_HANDLE_NAME, spHandle, DEFAULT_HANDLE, userId);
487 private boolean loadEscrowData(AuthenticationToken authToken, int userId) {
488 authToken.E0 = loadState(SP_E0_NAME, DEFAULT_HANDLE, userId);
489 authToken.P1 = loadState(SP_P1_NAME, DEFAULT_HANDLE, userId);
490 return authToken.E0 != null && authToken.P1 != null;
493 private void saveEscrowData(AuthenticationToken authToken, int userId) {
494 saveState(SP_E0_NAME, authToken.E0, DEFAULT_HANDLE, userId);
495 saveState(SP_P1_NAME, authToken.P1, DEFAULT_HANDLE, userId);
498 public boolean hasEscrowData(int userId) {
499 return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId)
500 && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId);
503 public void destroyEscrowData(int userId) {
504 destroyState(SP_E0_NAME, DEFAULT_HANDLE, userId);
505 destroyState(SP_P1_NAME, DEFAULT_HANDLE, userId);
508 private int loadWeaverSlot(long handle, int userId) {
509 final int LENGTH = Byte.BYTES + Integer.BYTES;
510 byte[] data = loadState(WEAVER_SLOT_NAME, handle, userId);
511 if (data == null || data.length != LENGTH) {
512 return INVALID_WEAVER_SLOT;
514 ByteBuffer buffer = ByteBuffer.allocate(LENGTH);
515 buffer.put(data, 0, data.length);
517 if (buffer.get() != WEAVER_VERSION) {
518 Log.e(TAG, "Invalid weaver slot version of handle " + handle);
519 return INVALID_WEAVER_SLOT;
521 return buffer.getInt();
524 private void saveWeaverSlot(int slot, long handle, int userId) {
525 ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES + Integer.BYTES);
526 buffer.put(WEAVER_VERSION);
528 saveState(WEAVER_SLOT_NAME, buffer.array(), handle, userId);
531 private void destroyWeaverSlot(long handle, int userId) {
532 int slot = loadWeaverSlot(handle, userId);
533 if (slot != INVALID_WEAVER_SLOT) {
535 weaverEnroll(slot, null, null);
536 } catch (RemoteException e) {
537 Log.w(TAG, "Failed to destroy slot", e);
540 destroyState(WEAVER_SLOT_NAME, handle, userId);
543 private int getNextAvailableWeaverSlot() {
544 Map<Integer, List<Long>> slotHandles = mStorage.listSyntheticPasswordHandlesForAllUsers(
546 HashSet<Integer> slots = new HashSet<>();
547 for (Map.Entry<Integer, List<Long>> entry : slotHandles.entrySet()) {
548 for (Long handle : entry.getValue()) {
549 int slot = loadWeaverSlot(handle, entry.getKey());
553 for (int i = 0; i < mWeaverConfig.slots; i++) {
554 if (!slots.contains(i)) {
558 throw new RuntimeException("Run out of weaver slots.");
562 * Create a new password based SP blob based on the supplied authentication token, such that
563 * a future successful authentication with unwrapPasswordBasedSyntheticPassword() would result
564 * in the same authentication token.
566 * This method only creates SP blob wrapping around the given synthetic password and does not
567 * handle logic around SID or SP handle. The caller should separately ensure that the user's SID
568 * is consistent with the device state by calling other APIs in this class.
570 * @see #newSidForUser
571 * @see #clearSidForUser
573 public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
574 String credential, int credentialType, AuthenticationToken authToken,
575 int requestedQuality, int userId)
576 throws RemoteException {
577 if (credential == null || credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
578 credentialType = LockPatternUtils.CREDENTIAL_TYPE_NONE;
579 credential = DEFAULT_PASSWORD;
582 long handle = generateHandle();
583 PasswordData pwd = PasswordData.create(credentialType);
584 byte[] pwdToken = computePasswordToken(credential, pwd);
586 final byte[] applicationId;
588 if (isWeaverAvailable()) {
589 // Weaver based user password
590 int weaverSlot = getNextAvailableWeaverSlot();
591 byte[] weaverSecret = weaverEnroll(weaverSlot, passwordTokenToWeaverKey(pwdToken), null);
592 if (weaverSecret == null) {
593 Log.e(TAG, "Fail to enroll user password under weaver " + userId);
594 return DEFAULT_HANDLE;
596 saveWeaverSlot(weaverSlot, handle, userId);
597 synchronizeWeaverFrpPassword(pwd, requestedQuality, userId, weaverSlot);
599 pwd.passwordHandle = null;
600 sid = GateKeeper.INVALID_SECURE_USER_ID;
601 applicationId = transformUnderWeaverSecret(pwdToken, weaverSecret);
603 // In case GK enrollment leaves persistent state around (in RPMB), this will nuke them
604 // to prevent them from accumulating and causing problems.
605 gatekeeper.clearSecureUserId(fakeUid(userId));
606 // GateKeeper based user password
607 GateKeeperResponse response = gatekeeper.enroll(fakeUid(userId), null, null,
608 passwordTokenToGkInput(pwdToken));
609 if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
610 Log.e(TAG, "Fail to enroll user password when creating SP for user " + userId);
611 return DEFAULT_HANDLE;
613 pwd.passwordHandle = response.getPayload();
614 sid = sidFromPasswordHandle(pwd.passwordHandle);
615 applicationId = transformUnderSecdiscardable(pwdToken,
616 createSecdiscardable(handle, userId));
617 synchronizeFrpPassword(pwd, requestedQuality, userId);
619 saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
621 createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken,
622 applicationId, sid, userId);
626 public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper,
627 String userCredential, int credentialType,
628 ICheckCredentialProgressCallback progressCallback) throws RemoteException {
629 PersistentData persistentData = mStorage.readPersistentDataBlock();
630 if (persistentData.type == PersistentData.TYPE_SP) {
631 PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
632 byte[] pwdToken = computePasswordToken(userCredential, pwd);
634 GateKeeperResponse response = gatekeeper.verifyChallenge(fakeUid(persistentData.userId),
635 0 /* challenge */, pwd.passwordHandle, passwordTokenToGkInput(pwdToken));
636 return VerifyCredentialResponse.fromGateKeeperResponse(response);
637 } else if (persistentData.type == PersistentData.TYPE_SP_WEAVER) {
638 PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
639 byte[] pwdToken = computePasswordToken(userCredential, pwd);
640 int weaverSlot = persistentData.userId;
642 return weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken)).stripPayload();
644 Log.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
645 + persistentData.type);
646 return VerifyCredentialResponse.ERROR;
651 public void migrateFrpPasswordLocked(long handle, UserInfo userInfo, int requestedQuality) {
652 if (mStorage.getPersistentDataBlock() != null
653 && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
654 PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle,
656 if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
657 int weaverSlot = loadWeaverSlot(handle, userInfo.id);
658 if (weaverSlot != INVALID_WEAVER_SLOT) {
659 synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot);
661 synchronizeFrpPassword(pwd, requestedQuality, userInfo.id);
667 private void synchronizeFrpPassword(PasswordData pwd,
668 int requestedQuality, int userId) {
669 if (mStorage.getPersistentDataBlock() != null
670 && LockPatternUtils.userOwnsFrpCredential(mContext,
671 mUserManager.getUserInfo(userId))) {
672 if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
673 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
676 mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, userId, 0, null);
681 private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
683 if (mStorage.getPersistentDataBlock() != null
684 && LockPatternUtils.userOwnsFrpCredential(mContext,
685 mUserManager.getUserInfo(userId))) {
686 if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
687 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
688 requestedQuality, pwd.toBytes());
690 mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, 0, 0, null);
695 private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
697 public long createTokenBasedSyntheticPassword(byte[] token, int userId) {
698 long handle = generateHandle();
699 if (!tokenMap.containsKey(userId)) {
700 tokenMap.put(userId, new ArrayMap<>());
702 TokenData tokenData = new TokenData();
703 final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH);
704 if (isWeaverAvailable()) {
705 tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
706 tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret,
707 PERSONALISATION_WEAVER_TOKEN, secdiscardable);
709 tokenData.secdiscardableOnDisk = secdiscardable;
710 tokenData.weaverSecret = null;
712 tokenData.aggregatedSecret = transformUnderSecdiscardable(token, secdiscardable);
714 tokenMap.get(userId).put(handle, tokenData);
718 public Set<Long> getPendingTokensForUser(int userId) {
719 if (!tokenMap.containsKey(userId)) {
720 return Collections.emptySet();
722 return tokenMap.get(userId).keySet();
725 public boolean removePendingToken(long handle, int userId) {
726 if (!tokenMap.containsKey(userId)) {
729 return tokenMap.get(userId).remove(handle) != null;
732 public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken,
734 if (!tokenMap.containsKey(userId)) {
737 TokenData tokenData = tokenMap.get(userId).get(handle);
738 if (tokenData == null) {
741 if (!loadEscrowData(authToken, userId)) {
742 Log.w(TAG, "User is not escrowable");
745 if (isWeaverAvailable()) {
746 int slot = getNextAvailableWeaverSlot();
748 weaverEnroll(slot, null, tokenData.weaverSecret);
749 } catch (RemoteException e) {
750 Log.e(TAG, "Failed to enroll weaver secret when activating token", e);
753 saveWeaverSlot(slot, handle, userId);
755 saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId);
756 createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken,
757 tokenData.aggregatedSecret, 0L, userId);
758 tokenMap.get(userId).remove(handle);
762 private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
763 byte[] applicationId, long sid, int userId) {
765 if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
766 secret = authToken.computeP0();
768 secret = authToken.syntheticPassword.getBytes();
770 byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid);
771 byte[] blob = new byte[content.length + 1 + 1];
772 blob[0] = SYNTHETIC_PASSWORD_VERSION;
774 System.arraycopy(content, 0, blob, 2, content.length);
775 saveState(SP_BLOB_NAME, blob, handle, userId);
779 * Decrypt a synthetic password by supplying the user credential and corresponding password
780 * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
781 * verification to referesh the SID & Auth token maintained by the system.
782 * Note: the credential type is not validated here since there are call sites where the type is
783 * unknown. Caller might choose to validate it by examining AuthenticationResult.credentialType
785 public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
786 long handle, String credential, int userId) throws RemoteException {
787 if (credential == null) {
788 credential = DEFAULT_PASSWORD;
790 AuthenticationResult result = new AuthenticationResult();
791 PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId));
792 result.credentialType = pwd.passwordType;
793 byte[] pwdToken = computePasswordToken(credential, pwd);
795 final byte[] applicationId;
797 int weaverSlot = loadWeaverSlot(handle, userId);
798 if (weaverSlot != INVALID_WEAVER_SLOT) {
799 // Weaver based user password
800 if (!isWeaverAvailable()) {
801 Log.e(TAG, "No weaver service to unwrap password based SP");
802 result.gkResponse = VerifyCredentialResponse.ERROR;
805 result.gkResponse = weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken));
806 if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
809 sid = GateKeeper.INVALID_SECURE_USER_ID;
810 applicationId = transformUnderWeaverSecret(pwdToken, result.gkResponse.getPayload());
812 byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
813 GateKeeperResponse response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
814 pwd.passwordHandle, gkPwdToken);
815 int responseCode = response.getResponseCode();
816 if (responseCode == GateKeeperResponse.RESPONSE_OK) {
817 result.gkResponse = VerifyCredentialResponse.OK;
818 if (response.getShouldReEnroll()) {
819 GateKeeperResponse reenrollResponse = gatekeeper.enroll(fakeUid(userId),
820 pwd.passwordHandle, gkPwdToken, gkPwdToken);
821 if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
822 pwd.passwordHandle = reenrollResponse.getPayload();
823 saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
824 synchronizeFrpPassword(pwd,
825 pwd.passwordType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
826 ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
827 : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
828 /* TODO(roosa): keep the same password quality */,
831 Log.w(TAG, "Fail to re-enroll user password for user " + userId);
832 // continue the flow anyway
835 } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
836 result.gkResponse = new VerifyCredentialResponse(response.getTimeout());
839 result.gkResponse = VerifyCredentialResponse.ERROR;
842 sid = sidFromPasswordHandle(pwd.passwordHandle);
843 applicationId = transformUnderSecdiscardable(pwdToken,
844 loadSecdiscardable(handle, userId));
847 result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
848 applicationId, sid, userId);
850 // Perform verifyChallenge to refresh auth tokens for GK if user password exists.
851 result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
856 * Decrypt a synthetic password by supplying an escrow token and corresponding token
857 * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
858 * verification to referesh the SID & Auth token maintained by the system.
860 public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword(
861 IGateKeeperService gatekeeper, long handle, byte[] token, int userId)
862 throws RemoteException {
863 AuthenticationResult result = new AuthenticationResult();
864 byte[] secdiscardable = loadSecdiscardable(handle, userId);
865 int slotId = loadWeaverSlot(handle, userId);
866 if (slotId != INVALID_WEAVER_SLOT) {
867 if (!isWeaverAvailable()) {
868 Log.e(TAG, "No weaver service to unwrap token based SP");
869 result.gkResponse = VerifyCredentialResponse.ERROR;
872 VerifyCredentialResponse response = weaverVerify(slotId, null);
873 if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK ||
874 response.getPayload() == null) {
875 Log.e(TAG, "Failed to retrieve weaver secret when unwrapping token");
876 result.gkResponse = VerifyCredentialResponse.ERROR;
879 secdiscardable = SyntheticPasswordCrypto.decrypt(response.getPayload(),
880 PERSONALISATION_WEAVER_TOKEN, secdiscardable);
882 byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
883 result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
884 applicationId, 0L, userId);
885 if (result.authToken != null) {
886 result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
887 if (result.gkResponse == null) {
888 // The user currently has no password. return OK with null payload so null
889 // is propagated to unlockUser()
890 result.gkResponse = VerifyCredentialResponse.OK;
893 result.gkResponse = VerifyCredentialResponse.ERROR;
898 private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
899 byte[] applicationId, long sid, int userId) {
900 byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
904 final byte version = blob[0];
905 if (version != SYNTHETIC_PASSWORD_VERSION && version != SYNTHETIC_PASSWORD_VERSION_V1) {
906 throw new RuntimeException("Unknown blob version");
908 if (blob[1] != type) {
909 throw new RuntimeException("Invalid blob type");
912 if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
913 secret = SyntheticPasswordCrypto.decryptBlobV1(getHandleName(handle),
914 Arrays.copyOfRange(blob, 2, blob.length), applicationId);
916 secret = decryptSPBlob(getHandleName(handle),
917 Arrays.copyOfRange(blob, 2, blob.length), applicationId);
919 if (secret == null) {
920 Log.e(TAG, "Fail to decrypt SP for user " + userId);
923 AuthenticationToken result = new AuthenticationToken();
924 if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
925 if (!loadEscrowData(result, userId)) {
926 Log.e(TAG, "User is not escrowable: " + userId);
929 result.recreate(secret);
931 result.syntheticPassword = new String(secret);
933 if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
934 Log.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
935 createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
941 * performs GK verifyChallenge and returns auth token, re-enrolling SP password handle
944 * Normally performing verifyChallenge with an AuthenticationToken should always return
945 * RESPONSE_OK, since user authentication failures are detected earlier when trying to
948 public @Nullable VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper,
949 @NonNull AuthenticationToken auth, long challenge, int userId) throws RemoteException {
950 byte[] spHandle = loadSyntheticPasswordHandle(userId);
951 if (spHandle == null) {
952 // There is no password handle associated with the given user, i.e. the user is not
953 // secured by lockscreen and has no SID, so just return here;
956 VerifyCredentialResponse result;
957 GateKeeperResponse response = gatekeeper.verifyChallenge(userId, challenge,
958 spHandle, auth.deriveGkPassword());
959 int responseCode = response.getResponseCode();
960 if (responseCode == GateKeeperResponse.RESPONSE_OK) {
961 result = new VerifyCredentialResponse(response.getPayload());
962 if (response.getShouldReEnroll()) {
963 response = gatekeeper.enroll(userId, spHandle,
964 spHandle, auth.deriveGkPassword());
965 if (response.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
966 spHandle = response.getPayload();
967 saveSyntheticPasswordHandle(spHandle, userId);
968 // Call self again to re-verify with updated handle
969 return verifyChallenge(gatekeeper, auth, challenge, userId);
971 Log.w(TAG, "Fail to re-enroll SP handle for user " + userId);
972 // Fall through, return existing handle
975 } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
976 result = new VerifyCredentialResponse(response.getTimeout());
978 result = VerifyCredentialResponse.ERROR;
983 public boolean existsHandle(long handle, int userId) {
984 return hasState(SP_BLOB_NAME, handle, userId);
987 public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
988 destroySyntheticPassword(handle, userId);
989 destroyState(SECDISCARDABLE_NAME, handle, userId);
992 public void destroyPasswordBasedSyntheticPassword(long handle, int userId) {
993 destroySyntheticPassword(handle, userId);
994 destroyState(SECDISCARDABLE_NAME, handle, userId);
995 destroyState(PASSWORD_DATA_NAME, handle, userId);
998 private void destroySyntheticPassword(long handle, int userId) {
999 destroyState(SP_BLOB_NAME, handle, userId);
1000 destroySPBlobKey(getHandleName(handle));
1001 if (hasState(WEAVER_SLOT_NAME, handle, userId)) {
1002 destroyWeaverSlot(handle, userId);
1006 private byte[] transformUnderWeaverSecret(byte[] data, byte[] secret) {
1007 byte[] weaverSecret = SyntheticPasswordCrypto.personalisedHash(
1008 PERSONALISATION_WEAVER_PASSWORD, secret);
1009 byte[] result = new byte[data.length + weaverSecret.length];
1010 System.arraycopy(data, 0, result, 0, data.length);
1011 System.arraycopy(weaverSecret, 0, result, data.length, weaverSecret.length);
1015 private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
1016 byte[] secdiscardable = SyntheticPasswordCrypto.personalisedHash(
1017 PERSONALISATION_SECDISCARDABLE, rawSecdiscardable);
1018 byte[] result = new byte[data.length + secdiscardable.length];
1019 System.arraycopy(data, 0, result, 0, data.length);
1020 System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
1024 private byte[] createSecdiscardable(long handle, int userId) {
1025 byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
1026 saveSecdiscardable(handle, data, userId);
1030 private void saveSecdiscardable(long handle, byte[] secdiscardable, int userId) {
1031 saveState(SECDISCARDABLE_NAME, secdiscardable, handle, userId);
1034 private byte[] loadSecdiscardable(long handle, int userId) {
1035 return loadState(SECDISCARDABLE_NAME, handle, userId);
1038 private boolean hasState(String stateName, long handle, int userId) {
1039 return !ArrayUtils.isEmpty(loadState(stateName, handle, userId));
1042 private byte[] loadState(String stateName, long handle, int userId) {
1043 return mStorage.readSyntheticPasswordState(userId, handle, stateName);
1046 private void saveState(String stateName, byte[] data, long handle, int userId) {
1047 mStorage.writeSyntheticPasswordState(userId, handle, stateName, data);
1050 private void destroyState(String stateName, long handle, int userId) {
1051 mStorage.deleteSyntheticPasswordState(userId, handle, stateName);
1054 protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
1055 return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, applicationId);
1058 protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
1059 return SyntheticPasswordCrypto.createBlob(blobKeyName, data, applicationId, sid);
1062 protected void destroySPBlobKey(String keyAlias) {
1063 SyntheticPasswordCrypto.destroyBlobKey(keyAlias);
1066 public static long generateHandle() {
1067 SecureRandom rng = new SecureRandom();
1070 result = rng.nextLong();
1071 } while (result == DEFAULT_HANDLE);
1075 private int fakeUid(int uid) {
1076 return 100000 + uid;
1079 protected static byte[] secureRandom(int length) {
1081 return SecureRandom.getInstance("SHA1PRNG").generateSeed(length);
1082 } catch (NoSuchAlgorithmException e) {
1083 e.printStackTrace();
1088 private String getHandleName(long handle) {
1089 return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
1092 private byte[] computePasswordToken(String password, PasswordData data) {
1093 return scrypt(password, data.salt, 1 << data.scryptN, 1 << data.scryptR, 1 << data.scryptP,
1094 PASSWORD_TOKEN_LENGTH);
1097 private byte[] passwordTokenToGkInput(byte[] token) {
1098 return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_USER_GK_AUTH, token);
1101 private byte[] passwordTokenToWeaverKey(byte[] token) {
1102 byte[] key = SyntheticPasswordCrypto.personalisedHash(PERSONALISATION_WEAVER_KEY, token);
1103 if (key.length < mWeaverConfig.keySize) {
1104 throw new RuntimeException("weaver key length too small");
1106 return Arrays.copyOf(key, mWeaverConfig.keySize);
1109 protected long sidFromPasswordHandle(byte[] handle) {
1110 return nativeSidFromPasswordHandle(handle);
1113 protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) {
1114 return nativeScrypt(password.getBytes(), salt, N, r, p, outLen);
1117 native long nativeSidFromPasswordHandle(byte[] handle);
1118 native byte[] nativeScrypt(byte[] password, byte[] salt, int N, int r, int p, int outLen);
1120 protected static ArrayList<Byte> toByteArrayList(byte[] data) {
1121 ArrayList<Byte> result = new ArrayList<Byte>(data.length);
1122 for (int i = 0; i < data.length; i++) {
1123 result.add(data[i]);
1128 protected static byte[] fromByteArrayList(ArrayList<Byte> data) {
1129 byte[] result = new byte[data.size()];
1130 for (int i = 0; i < data.size(); i++) {
1131 result[i] = data.get(i);
1136 final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
1137 public static String bytesToHex(byte[] bytes) {
1138 if (bytes == null) {
1141 char[] hexChars = new char[bytes.length * 2];
1142 for ( int j = 0; j < bytes.length; j++ ) {
1143 int v = bytes[j] & 0xFF;
1144 hexChars[j * 2] = hexArray[v >>> 4];
1145 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
1147 return new String(hexChars);