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.
16 package com.android.server;
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.hardware.weaver.V1_0.IWeaver;
21 import android.hardware.weaver.V1_0.WeaverConfig;
22 import android.hardware.weaver.V1_0.WeaverReadResponse;
23 import android.hardware.weaver.V1_0.WeaverReadStatus;
24 import android.hardware.weaver.V1_0.WeaverStatus;
25 import android.os.RemoteException;
26 import android.security.GateKeeper;
27 import android.service.gatekeeper.GateKeeperResponse;
28 import android.service.gatekeeper.IGateKeeperService;
29 import android.util.ArrayMap;
30 import android.util.Log;
31 import android.util.Slog;
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.internal.util.ArrayUtils;
35 import com.android.internal.widget.LockPatternUtils;
36 import com.android.internal.widget.VerifyCredentialResponse;
38 import libcore.util.HexEncoding;
40 import java.nio.ByteBuffer;
41 import java.security.NoSuchAlgorithmException;
42 import java.security.SecureRandom;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Collections;
46 import java.util.HashSet;
47 import java.util.List;
49 import java.util.NoSuchElementException;
54 * A class that maintains the wrapping of synthetic password by user credentials or escrow tokens.
55 * It's (mostly) a pure storage for synthetic passwords, providing APIs to creating and destroying
56 * synthetic password blobs which are wrapped by user credentials or escrow tokens.
58 * Here is the assumptions it makes:
59 * Each user has one single synthetic password at any time.
60 * The SP has an associated password handle, which binds to the SID for that user. The password
61 * handle is persisted by SyntheticPasswordManager internally.
62 * If the user credential is null, it's treated as if the credential is DEFAULT_PASSWORD
64 * Information persisted on disk:
65 * for each user (stored under DEFAULT_HANDLE):
66 * SP_HANDLE_NAME: GateKeeper password handle of synthetic password. Only available if user
67 * credential exists, cleared when user clears their credential.
68 * SP_E0_NAME, SP_P1_NAME: Secret to derive synthetic password when combining with escrow
69 * tokens. Destroyed when escrow support is turned off for the given user.
71 * for each SP blob under the user (stored under the corresponding handle):
72 * SP_BLOB_NAME: The encrypted synthetic password. Always exists.
73 * PASSWORD_DATA_NAME: Metadata about user credential. Only exists for password based SP.
74 * SECDISCARDABLE_NAME: Part of the necessary ingredient to decrypt SP_BLOB_NAME in order
75 * to facilitate secure deletion. Exists if this is a non-weaver SP
76 * (both password and token based), or it's a token-based SP under weaver.
77 * WEAVER_SLOT: Metadata about the weaver slot used. Only exists if this is a SP under weaver.
81 public class SyntheticPasswordManager {
82 private static final String SP_BLOB_NAME = "spblob";
83 private static final String SP_E0_NAME = "e0";
84 private static final String SP_P1_NAME = "p1";
85 private static final String SP_HANDLE_NAME = "handle";
86 private static final String SECDISCARDABLE_NAME = "secdis";
87 private static final int SECDISCARDABLE_LENGTH = 16 * 1024;
88 private static final String PASSWORD_DATA_NAME = "pwd";
89 private static final String WEAVER_SLOT_NAME = "weaver";
91 public static final long DEFAULT_HANDLE = 0L;
92 private static final String DEFAULT_PASSWORD = "default-password";
94 private static final byte WEAVER_VERSION = 1;
95 private static final int INVALID_WEAVER_SLOT = -1;
97 private static final byte SYNTHETIC_PASSWORD_VERSION = 1;
98 private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
99 private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
101 // 256-bit synthetic password
102 private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
104 private static final int PASSWORD_SCRYPT_N = 13;
105 private static final int PASSWORD_SCRYPT_R = 3;
106 private static final int PASSWORD_SCRYPT_P = 1;
107 private static final int PASSWORD_SALT_LENGTH = 16;
108 private static final int PASSWORD_TOKEN_LENGTH = 32;
109 private static final String TAG = "SyntheticPasswordManager";
111 private static final byte[] PERSONALISATION_SECDISCARDABLE = "secdiscardable-transform".getBytes();
112 private static final byte[] PERSONALIZATION_KEY_STORE_PASSWORD = "keystore-password".getBytes();
113 private static final byte[] PERSONALIZATION_USER_GK_AUTH = "user-gk-authentication".getBytes();
114 private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
115 private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
116 private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
117 private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
118 private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
119 private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes();
120 private static final byte[] PERSONALISATION_WEAVER_TOKEN = "weaver-token".getBytes();
122 static class AuthenticationResult {
123 public AuthenticationToken authToken;
124 public VerifyCredentialResponse gkResponse;
127 static class AuthenticationToken {
129 * Here is the relationship between all three fields:
130 * P0 and P1 are two randomly-generated blocks. P1 is stored on disk but P0 is not.
131 * syntheticPassword = hash(P0 || P1)
132 * E0 = P0 encrypted under syntheticPassword, stored on disk.
134 private @Nullable byte[] E0;
135 private @Nullable byte[] P1;
136 private @NonNull String syntheticPassword;
138 public String deriveKeyStorePassword() {
139 return bytesToHex(SyntheticPasswordCrypto.personalisedHash(
140 PERSONALIZATION_KEY_STORE_PASSWORD, syntheticPassword.getBytes()));
143 public byte[] deriveGkPassword() {
144 return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_SP_GK_AUTH,
145 syntheticPassword.getBytes());
148 public byte[] deriveDiskEncryptionKey() {
149 return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_FBE_KEY,
150 syntheticPassword.getBytes());
153 private void initialize(byte[] P0, byte[] P1) {
155 this.syntheticPassword = String.valueOf(HexEncoding.encode(
156 SyntheticPasswordCrypto.personalisedHash(
157 PERSONALIZATION_SP_SPLIT, P0, P1)));
158 this.E0 = SyntheticPasswordCrypto.encrypt(this.syntheticPassword.getBytes(),
159 PERSONALIZATION_E0, P0);
162 public void recreate(byte[] secret) {
163 initialize(secret, this.P1);
166 protected static AuthenticationToken create() {
167 AuthenticationToken result = new AuthenticationToken();
168 result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH),
169 secureRandom(SYNTHETIC_PASSWORD_LENGTH));
173 public byte[] computeP0() {
177 return SyntheticPasswordCrypto.decrypt(syntheticPassword.getBytes(), PERSONALIZATION_E0,
182 static class PasswordData {
186 public int passwordType;
188 // For GateKeeper-based credential, this is the password handle returned by GK,
189 // for weaver-based credential, this is empty.
190 public byte[] passwordHandle;
192 public static PasswordData create(int passwordType) {
193 PasswordData result = new PasswordData();
194 result.scryptN = PASSWORD_SCRYPT_N;
195 result.scryptR = PASSWORD_SCRYPT_R;
196 result.scryptP = PASSWORD_SCRYPT_P;
197 result.passwordType = passwordType;
198 result.salt = secureRandom(PASSWORD_SALT_LENGTH);
202 public static PasswordData fromBytes(byte[] data) {
203 PasswordData result = new PasswordData();
204 ByteBuffer buffer = ByteBuffer.allocate(data.length);
205 buffer.put(data, 0, data.length);
207 result.passwordType = buffer.getInt();
208 result.scryptN = buffer.get();
209 result.scryptR = buffer.get();
210 result.scryptP = buffer.get();
211 int saltLen = buffer.getInt();
212 result.salt = new byte[saltLen];
213 buffer.get(result.salt);
214 int handleLen = buffer.getInt();
216 result.passwordHandle = new byte[handleLen];
217 buffer.get(result.passwordHandle);
219 result.passwordHandle = null;
224 public byte[] toBytes() {
226 ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES
227 + Integer.BYTES + salt.length + Integer.BYTES +
228 (passwordHandle != null ? passwordHandle.length : 0));
229 buffer.putInt(passwordType);
233 buffer.putInt(salt.length);
235 if (passwordHandle != null && passwordHandle.length > 0) {
236 buffer.putInt(passwordHandle.length);
237 buffer.put(passwordHandle);
241 return buffer.array();
245 static class TokenData {
246 byte[] secdiscardableOnDisk;
248 byte[] aggregatedSecret;
251 private LockSettingsStorage mStorage;
252 private IWeaver mWeaver;
253 private WeaverConfig mWeaverConfig;
255 public SyntheticPasswordManager(LockSettingsStorage storage) {
260 protected IWeaver getWeaverService() throws RemoteException {
262 return IWeaver.getService();
263 } catch (NoSuchElementException e) {
264 Slog.i(TAG, "Device does not support weaver");
269 public synchronized void initWeaverService() {
270 if (mWeaver != null) {
274 mWeaverConfig = null;
275 mWeaver = getWeaverService();
276 if (mWeaver != null) {
277 mWeaver.getConfig((int status, WeaverConfig config) -> {
278 if (status == WeaverStatus.OK && config.slots > 0) {
279 mWeaverConfig = config;
281 Slog.e(TAG, "Failed to get weaver config, status " + status
282 + " slots: " + config.slots);
287 } catch (RemoteException e) {
288 Slog.e(TAG, "Failed to get weaver service", e);
292 private synchronized boolean isWeaverAvailable() {
293 if (mWeaver == null) {
294 //Re-initializing weaver in case there was a transient error preventing access to it.
297 return mWeaver != null && mWeaverConfig.slots > 0;
301 * Enroll the given key value pair into the specified weaver slot. if the given key is null,
302 * a default all-zero key is used. If the value is not specified, a fresh random secret is
303 * generated as the value.
305 * @return the value stored in the weaver slot
306 * @throws RemoteException
308 private byte[] weaverEnroll(int slot, byte[] key, @Nullable byte[] value)
309 throws RemoteException {
310 if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) {
311 throw new RuntimeException("Invalid slot for weaver");
314 key = new byte[mWeaverConfig.keySize];
315 } else if (key.length != mWeaverConfig.keySize) {
316 throw new RuntimeException("Invalid key size for weaver");
319 value = secureRandom(mWeaverConfig.valueSize);
321 int writeStatus = mWeaver.write(slot, toByteArrayList(key), toByteArrayList(value));
322 if (writeStatus != WeaverStatus.OK) {
323 Log.e(TAG, "weaver write failed, slot: " + slot + " status: " + writeStatus);
330 * Verify the supplied key against a weaver slot, returning a response indicating whether
331 * the verification is successful, throttled or failed. If successful, the bound secret
333 * @throws RemoteException
335 private VerifyCredentialResponse weaverVerify(int slot, byte[] key) throws RemoteException {
336 if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) {
337 throw new RuntimeException("Invalid slot for weaver");
340 key = new byte[mWeaverConfig.keySize];
341 } else if (key.length != mWeaverConfig.keySize) {
342 throw new RuntimeException("Invalid key size for weaver");
344 final VerifyCredentialResponse[] response = new VerifyCredentialResponse[1];
345 mWeaver.read(slot, toByteArrayList(key), (int status, WeaverReadResponse readResponse) -> {
347 case WeaverReadStatus.OK:
348 response[0] = new VerifyCredentialResponse(
349 fromByteArrayList(readResponse.value));
351 case WeaverReadStatus.THROTTLE:
352 response[0] = new VerifyCredentialResponse(readResponse.timeout);
353 Log.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
355 case WeaverReadStatus.INCORRECT_KEY:
356 if (readResponse.timeout == 0) {
357 response[0] = VerifyCredentialResponse.ERROR;
358 Log.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
360 response[0] = new VerifyCredentialResponse(readResponse.timeout);
361 Log.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: " + slot);
364 case WeaverReadStatus.FAILED:
365 response[0] = VerifyCredentialResponse.ERROR;
366 Log.e(TAG, "weaver read failed (FAILED), slot: " + slot);
369 response[0] = VerifyCredentialResponse.ERROR;
370 Log.e(TAG, "weaver read unknown status " + status + ", slot: " + slot);
377 public void removeUser(int userId) {
378 if (isWeaverAvailable()) {
379 for (long handle : mStorage.listSyntheticPasswordHandlesForUser(WEAVER_SLOT_NAME,
381 destroyWeaverSlot(handle, userId);
386 public int getCredentialType(long handle, int userId) {
387 byte[] passwordData = loadState(PASSWORD_DATA_NAME, handle, userId);
388 if (passwordData == null) {
389 Log.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
390 return LockPatternUtils.CREDENTIAL_TYPE_NONE;
392 return PasswordData.fromBytes(passwordData).passwordType;
396 * Initializing a new Authentication token, possibly from an existing credential and hash.
398 * The authentication token would bear a randomly-generated synthetic password.
400 * This method has the side effect of rebinding the SID of the given user to the
401 * newly-generated SP.
403 * If the existing credential hash is non-null, the existing SID mill be migrated so
404 * the synthetic password in the authentication token will produce the same SID
405 * (the corresponding synthetic password handle is persisted by SyntheticPasswordManager
406 * in a per-user data storage.)
408 * If the existing credential hash is null, it means the given user should have no SID so
409 * SyntheticPasswordManager will nuke any SP handle previously persisted. In this case,
410 * the supplied credential parameter is also ignored.
412 * Also saves the escrow information necessary to re-generate the synthetic password under
413 * an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
414 * password escrow should be disabled completely on the given user.
417 public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
418 byte[] hash, String credential, int userId) throws RemoteException {
419 AuthenticationToken result = AuthenticationToken.create();
420 GateKeeperResponse response;
422 response = gatekeeper.enroll(userId, hash, credential.getBytes(),
423 result.deriveGkPassword());
424 if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
425 Log.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
426 clearSidForUser(userId);
428 saveSyntheticPasswordHandle(response.getPayload(), userId);
431 clearSidForUser(userId);
433 saveEscrowData(result, userId);
438 * Enroll a new password handle and SID for the given synthetic password and persist it on disk.
439 * Used when adding password to previously-unsecured devices.
441 public void newSidForUser(IGateKeeperService gatekeeper, AuthenticationToken authToken,
442 int userId) throws RemoteException {
443 GateKeeperResponse response = gatekeeper.enroll(userId, null, null,
444 authToken.deriveGkPassword());
445 if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
446 Log.e(TAG, "Fail to create new SID for user " + userId);
449 saveSyntheticPasswordHandle(response.getPayload(), userId);
452 // Nuke the SP handle (and as a result, its SID) for the given user.
453 public void clearSidForUser(int userId) {
454 destroyState(SP_HANDLE_NAME, true, DEFAULT_HANDLE, userId);
457 public boolean hasSidForUser(int userId) {
458 return hasState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
461 // if null, it means there is no SID associated with the user
462 // This can happen if the user is migrated to SP but currently
463 // do not have a lockscreen password.
464 private byte[] loadSyntheticPasswordHandle(int userId) {
465 return loadState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
468 private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
469 saveState(SP_HANDLE_NAME, spHandle, DEFAULT_HANDLE, userId);
472 private boolean loadEscrowData(AuthenticationToken authToken, int userId) {
473 authToken.E0 = loadState(SP_E0_NAME, DEFAULT_HANDLE, userId);
474 authToken.P1 = loadState(SP_P1_NAME, DEFAULT_HANDLE, userId);
475 return authToken.E0 != null && authToken.P1 != null;
478 private void saveEscrowData(AuthenticationToken authToken, int userId) {
479 saveState(SP_E0_NAME, authToken.E0, DEFAULT_HANDLE, userId);
480 saveState(SP_P1_NAME, authToken.P1, DEFAULT_HANDLE, userId);
483 public boolean hasEscrowData(int userId) {
484 return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId)
485 && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId);
488 public void destroyEscrowData(int userId) {
489 destroyState(SP_E0_NAME, true, DEFAULT_HANDLE, userId);
490 destroyState(SP_P1_NAME, true, DEFAULT_HANDLE, userId);
493 private int loadWeaverSlot(long handle, int userId) {
494 final int LENGTH = Byte.BYTES + Integer.BYTES;
495 byte[] data = loadState(WEAVER_SLOT_NAME, handle, userId);
496 if (data == null || data.length != LENGTH) {
497 return INVALID_WEAVER_SLOT;
499 ByteBuffer buffer = ByteBuffer.allocate(LENGTH);
500 buffer.put(data, 0, data.length);
502 if (buffer.get() != WEAVER_VERSION) {
503 Log.e(TAG, "Invalid weaver slot version of handle " + handle);
504 return INVALID_WEAVER_SLOT;
506 return buffer.getInt();
509 private void saveWeaverSlot(int slot, long handle, int userId) {
510 ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES + Integer.BYTES);
511 buffer.put(WEAVER_VERSION);
513 saveState(WEAVER_SLOT_NAME, buffer.array(), handle, userId);
516 private void destroyWeaverSlot(long handle, int userId) {
517 int slot = loadWeaverSlot(handle, userId);
518 if (slot != INVALID_WEAVER_SLOT) {
520 weaverEnroll(slot, null, null);
521 } catch (RemoteException e) {
522 Log.w(TAG, "Failed to destroy slot", e);
525 destroyState(WEAVER_SLOT_NAME, true, handle, userId);
528 private int getNextAvailableWeaverSlot() {
529 Map<Integer, List<Long>> slotHandles = mStorage.listSyntheticPasswordHandlesForAllUsers(
531 HashSet<Integer> slots = new HashSet<>();
532 for (Map.Entry<Integer, List<Long>> entry : slotHandles.entrySet()) {
533 for (Long handle : entry.getValue()) {
534 int slot = loadWeaverSlot(handle, entry.getKey());
538 for (int i = 0; i < mWeaverConfig.slots; i++) {
539 if (!slots.contains(i)) {
543 throw new RuntimeException("Run out of weaver slots.");
547 * Create a new password based SP blob based on the supplied authentication token, such that
548 * a future successful authentication with unwrapPasswordBasedSyntheticPassword() would result
549 * in the same authentication token.
551 * This method only creates SP blob wrapping around the given synthetic password and does not
552 * handle logic around SID or SP handle. The caller should separately ensure that the user's SID
553 * is consistent with the device state by calling other APIs in this class.
555 * @see #newSidForUser
556 * @see #clearSidForUser
558 public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
559 String credential, int credentialType, AuthenticationToken authToken, int userId)
560 throws RemoteException {
561 if (credential == null || credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
562 credentialType = LockPatternUtils.CREDENTIAL_TYPE_NONE;
563 credential = DEFAULT_PASSWORD;
566 long handle = generateHandle();
567 PasswordData pwd = PasswordData.create(credentialType);
568 byte[] pwdToken = computePasswordToken(credential, pwd);
570 final byte[] applicationId;
572 if (isWeaverAvailable()) {
573 // Weaver based user password
574 int weaverSlot = getNextAvailableWeaverSlot();
575 byte[] weaverSecret = weaverEnroll(weaverSlot, passwordTokenToWeaverKey(pwdToken), null);
576 if (weaverSecret == null) {
577 Log.e(TAG, "Fail to enroll user password under weaver " + userId);
578 return DEFAULT_HANDLE;
580 saveWeaverSlot(weaverSlot, handle, userId);
582 pwd.passwordHandle = null;
583 sid = GateKeeper.INVALID_SECURE_USER_ID;
584 applicationId = transformUnderWeaverSecret(pwdToken, weaverSecret);
586 // GateKeeper based user password
587 GateKeeperResponse response = gatekeeper.enroll(fakeUid(userId), null, null,
588 passwordTokenToGkInput(pwdToken));
589 if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
590 Log.e(TAG, "Fail to enroll user password when creating SP for user " + userId);
591 return DEFAULT_HANDLE;
593 pwd.passwordHandle = response.getPayload();
594 sid = sidFromPasswordHandle(pwd.passwordHandle);
595 applicationId = transformUnderSecdiscardable(pwdToken,
596 createSecdiscardable(handle, userId));
598 saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
600 createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken,
601 applicationId, sid, userId);
605 private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
607 public long createTokenBasedSyntheticPassword(byte[] token, int userId) {
608 long handle = generateHandle();
609 if (!tokenMap.containsKey(userId)) {
610 tokenMap.put(userId, new ArrayMap<>());
612 TokenData tokenData = new TokenData();
613 final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH);
614 if (isWeaverAvailable()) {
615 tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
616 tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret,
617 PERSONALISATION_WEAVER_TOKEN, secdiscardable);
619 tokenData.secdiscardableOnDisk = secdiscardable;
620 tokenData.weaverSecret = null;
622 tokenData.aggregatedSecret = transformUnderSecdiscardable(token, secdiscardable);
624 tokenMap.get(userId).put(handle, tokenData);
628 public Set<Long> getPendingTokensForUser(int userId) {
629 if (!tokenMap.containsKey(userId)) {
630 return Collections.emptySet();
632 return tokenMap.get(userId).keySet();
635 public boolean removePendingToken(long handle, int userId) {
636 if (!tokenMap.containsKey(userId)) {
639 return tokenMap.get(userId).remove(handle) != null;
642 public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken,
644 if (!tokenMap.containsKey(userId)) {
647 TokenData tokenData = tokenMap.get(userId).get(handle);
648 if (tokenData == null) {
651 if (!loadEscrowData(authToken, userId)) {
652 Log.w(TAG, "User is not escrowable");
655 if (isWeaverAvailable()) {
656 int slot = getNextAvailableWeaverSlot();
658 weaverEnroll(slot, null, tokenData.weaverSecret);
659 } catch (RemoteException e) {
660 Log.e(TAG, "Failed to enroll weaver secret when activating token", e);
663 saveWeaverSlot(slot, handle, userId);
665 saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId);
666 createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken,
667 tokenData.aggregatedSecret, 0L, userId);
668 tokenMap.get(userId).remove(handle);
672 private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
673 byte[] applicationId, long sid, int userId) {
675 if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
676 secret = authToken.computeP0();
678 secret = authToken.syntheticPassword.getBytes();
680 byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid);
681 byte[] blob = new byte[content.length + 1 + 1];
682 blob[0] = SYNTHETIC_PASSWORD_VERSION;
684 System.arraycopy(content, 0, blob, 2, content.length);
685 saveState(SP_BLOB_NAME, blob, handle, userId);
689 * Decrypt a synthetic password by supplying the user credential and corresponding password
690 * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
691 * verification to referesh the SID & Auth token maintained by the system.
693 public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
694 long handle, String credential, int userId) throws RemoteException {
695 if (credential == null) {
696 credential = DEFAULT_PASSWORD;
698 AuthenticationResult result = new AuthenticationResult();
699 PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId));
700 byte[] pwdToken = computePasswordToken(credential, pwd);
702 final byte[] applicationId;
703 int weaverSlot = loadWeaverSlot(handle, userId);
704 if (weaverSlot != INVALID_WEAVER_SLOT) {
705 // Weaver based user password
706 if (!isWeaverAvailable()) {
707 Log.e(TAG, "No weaver service to unwrap password based SP");
708 result.gkResponse = VerifyCredentialResponse.ERROR;
711 result.gkResponse = weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken));
712 if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
715 applicationId = transformUnderWeaverSecret(pwdToken, result.gkResponse.getPayload());
717 byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
718 GateKeeperResponse response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
719 pwd.passwordHandle, gkPwdToken);
720 int responseCode = response.getResponseCode();
721 if (responseCode == GateKeeperResponse.RESPONSE_OK) {
722 result.gkResponse = VerifyCredentialResponse.OK;
723 if (response.getShouldReEnroll()) {
724 GateKeeperResponse reenrollResponse = gatekeeper.enroll(fakeUid(userId),
725 pwd.passwordHandle, gkPwdToken, gkPwdToken);
726 if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
727 pwd.passwordHandle = reenrollResponse.getPayload();
728 saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
730 Log.w(TAG, "Fail to re-enroll user password for user " + userId);
731 // continue the flow anyway
734 } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
735 result.gkResponse = new VerifyCredentialResponse(response.getTimeout());
738 result.gkResponse = VerifyCredentialResponse.ERROR;
741 applicationId = transformUnderSecdiscardable(pwdToken,
742 loadSecdiscardable(handle, userId));
745 result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
746 applicationId, userId);
748 // Perform verifyChallenge to refresh auth tokens for GK if user password exists.
749 result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
754 * Decrypt a synthetic password by supplying an escrow token and corresponding token
755 * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
756 * verification to referesh the SID & Auth token maintained by the system.
758 public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword(
759 IGateKeeperService gatekeeper, long handle, byte[] token, int userId)
760 throws RemoteException {
761 AuthenticationResult result = new AuthenticationResult();
762 byte[] secdiscardable = loadSecdiscardable(handle, userId);
763 int slotId = loadWeaverSlot(handle, userId);
764 if (slotId != INVALID_WEAVER_SLOT) {
765 if (!isWeaverAvailable()) {
766 Log.e(TAG, "No weaver service to unwrap token based SP");
767 result.gkResponse = VerifyCredentialResponse.ERROR;
770 VerifyCredentialResponse response = weaverVerify(slotId, null);
771 if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK ||
772 response.getPayload() == null) {
773 Log.e(TAG, "Failed to retrieve weaver secret when unwrapping token");
774 result.gkResponse = VerifyCredentialResponse.ERROR;
777 secdiscardable = SyntheticPasswordCrypto.decrypt(response.getPayload(),
778 PERSONALISATION_WEAVER_TOKEN, secdiscardable);
780 byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
781 result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
782 applicationId, userId);
783 if (result.authToken != null) {
784 result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
785 if (result.gkResponse == null) {
786 // The user currently has no password. return OK with null payload so null
787 // is propagated to unlockUser()
788 result.gkResponse = VerifyCredentialResponse.OK;
791 result.gkResponse = VerifyCredentialResponse.ERROR;
796 private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
797 byte[] applicationId, int userId) {
798 byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
802 if (blob[0] != SYNTHETIC_PASSWORD_VERSION) {
803 throw new RuntimeException("Unknown blob version");
805 if (blob[1] != type) {
806 throw new RuntimeException("Invalid blob type");
808 byte[] secret = decryptSPBlob(getHandleName(handle),
809 Arrays.copyOfRange(blob, 2, blob.length), applicationId);
810 if (secret == null) {
811 Log.e(TAG, "Fail to decrypt SP for user " + userId);
814 AuthenticationToken result = new AuthenticationToken();
815 if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
816 if (!loadEscrowData(result, userId)) {
817 Log.e(TAG, "User is not escrowable: " + userId);
820 result.recreate(secret);
822 result.syntheticPassword = new String(secret);
828 * performs GK verifyChallenge and returns auth token, re-enrolling SP password handle
831 * Normally performing verifyChallenge with an AuthenticationToken should always return
832 * RESPONSE_OK, since user authentication failures are detected earlier when trying to
835 public @Nullable VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper,
836 @NonNull AuthenticationToken auth, long challenge, int userId) throws RemoteException {
837 byte[] spHandle = loadSyntheticPasswordHandle(userId);
838 if (spHandle == null) {
839 // There is no password handle associated with the given user, i.e. the user is not
840 // secured by lockscreen and has no SID, so just return here;
843 VerifyCredentialResponse result;
844 GateKeeperResponse response = gatekeeper.verifyChallenge(userId, challenge,
845 spHandle, auth.deriveGkPassword());
846 int responseCode = response.getResponseCode();
847 if (responseCode == GateKeeperResponse.RESPONSE_OK) {
848 result = new VerifyCredentialResponse(response.getPayload());
849 if (response.getShouldReEnroll()) {
850 response = gatekeeper.enroll(userId, spHandle,
851 spHandle, auth.deriveGkPassword());
852 if (response.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
853 spHandle = response.getPayload();
854 saveSyntheticPasswordHandle(spHandle, userId);
855 // Call self again to re-verify with updated handle
856 return verifyChallenge(gatekeeper, auth, challenge, userId);
858 Log.w(TAG, "Fail to re-enroll SP handle for user " + userId);
859 // Fall through, return existing handle
862 } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
863 result = new VerifyCredentialResponse(response.getTimeout());
865 result = VerifyCredentialResponse.ERROR;
870 public boolean existsHandle(long handle, int userId) {
871 return hasState(SP_BLOB_NAME, handle, userId);
874 public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
875 destroySyntheticPassword(handle, userId);
876 destroyState(SECDISCARDABLE_NAME, true, handle, userId);
879 public void destroyPasswordBasedSyntheticPassword(long handle, int userId) {
880 destroySyntheticPassword(handle, userId);
881 destroyState(SECDISCARDABLE_NAME, true, handle, userId);
882 destroyState(PASSWORD_DATA_NAME, true, handle, userId);
885 private void destroySyntheticPassword(long handle, int userId) {
886 destroyState(SP_BLOB_NAME, true, handle, userId);
887 destroySPBlobKey(getHandleName(handle));
888 if (hasState(WEAVER_SLOT_NAME, handle, userId)) {
889 destroyWeaverSlot(handle, userId);
893 private byte[] transformUnderWeaverSecret(byte[] data, byte[] secret) {
894 byte[] weaverSecret = SyntheticPasswordCrypto.personalisedHash(
895 PERSONALISATION_WEAVER_PASSWORD, secret);
896 byte[] result = new byte[data.length + weaverSecret.length];
897 System.arraycopy(data, 0, result, 0, data.length);
898 System.arraycopy(weaverSecret, 0, result, data.length, weaverSecret.length);
902 private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
903 byte[] secdiscardable = SyntheticPasswordCrypto.personalisedHash(
904 PERSONALISATION_SECDISCARDABLE, rawSecdiscardable);
905 byte[] result = new byte[data.length + secdiscardable.length];
906 System.arraycopy(data, 0, result, 0, data.length);
907 System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
911 private byte[] createSecdiscardable(long handle, int userId) {
912 byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
913 saveSecdiscardable(handle, data, userId);
917 private void saveSecdiscardable(long handle, byte[] secdiscardable, int userId) {
918 saveState(SECDISCARDABLE_NAME, secdiscardable, handle, userId);
921 private byte[] loadSecdiscardable(long handle, int userId) {
922 return loadState(SECDISCARDABLE_NAME, handle, userId);
925 private boolean hasState(String stateName, long handle, int userId) {
926 return !ArrayUtils.isEmpty(loadState(stateName, handle, userId));
929 private byte[] loadState(String stateName, long handle, int userId) {
930 return mStorage.readSyntheticPasswordState(userId, handle, stateName);
933 private void saveState(String stateName, byte[] data, long handle, int userId) {
934 mStorage.writeSyntheticPasswordState(userId, handle, stateName, data);
937 private void destroyState(String stateName, boolean secure, long handle, int userId) {
938 mStorage.deleteSyntheticPasswordState(userId, handle, stateName, secure);
941 protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
942 return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, applicationId);
945 protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
946 return SyntheticPasswordCrypto.createBlob(blobKeyName, data, applicationId, sid);
949 protected void destroySPBlobKey(String keyAlias) {
950 SyntheticPasswordCrypto.destroyBlobKey(keyAlias);
953 public static long generateHandle() {
954 SecureRandom rng = new SecureRandom();
957 result = rng.nextLong();
958 } while (result == DEFAULT_HANDLE);
962 private int fakeUid(int uid) {
966 protected static byte[] secureRandom(int length) {
968 return SecureRandom.getInstance("SHA1PRNG").generateSeed(length);
969 } catch (NoSuchAlgorithmException e) {
975 private String getHandleName(long handle) {
976 return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
979 private byte[] computePasswordToken(String password, PasswordData data) {
980 return scrypt(password, data.salt, 1 << data.scryptN, 1 << data.scryptR, 1 << data.scryptP,
981 PASSWORD_TOKEN_LENGTH);
984 private byte[] passwordTokenToGkInput(byte[] token) {
985 return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_USER_GK_AUTH, token);
988 private byte[] passwordTokenToWeaverKey(byte[] token) {
989 byte[] key = SyntheticPasswordCrypto.personalisedHash(PERSONALISATION_WEAVER_KEY, token);
990 if (key.length < mWeaverConfig.keySize) {
991 throw new RuntimeException("weaver key length too small");
993 return Arrays.copyOf(key, mWeaverConfig.keySize);
996 protected long sidFromPasswordHandle(byte[] handle) {
997 return nativeSidFromPasswordHandle(handle);
1000 protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) {
1001 return nativeScrypt(password.getBytes(), salt, N, r, p, outLen);
1004 native long nativeSidFromPasswordHandle(byte[] handle);
1005 native byte[] nativeScrypt(byte[] password, byte[] salt, int N, int r, int p, int outLen);
1007 protected static ArrayList<Byte> toByteArrayList(byte[] data) {
1008 ArrayList<Byte> result = new ArrayList<Byte>(data.length);
1009 for (int i = 0; i < data.length; i++) {
1010 result.add(data[i]);
1015 protected static byte[] fromByteArrayList(ArrayList<Byte> data) {
1016 byte[] result = new byte[data.size()];
1017 for (int i = 0; i < data.size(); i++) {
1018 result[i] = data.get(i);
1023 final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
1024 public static String bytesToHex(byte[] bytes) {
1025 if (bytes == null) {
1028 char[] hexChars = new char[bytes.length * 2];
1029 for ( int j = 0; j < bytes.length; j++ ) {
1030 int v = bytes[j] & 0xFF;
1031 hexChars[j * 2] = hexArray[v >>> 4];
1032 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
1034 return new String(hexChars);