OSDN Git Service

Swap the order of synthetic password wrapping
[android-x86/frameworks-base.git] / services / core / java / com / android / server / locksettings / SyntheticPasswordManager.java
1 /*
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
17 package com.android.server.locksettings;
18
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;
37
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;
44
45 import libcore.util.HexEncoding;
46
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;
55 import java.util.Map;
56 import java.util.NoSuchElementException;
57 import java.util.Set;
58
59
60 /**
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.
64  *
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
70  *
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.
77  *
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.
85  *
86  *
87  */
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";
97
98     public static final long DEFAULT_HANDLE = 0L;
99     private static final String DEFAULT_PASSWORD = "default-password";
100
101     private static final byte WEAVER_VERSION = 1;
102     private static final int INVALID_WEAVER_SLOT = -1;
103
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;
108
109     // 256-bit synthetic password
110     private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
111
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";
118
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();
129
130     static class AuthenticationResult {
131         public AuthenticationToken authToken;
132         public VerifyCredentialResponse gkResponse;
133         public int credentialType;
134     }
135
136     static class AuthenticationToken {
137         /*
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.
142          */
143         private @Nullable byte[] E0;
144         private @Nullable byte[] P1;
145         private @NonNull String syntheticPassword;
146
147         public String deriveKeyStorePassword() {
148             return bytesToHex(SyntheticPasswordCrypto.personalisedHash(
149                     PERSONALIZATION_KEY_STORE_PASSWORD, syntheticPassword.getBytes()));
150         }
151
152         public byte[] deriveGkPassword() {
153             return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_SP_GK_AUTH,
154                     syntheticPassword.getBytes());
155         }
156
157         public byte[] deriveDiskEncryptionKey() {
158             return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_FBE_KEY,
159                     syntheticPassword.getBytes());
160         }
161
162         private void initialize(byte[] P0, byte[] P1) {
163             this.P1 = 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);
169         }
170
171         public void recreate(byte[] secret) {
172             initialize(secret, this.P1);
173         }
174
175         protected static AuthenticationToken create() {
176             AuthenticationToken result = new AuthenticationToken();
177             result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH),
178                     secureRandom(SYNTHETIC_PASSWORD_LENGTH));
179             return result;
180         }
181
182         public byte[] computeP0() {
183             if (E0 == null) {
184                 return null;
185             }
186             return SyntheticPasswordCrypto.decrypt(syntheticPassword.getBytes(), PERSONALIZATION_E0,
187                     E0);
188         }
189     }
190
191     static class PasswordData {
192         byte scryptN;
193         byte scryptR;
194         byte scryptP;
195         public int passwordType;
196         byte[] salt;
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;
200
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);
208             return result;
209         }
210
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);
215             buffer.flip();
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();
224             if (handleLen > 0) {
225                 result.passwordHandle = new byte[handleLen];
226                 buffer.get(result.passwordHandle);
227             } else {
228                 result.passwordHandle = null;
229             }
230             return result;
231         }
232
233         public byte[] toBytes() {
234
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);
239             buffer.put(scryptN);
240             buffer.put(scryptR);
241             buffer.put(scryptP);
242             buffer.putInt(salt.length);
243             buffer.put(salt);
244             if (passwordHandle != null && passwordHandle.length > 0) {
245                 buffer.putInt(passwordHandle.length);
246                 buffer.put(passwordHandle);
247             } else {
248                 buffer.putInt(0);
249             }
250             return buffer.array();
251         }
252     }
253
254     static class TokenData {
255         byte[] secdiscardableOnDisk;
256         byte[] weaverSecret;
257         byte[] aggregatedSecret;
258     }
259
260     private final Context mContext;
261     private LockSettingsStorage mStorage;
262     private IWeaver mWeaver;
263     private WeaverConfig mWeaverConfig;
264
265     private final UserManager mUserManager;
266
267     public SyntheticPasswordManager(Context context, LockSettingsStorage storage,
268             UserManager userManager) {
269         mContext = context;
270         mStorage = storage;
271         mUserManager = userManager;
272     }
273
274     @VisibleForTesting
275     protected IWeaver getWeaverService() throws RemoteException {
276         try {
277             return IWeaver.getService();
278         } catch (NoSuchElementException e) {
279             Slog.i(TAG, "Device does not support weaver");
280             return null;
281         }
282     }
283
284     public synchronized void initWeaverService() {
285         if (mWeaver != null) {
286             return;
287         }
288         try {
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;
295                     } else {
296                         Slog.e(TAG, "Failed to get weaver config, status " + status
297                                 + " slots: " + config.slots);
298                         mWeaver = null;
299                     }
300                 });
301             }
302         } catch (RemoteException e) {
303             Slog.e(TAG, "Failed to get weaver service", e);
304         }
305     }
306
307     private synchronized boolean isWeaverAvailable() {
308         if (mWeaver == null) {
309             //Re-initializing weaver in case there was a transient error preventing access to it.
310             initWeaverService();
311         }
312         return mWeaver != null && mWeaverConfig.slots > 0;
313     }
314
315     /**
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.
319      *
320      * @return the value stored in the weaver slot
321      * @throws RemoteException
322      */
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");
327         }
328         if (key == null) {
329             key = new byte[mWeaverConfig.keySize];
330         } else if (key.length != mWeaverConfig.keySize) {
331             throw new RuntimeException("Invalid key size for weaver");
332         }
333         if (value == null) {
334             value = secureRandom(mWeaverConfig.valueSize);
335         }
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);
339             return null;
340         }
341         return value;
342     }
343
344     /**
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
347      * is also returned.
348      * @throws RemoteException
349      */
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");
353         }
354         if (key == null) {
355             key = new byte[mWeaverConfig.keySize];
356         } else if (key.length != mWeaverConfig.keySize) {
357             throw new RuntimeException("Invalid key size for weaver");
358         }
359         final VerifyCredentialResponse[] response = new VerifyCredentialResponse[1];
360         mWeaver.read(slot, toByteArrayList(key), (int status, WeaverReadResponse readResponse) -> {
361             switch (status) {
362                 case WeaverReadStatus.OK:
363                     response[0] = new VerifyCredentialResponse(
364                             fromByteArrayList(readResponse.value));
365                     break;
366                 case WeaverReadStatus.THROTTLE:
367                     response[0] = new VerifyCredentialResponse(readResponse.timeout);
368                     Log.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
369                     break;
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);
374                     } else {
375                         response[0] = new VerifyCredentialResponse(readResponse.timeout);
376                         Log.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: " + slot);
377                     }
378                     break;
379                 case WeaverReadStatus.FAILED:
380                     response[0] = VerifyCredentialResponse.ERROR;
381                     Log.e(TAG, "weaver read failed (FAILED), slot: " + slot);
382                     break;
383                default:
384                    response[0] = VerifyCredentialResponse.ERROR;
385                    Log.e(TAG, "weaver read unknown status " + status + ", slot: " + slot);
386                    break;
387             }
388         });
389         return response[0];
390     }
391
392     public void removeUser(int userId) {
393         if (isWeaverAvailable()) {
394             for (long handle : mStorage.listSyntheticPasswordHandlesForUser(WEAVER_SLOT_NAME,
395                     userId)) {
396                 destroyWeaverSlot(handle, userId);
397             }
398         }
399     }
400
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;
406         }
407         return PasswordData.fromBytes(passwordData).passwordType;
408     }
409
410     /**
411      * Initializing a new Authentication token, possibly from an existing credential and hash.
412      *
413      * The authentication token would bear a randomly-generated synthetic password.
414      *
415      * This method has the side effect of rebinding the SID of the given user to the
416      * newly-generated SP.
417      *
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.)
422      *
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.
426      *
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.
430      *
431      */
432     public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
433             byte[] hash, String credential, int userId) throws RemoteException {
434         AuthenticationToken result = AuthenticationToken.create();
435         GateKeeperResponse response;
436         if (hash != null) {
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);
442             } else {
443                 saveSyntheticPasswordHandle(response.getPayload(), userId);
444             }
445         } else {
446             clearSidForUser(userId);
447         }
448         saveEscrowData(result, userId);
449         return result;
450     }
451
452     /**
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.
455      */
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);
462             return;
463         }
464         saveSyntheticPasswordHandle(response.getPayload(), userId);
465     }
466
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);
470     }
471
472     public boolean hasSidForUser(int userId) {
473         return hasState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
474     }
475
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);
481     }
482
483     private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
484         saveState(SP_HANDLE_NAME, spHandle, DEFAULT_HANDLE, userId);
485     }
486
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;
491     }
492
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);
496     }
497
498     public boolean hasEscrowData(int userId) {
499         return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId)
500                 && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId);
501     }
502
503     public void destroyEscrowData(int userId) {
504         destroyState(SP_E0_NAME, DEFAULT_HANDLE, userId);
505         destroyState(SP_P1_NAME, DEFAULT_HANDLE, userId);
506     }
507
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;
513         }
514         ByteBuffer buffer = ByteBuffer.allocate(LENGTH);
515         buffer.put(data, 0, data.length);
516         buffer.flip();
517         if (buffer.get() != WEAVER_VERSION) {
518             Log.e(TAG, "Invalid weaver slot version of handle " + handle);
519             return INVALID_WEAVER_SLOT;
520         }
521         return buffer.getInt();
522     }
523
524     private void saveWeaverSlot(int slot, long handle, int userId) {
525         ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES + Integer.BYTES);
526         buffer.put(WEAVER_VERSION);
527         buffer.putInt(slot);
528         saveState(WEAVER_SLOT_NAME, buffer.array(), handle, userId);
529     }
530
531     private void destroyWeaverSlot(long handle, int userId) {
532         int slot = loadWeaverSlot(handle, userId);
533         if (slot != INVALID_WEAVER_SLOT) {
534             try {
535                 weaverEnroll(slot, null, null);
536             } catch (RemoteException e) {
537                 Log.w(TAG, "Failed to destroy slot", e);
538             }
539         }
540         destroyState(WEAVER_SLOT_NAME, handle, userId);
541     }
542
543     private int getNextAvailableWeaverSlot() {
544         Map<Integer, List<Long>> slotHandles = mStorage.listSyntheticPasswordHandlesForAllUsers(
545                 WEAVER_SLOT_NAME);
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());
550                 slots.add(slot);
551             }
552         }
553         for (int i = 0; i < mWeaverConfig.slots; i++) {
554             if (!slots.contains(i)) {
555                 return i;
556             }
557         }
558         throw new RuntimeException("Run out of weaver slots.");
559     }
560
561     /**
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.
565      *
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.
569      *
570      * @see #newSidForUser
571      * @see #clearSidForUser
572      */
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;
580         }
581
582         long handle = generateHandle();
583         PasswordData pwd = PasswordData.create(credentialType);
584         byte[] pwdToken = computePasswordToken(credential, pwd);
585         final long sid;
586         final byte[] applicationId;
587
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;
595             }
596             saveWeaverSlot(weaverSlot, handle, userId);
597             synchronizeWeaverFrpPassword(pwd, requestedQuality, userId, weaverSlot);
598
599             pwd.passwordHandle = null;
600             sid = GateKeeper.INVALID_SECURE_USER_ID;
601             applicationId = transformUnderWeaverSecret(pwdToken, weaverSecret);
602         } else {
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;
612             }
613             pwd.passwordHandle = response.getPayload();
614             sid = sidFromPasswordHandle(pwd.passwordHandle);
615             applicationId = transformUnderSecdiscardable(pwdToken,
616                     createSecdiscardable(handle, userId));
617             synchronizeFrpPassword(pwd, requestedQuality, userId);
618         }
619         saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
620
621         createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken,
622                 applicationId, sid, userId);
623         return handle;
624     }
625
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);
633
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;
641
642             return weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken)).stripPayload();
643         } else {
644             Log.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
645                     + persistentData.type);
646             return VerifyCredentialResponse.ERROR;
647         }
648     }
649
650
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,
655                     userInfo.id));
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);
660                 } else {
661                     synchronizeFrpPassword(pwd, requestedQuality, userInfo.id);
662                 }
663             }
664         }
665     }
666
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,
674                         pwd.toBytes());
675             } else {
676                 mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, userId, 0, null);
677             }
678         }
679     }
680
681     private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
682             int weaverSlot) {
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());
689             } else {
690                 mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, 0, 0, null);
691             }
692         }
693     }
694
695     private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
696
697     public long createTokenBasedSyntheticPassword(byte[] token, int userId) {
698         long handle = generateHandle();
699         if (!tokenMap.containsKey(userId)) {
700             tokenMap.put(userId, new ArrayMap<>());
701         }
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);
708         } else {
709             tokenData.secdiscardableOnDisk = secdiscardable;
710             tokenData.weaverSecret = null;
711         }
712         tokenData.aggregatedSecret = transformUnderSecdiscardable(token, secdiscardable);
713
714         tokenMap.get(userId).put(handle, tokenData);
715         return handle;
716     }
717
718     public Set<Long> getPendingTokensForUser(int userId) {
719         if (!tokenMap.containsKey(userId)) {
720             return Collections.emptySet();
721         }
722         return tokenMap.get(userId).keySet();
723     }
724
725     public boolean removePendingToken(long handle, int userId) {
726         if (!tokenMap.containsKey(userId)) {
727             return false;
728         }
729         return tokenMap.get(userId).remove(handle) != null;
730     }
731
732     public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken,
733             int userId) {
734         if (!tokenMap.containsKey(userId)) {
735             return false;
736         }
737         TokenData tokenData = tokenMap.get(userId).get(handle);
738         if (tokenData == null) {
739             return false;
740         }
741         if (!loadEscrowData(authToken, userId)) {
742             Log.w(TAG, "User is not escrowable");
743             return false;
744         }
745         if (isWeaverAvailable()) {
746             int slot = getNextAvailableWeaverSlot();
747             try {
748                 weaverEnroll(slot, null, tokenData.weaverSecret);
749             } catch (RemoteException e) {
750                 Log.e(TAG, "Failed to enroll weaver secret when activating token", e);
751                 return false;
752             }
753             saveWeaverSlot(slot, handle, userId);
754         }
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);
759         return true;
760     }
761
762     private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
763             byte[] applicationId, long sid, int userId) {
764         final byte[] secret;
765         if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
766             secret = authToken.computeP0();
767         } else {
768             secret = authToken.syntheticPassword.getBytes();
769         }
770         byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid);
771         byte[] blob = new byte[content.length + 1 + 1];
772         blob[0] = SYNTHETIC_PASSWORD_VERSION;
773         blob[1] = type;
774         System.arraycopy(content, 0, blob, 2, content.length);
775         saveState(SP_BLOB_NAME, blob, handle, userId);
776     }
777
778     /**
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
784      */
785     public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
786             long handle, String credential, int userId) throws RemoteException {
787         if (credential == null) {
788             credential = DEFAULT_PASSWORD;
789         }
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);
794
795         final byte[] applicationId;
796         final long sid;
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;
803                 return result;
804             }
805             result.gkResponse = weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken));
806             if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
807                 return result;
808             }
809             sid = GateKeeper.INVALID_SECURE_USER_ID;
810             applicationId = transformUnderWeaverSecret(pwdToken, result.gkResponse.getPayload());
811         } else {
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 */,
829                                 userId);
830                     } else {
831                         Log.w(TAG, "Fail to re-enroll user password for user " + userId);
832                         // continue the flow anyway
833                     }
834                 }
835             } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
836                 result.gkResponse = new VerifyCredentialResponse(response.getTimeout());
837                 return result;
838             } else  {
839                 result.gkResponse = VerifyCredentialResponse.ERROR;
840                 return result;
841             }
842             sid = sidFromPasswordHandle(pwd.passwordHandle);
843             applicationId = transformUnderSecdiscardable(pwdToken,
844                     loadSecdiscardable(handle, userId));
845         }
846
847         result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
848                 applicationId, sid, userId);
849
850         // Perform verifyChallenge to refresh auth tokens for GK if user password exists.
851         result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
852         return result;
853     }
854
855     /**
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.
859      */
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;
870                 return result;
871             }
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;
877                 return result;
878             }
879             secdiscardable = SyntheticPasswordCrypto.decrypt(response.getPayload(),
880                     PERSONALISATION_WEAVER_TOKEN, secdiscardable);
881         }
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;
891             }
892         } else {
893             result.gkResponse = VerifyCredentialResponse.ERROR;
894         }
895         return result;
896     }
897
898     private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
899             byte[] applicationId, long sid, int userId) {
900         byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
901         if (blob == null) {
902             return null;
903         }
904         final byte version = blob[0];
905         if (version != SYNTHETIC_PASSWORD_VERSION && version != SYNTHETIC_PASSWORD_VERSION_V1) {
906             throw new RuntimeException("Unknown blob version");
907         }
908         if (blob[1] != type) {
909             throw new RuntimeException("Invalid blob type");
910         }
911         final byte[] secret;
912         if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
913             secret = SyntheticPasswordCrypto.decryptBlobV1(getHandleName(handle),
914                     Arrays.copyOfRange(blob, 2, blob.length), applicationId);
915         } else {
916             secret = decryptSPBlob(getHandleName(handle),
917                 Arrays.copyOfRange(blob, 2, blob.length), applicationId);
918         }
919         if (secret == null) {
920             Log.e(TAG, "Fail to decrypt SP for user " + userId);
921             return null;
922         }
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);
927                 return null;
928             }
929             result.recreate(secret);
930         } else {
931             result.syntheticPassword = new String(secret);
932         }
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);
936         }
937         return result;
938     }
939
940     /**
941      * performs GK verifyChallenge and returns auth token, re-enrolling SP password handle
942      * if required.
943      *
944      * Normally performing verifyChallenge with an AuthenticationToken should always return
945      * RESPONSE_OK, since user authentication failures are detected earlier when trying to
946      * decrypt SP.
947      */
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;
954             return null;
955         }
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);
970                 } else {
971                     Log.w(TAG, "Fail to re-enroll SP handle for user " + userId);
972                     // Fall through, return existing handle
973                 }
974             }
975         } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
976             result = new VerifyCredentialResponse(response.getTimeout());
977         } else {
978             result = VerifyCredentialResponse.ERROR;
979         }
980         return result;
981     }
982
983     public boolean existsHandle(long handle, int userId) {
984         return hasState(SP_BLOB_NAME, handle, userId);
985     }
986
987     public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
988         destroySyntheticPassword(handle, userId);
989         destroyState(SECDISCARDABLE_NAME, handle, userId);
990     }
991
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);
996     }
997
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);
1003         }
1004     }
1005
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);
1012         return result;
1013     }
1014
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);
1021         return result;
1022     }
1023
1024     private byte[] createSecdiscardable(long handle, int userId) {
1025         byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
1026         saveSecdiscardable(handle, data, userId);
1027         return data;
1028     }
1029
1030     private void saveSecdiscardable(long handle, byte[] secdiscardable, int userId) {
1031         saveState(SECDISCARDABLE_NAME, secdiscardable, handle, userId);
1032     }
1033
1034     private byte[] loadSecdiscardable(long handle, int userId) {
1035         return loadState(SECDISCARDABLE_NAME, handle, userId);
1036     }
1037
1038     private boolean hasState(String stateName, long handle, int userId) {
1039         return !ArrayUtils.isEmpty(loadState(stateName, handle, userId));
1040     }
1041
1042     private byte[] loadState(String stateName, long handle, int userId) {
1043         return mStorage.readSyntheticPasswordState(userId, handle, stateName);
1044     }
1045
1046     private void saveState(String stateName, byte[] data, long handle, int userId) {
1047         mStorage.writeSyntheticPasswordState(userId, handle, stateName, data);
1048     }
1049
1050     private void destroyState(String stateName, long handle, int userId) {
1051         mStorage.deleteSyntheticPasswordState(userId, handle, stateName);
1052     }
1053
1054     protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
1055         return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, applicationId);
1056     }
1057
1058     protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
1059         return SyntheticPasswordCrypto.createBlob(blobKeyName, data, applicationId, sid);
1060     }
1061
1062     protected void destroySPBlobKey(String keyAlias) {
1063         SyntheticPasswordCrypto.destroyBlobKey(keyAlias);
1064     }
1065
1066     public static long generateHandle() {
1067         SecureRandom rng = new SecureRandom();
1068         long result;
1069         do {
1070             result = rng.nextLong();
1071         } while (result == DEFAULT_HANDLE);
1072         return result;
1073     }
1074
1075     private int fakeUid(int uid) {
1076         return 100000 + uid;
1077     }
1078
1079     protected static byte[] secureRandom(int length) {
1080         try {
1081             return SecureRandom.getInstance("SHA1PRNG").generateSeed(length);
1082         } catch (NoSuchAlgorithmException e) {
1083             e.printStackTrace();
1084             return null;
1085         }
1086     }
1087
1088     private String getHandleName(long handle) {
1089         return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
1090     }
1091
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);
1095     }
1096
1097     private byte[] passwordTokenToGkInput(byte[] token) {
1098         return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_USER_GK_AUTH, token);
1099     }
1100
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");
1105         }
1106         return Arrays.copyOf(key, mWeaverConfig.keySize);
1107     }
1108
1109     protected long sidFromPasswordHandle(byte[] handle) {
1110         return nativeSidFromPasswordHandle(handle);
1111     }
1112
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);
1115     }
1116
1117     native long nativeSidFromPasswordHandle(byte[] handle);
1118     native byte[] nativeScrypt(byte[] password, byte[] salt, int N, int r, int p, int outLen);
1119
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]);
1124         }
1125         return result;
1126     }
1127
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);
1132         }
1133         return result;
1134     }
1135
1136     final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
1137     public static String bytesToHex(byte[] bytes) {
1138         if (bytes == null) {
1139             return "null";
1140         }
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];
1146         }
1147         return new String(hexChars);
1148     }
1149 }