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 = 1; 105 private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0; 106 private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1; 107 108 // 256-bit synthetic password 109 private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8; 110 111 private static final int PASSWORD_SCRYPT_N = 11; 112 private static final int PASSWORD_SCRYPT_R = 3; 113 private static final int PASSWORD_SCRYPT_P = 1; 114 private static final int PASSWORD_SALT_LENGTH = 16; 115 private static final int PASSWORD_TOKEN_LENGTH = 32; 116 private static final String TAG = "SyntheticPasswordManager"; 117 118 private static final byte[] PERSONALISATION_SECDISCARDABLE = "secdiscardable-transform".getBytes(); 119 private static final byte[] PERSONALIZATION_KEY_STORE_PASSWORD = "keystore-password".getBytes(); 120 private static final byte[] PERSONALIZATION_USER_GK_AUTH = "user-gk-authentication".getBytes(); 121 private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes(); 122 private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes(); 123 private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes(); 124 private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes(); 125 private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes(); 126 private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes(); 127 private static final byte[] PERSONALISATION_WEAVER_TOKEN = "weaver-token".getBytes(); 128 129 static class AuthenticationResult { 130 public AuthenticationToken authToken; 131 public VerifyCredentialResponse gkResponse; 132 public int credentialType; 133 } 134 135 static class AuthenticationToken { 136 /* 137 * Here is the relationship between all three fields: 138 * P0 and P1 are two randomly-generated blocks. P1 is stored on disk but P0 is not. 139 * syntheticPassword = hash(P0 || P1) 140 * E0 = P0 encrypted under syntheticPassword, stored on disk. 141 */ 142 private @Nullable byte[] E0; 143 private @Nullable byte[] P1; 144 private @NonNull String syntheticPassword; 145 146 public String deriveKeyStorePassword() { 147 return bytesToHex(SyntheticPasswordCrypto.personalisedHash( 148 PERSONALIZATION_KEY_STORE_PASSWORD, syntheticPassword.getBytes())); 149 } 150 151 public byte[] deriveGkPassword() { 152 return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_SP_GK_AUTH, 153 syntheticPassword.getBytes()); 154 } 155 156 public byte[] deriveDiskEncryptionKey() { 157 return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_FBE_KEY, 158 syntheticPassword.getBytes()); 159 } 160 161 private void initialize(byte[] P0, byte[] P1) { 162 this.P1 = P1; 163 this.syntheticPassword = String.valueOf(HexEncoding.encode( 164 SyntheticPasswordCrypto.personalisedHash( 165 PERSONALIZATION_SP_SPLIT, P0, P1))); 166 this.E0 = SyntheticPasswordCrypto.encrypt(this.syntheticPassword.getBytes(), 167 PERSONALIZATION_E0, P0); 168 } 169 170 public void recreate(byte[] secret) { 171 initialize(secret, this.P1); 172 } 173 174 protected static AuthenticationToken create() { 175 AuthenticationToken result = new AuthenticationToken(); 176 result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH), 177 secureRandom(SYNTHETIC_PASSWORD_LENGTH)); 178 return result; 179 } 180 181 public byte[] computeP0() { 182 if (E0 == null) { 183 return null; 184 } 185 return SyntheticPasswordCrypto.decrypt(syntheticPassword.getBytes(), PERSONALIZATION_E0, 186 E0); 187 } 188 } 189 190 static class PasswordData { 191 byte scryptN; 192 byte scryptR; 193 byte scryptP; 194 public int passwordType; 195 byte[] salt; 196 // For GateKeeper-based credential, this is the password handle returned by GK, 197 // for weaver-based credential, this is empty. 198 public byte[] passwordHandle; 199 200 public static PasswordData create(int passwordType) { 201 PasswordData result = new PasswordData(); 202 result.scryptN = PASSWORD_SCRYPT_N; 203 result.scryptR = PASSWORD_SCRYPT_R; 204 result.scryptP = PASSWORD_SCRYPT_P; 205 result.passwordType = passwordType; 206 result.salt = secureRandom(PASSWORD_SALT_LENGTH); 207 return result; 208 } 209 210 public static PasswordData fromBytes(byte[] data) { 211 PasswordData result = new PasswordData(); 212 ByteBuffer buffer = ByteBuffer.allocate(data.length); 213 buffer.put(data, 0, data.length); 214 buffer.flip(); 215 result.passwordType = buffer.getInt(); 216 result.scryptN = buffer.get(); 217 result.scryptR = buffer.get(); 218 result.scryptP = buffer.get(); 219 int saltLen = buffer.getInt(); 220 result.salt = new byte[saltLen]; 221 buffer.get(result.salt); 222 int handleLen = buffer.getInt(); 223 if (handleLen > 0) { 224 result.passwordHandle = new byte[handleLen]; 225 buffer.get(result.passwordHandle); 226 } else { 227 result.passwordHandle = null; 228 } 229 return result; 230 } 231 232 public byte[] toBytes() { 233 234 ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES 235 + Integer.BYTES + salt.length + Integer.BYTES + 236 (passwordHandle != null ? passwordHandle.length : 0)); 237 buffer.putInt(passwordType); 238 buffer.put(scryptN); 239 buffer.put(scryptR); 240 buffer.put(scryptP); 241 buffer.putInt(salt.length); 242 buffer.put(salt); 243 if (passwordHandle != null && passwordHandle.length > 0) { 244 buffer.putInt(passwordHandle.length); 245 buffer.put(passwordHandle); 246 } else { 247 buffer.putInt(0); 248 } 249 return buffer.array(); 250 } 251 } 252 253 static class TokenData { 254 byte[] secdiscardableOnDisk; 255 byte[] weaverSecret; 256 byte[] aggregatedSecret; 257 } 258 259 private final Context mContext; 260 private LockSettingsStorage mStorage; 261 private IWeaver mWeaver; 262 private WeaverConfig mWeaverConfig; 263 264 private final UserManager mUserManager; 265 266 public SyntheticPasswordManager(Context context, LockSettingsStorage storage, 267 UserManager userManager) { 268 mContext = context; 269 mStorage = storage; 270 mUserManager = userManager; 271 } 272 273 @VisibleForTesting 274 protected IWeaver getWeaverService() throws RemoteException { 275 try { 276 return IWeaver.getService(); 277 } catch (NoSuchElementException e) { 278 Slog.i(TAG, "Device does not support weaver"); 279 return null; 280 } 281 } 282 283 public synchronized void initWeaverService() { 284 if (mWeaver != null) { 285 return; 286 } 287 try { 288 mWeaverConfig = null; 289 mWeaver = getWeaverService(); 290 if (mWeaver != null) { 291 mWeaver.getConfig((int status, WeaverConfig config) -> { 292 if (status == WeaverStatus.OK && config.slots > 0) { 293 mWeaverConfig = config; 294 } else { 295 Slog.e(TAG, "Failed to get weaver config, status " + status 296 + " slots: " + config.slots); 297 mWeaver = null; 298 } 299 }); 300 } 301 } catch (RemoteException e) { 302 Slog.e(TAG, "Failed to get weaver service", e); 303 } 304 } 305 306 private synchronized boolean isWeaverAvailable() { 307 if (mWeaver == null) { 308 //Re-initializing weaver in case there was a transient error preventing access to it. 309 initWeaverService(); 310 } 311 return mWeaver != null && mWeaverConfig.slots > 0; 312 } 313 314 /** 315 * Enroll the given key value pair into the specified weaver slot. if the given key is null, 316 * a default all-zero key is used. If the value is not specified, a fresh random secret is 317 * generated as the value. 318 * 319 * @return the value stored in the weaver slot 320 * @throws RemoteException 321 */ 322 private byte[] weaverEnroll(int slot, byte[] key, @Nullable byte[] value) 323 throws RemoteException { 324 if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) { 325 throw new RuntimeException("Invalid slot for weaver"); 326 } 327 if (key == null) { 328 key = new byte[mWeaverConfig.keySize]; 329 } else if (key.length != mWeaverConfig.keySize) { 330 throw new RuntimeException("Invalid key size for weaver"); 331 } 332 if (value == null) { 333 value = secureRandom(mWeaverConfig.valueSize); 334 } 335 int writeStatus = mWeaver.write(slot, toByteArrayList(key), toByteArrayList(value)); 336 if (writeStatus != WeaverStatus.OK) { 337 Log.e(TAG, "weaver write failed, slot: " + slot + " status: " + writeStatus); 338 return null; 339 } 340 return value; 341 } 342 343 /** 344 * Verify the supplied key against a weaver slot, returning a response indicating whether 345 * the verification is successful, throttled or failed. If successful, the bound secret 346 * is also returned. 347 * @throws RemoteException 348 */ 349 private VerifyCredentialResponse weaverVerify(int slot, byte[] key) throws RemoteException { 350 if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) { 351 throw new RuntimeException("Invalid slot for weaver"); 352 } 353 if (key == null) { 354 key = new byte[mWeaverConfig.keySize]; 355 } else if (key.length != mWeaverConfig.keySize) { 356 throw new RuntimeException("Invalid key size for weaver"); 357 } 358 final VerifyCredentialResponse[] response = new VerifyCredentialResponse[1]; 359 mWeaver.read(slot, toByteArrayList(key), (int status, WeaverReadResponse readResponse) -> { 360 switch (status) { 361 case WeaverReadStatus.OK: 362 response[0] = new VerifyCredentialResponse( 363 fromByteArrayList(readResponse.value)); 364 break; 365 case WeaverReadStatus.THROTTLE: 366 response[0] = new VerifyCredentialResponse(readResponse.timeout); 367 Log.e(TAG, "weaver read failed (THROTTLE), slot: " + slot); 368 break; 369 case WeaverReadStatus.INCORRECT_KEY: 370 if (readResponse.timeout == 0) { 371 response[0] = VerifyCredentialResponse.ERROR; 372 Log.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot); 373 } else { 374 response[0] = new VerifyCredentialResponse(readResponse.timeout); 375 Log.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: " + slot); 376 } 377 break; 378 case WeaverReadStatus.FAILED: 379 response[0] = VerifyCredentialResponse.ERROR; 380 Log.e(TAG, "weaver read failed (FAILED), slot: " + slot); 381 break; 382 default: 383 response[0] = VerifyCredentialResponse.ERROR; 384 Log.e(TAG, "weaver read unknown status " + status + ", slot: " + slot); 385 break; 386 } 387 }); 388 return response[0]; 389 } 390 391 public void removeUser(int userId) { 392 if (isWeaverAvailable()) { 393 for (long handle : mStorage.listSyntheticPasswordHandlesForUser(WEAVER_SLOT_NAME, 394 userId)) { 395 destroyWeaverSlot(handle, userId); 396 } 397 } 398 } 399 400 public int getCredentialType(long handle, int userId) { 401 byte[] passwordData = loadState(PASSWORD_DATA_NAME, handle, userId); 402 if (passwordData == null) { 403 Log.w(TAG, "getCredentialType: encountered empty password data for user " + userId); 404 return LockPatternUtils.CREDENTIAL_TYPE_NONE; 405 } 406 return PasswordData.fromBytes(passwordData).passwordType; 407 } 408 409 /** 410 * Initializing a new Authentication token, possibly from an existing credential and hash. 411 * 412 * The authentication token would bear a randomly-generated synthetic password. 413 * 414 * This method has the side effect of rebinding the SID of the given user to the 415 * newly-generated SP. 416 * 417 * If the existing credential hash is non-null, the existing SID mill be migrated so 418 * the synthetic password in the authentication token will produce the same SID 419 * (the corresponding synthetic password handle is persisted by SyntheticPasswordManager 420 * in a per-user data storage.) 421 * 422 * If the existing credential hash is null, it means the given user should have no SID so 423 * SyntheticPasswordManager will nuke any SP handle previously persisted. In this case, 424 * the supplied credential parameter is also ignored. 425 * 426 * Also saves the escrow information necessary to re-generate the synthetic password under 427 * an escrow scheme. This information can be removed with {@link #destroyEscrowData} if 428 * password escrow should be disabled completely on the given user. 429 * 430 */ 431 public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper, 432 byte[] hash, String credential, int userId) throws RemoteException { 433 AuthenticationToken result = AuthenticationToken.create(); 434 GateKeeperResponse response; 435 if (hash != null) { 436 response = gatekeeper.enroll(userId, hash, credential.getBytes(), 437 result.deriveGkPassword()); 438 if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) { 439 Log.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId); 440 clearSidForUser(userId); 441 } else { 442 saveSyntheticPasswordHandle(response.getPayload(), userId); 443 } 444 } else { 445 clearSidForUser(userId); 446 } 447 saveEscrowData(result, userId); 448 return result; 449 } 450 451 /** 452 * Enroll a new password handle and SID for the given synthetic password and persist it on disk. 453 * Used when adding password to previously-unsecured devices. 454 */ 455 public void newSidForUser(IGateKeeperService gatekeeper, AuthenticationToken authToken, 456 int userId) throws RemoteException { 457 GateKeeperResponse response = gatekeeper.enroll(userId, null, null, 458 authToken.deriveGkPassword()); 459 if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) { 460 Log.e(TAG, "Fail to create new SID for user " + userId); 461 return; 462 } 463 saveSyntheticPasswordHandle(response.getPayload(), userId); 464 } 465 466 // Nuke the SP handle (and as a result, its SID) for the given user. 467 public void clearSidForUser(int userId) { 468 destroyState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId); 469 } 470 471 public boolean hasSidForUser(int userId) { 472 return hasState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId); 473 } 474 475 // if null, it means there is no SID associated with the user 476 // This can happen if the user is migrated to SP but currently 477 // do not have a lockscreen password. 478 private byte[] loadSyntheticPasswordHandle(int userId) { 479 return loadState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId); 480 } 481 482 private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) { 483 saveState(SP_HANDLE_NAME, spHandle, DEFAULT_HANDLE, userId); 484 } 485 486 private boolean loadEscrowData(AuthenticationToken authToken, int userId) { 487 authToken.E0 = loadState(SP_E0_NAME, DEFAULT_HANDLE, userId); 488 authToken.P1 = loadState(SP_P1_NAME, DEFAULT_HANDLE, userId); 489 return authToken.E0 != null && authToken.P1 != null; 490 } 491 492 private void saveEscrowData(AuthenticationToken authToken, int userId) { 493 saveState(SP_E0_NAME, authToken.E0, DEFAULT_HANDLE, userId); 494 saveState(SP_P1_NAME, authToken.P1, DEFAULT_HANDLE, userId); 495 } 496 497 public boolean hasEscrowData(int userId) { 498 return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId) 499 && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId); 500 } 501 502 public void destroyEscrowData(int userId) { 503 destroyState(SP_E0_NAME, DEFAULT_HANDLE, userId); 504 destroyState(SP_P1_NAME, DEFAULT_HANDLE, userId); 505 } 506 507 private int loadWeaverSlot(long handle, int userId) { 508 final int LENGTH = Byte.BYTES + Integer.BYTES; 509 byte[] data = loadState(WEAVER_SLOT_NAME, handle, userId); 510 if (data == null || data.length != LENGTH) { 511 return INVALID_WEAVER_SLOT; 512 } 513 ByteBuffer buffer = ByteBuffer.allocate(LENGTH); 514 buffer.put(data, 0, data.length); 515 buffer.flip(); 516 if (buffer.get() != WEAVER_VERSION) { 517 Log.e(TAG, "Invalid weaver slot version of handle " + handle); 518 return INVALID_WEAVER_SLOT; 519 } 520 return buffer.getInt(); 521 } 522 523 private void saveWeaverSlot(int slot, long handle, int userId) { 524 ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES + Integer.BYTES); 525 buffer.put(WEAVER_VERSION); 526 buffer.putInt(slot); 527 saveState(WEAVER_SLOT_NAME, buffer.array(), handle, userId); 528 } 529 530 private void destroyWeaverSlot(long handle, int userId) { 531 int slot = loadWeaverSlot(handle, userId); 532 if (slot != INVALID_WEAVER_SLOT) { 533 try { 534 weaverEnroll(slot, null, null); 535 } catch (RemoteException e) { 536 Log.w(TAG, "Failed to destroy slot", e); 537 } 538 } 539 destroyState(WEAVER_SLOT_NAME, handle, userId); 540 } 541 542 private int getNextAvailableWeaverSlot() { 543 Map<Integer, List<Long>> slotHandles = mStorage.listSyntheticPasswordHandlesForAllUsers( 544 WEAVER_SLOT_NAME); 545 HashSet<Integer> slots = new HashSet<>(); 546 for (Map.Entry<Integer, List<Long>> entry : slotHandles.entrySet()) { 547 for (Long handle : entry.getValue()) { 548 int slot = loadWeaverSlot(handle, entry.getKey()); 549 slots.add(slot); 550 } 551 } 552 for (int i = 0; i < mWeaverConfig.slots; i++) { 553 if (!slots.contains(i)) { 554 return i; 555 } 556 } 557 throw new RuntimeException("Run out of weaver slots."); 558 } 559 560 /** 561 * Create a new password based SP blob based on the supplied authentication token, such that 562 * a future successful authentication with unwrapPasswordBasedSyntheticPassword() would result 563 * in the same authentication token. 564 * 565 * This method only creates SP blob wrapping around the given synthetic password and does not 566 * handle logic around SID or SP handle. The caller should separately ensure that the user's SID 567 * is consistent with the device state by calling other APIs in this class. 568 * 569 * @see #newSidForUser 570 * @see #clearSidForUser 571 */ 572 public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper, 573 String credential, int credentialType, AuthenticationToken authToken, 574 int requestedQuality, int userId) 575 throws RemoteException { 576 if (credential == null || credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { 577 credentialType = LockPatternUtils.CREDENTIAL_TYPE_NONE; 578 credential = DEFAULT_PASSWORD; 579 } 580 581 long handle = generateHandle(); 582 PasswordData pwd = PasswordData.create(credentialType); 583 byte[] pwdToken = computePasswordToken(credential, pwd); 584 final long sid; 585 final byte[] applicationId; 586 587 if (isWeaverAvailable()) { 588 // Weaver based user password 589 int weaverSlot = getNextAvailableWeaverSlot(); 590 byte[] weaverSecret = weaverEnroll(weaverSlot, passwordTokenToWeaverKey(pwdToken), null); 591 if (weaverSecret == null) { 592 Log.e(TAG, "Fail to enroll user password under weaver " + userId); 593 return DEFAULT_HANDLE; 594 } 595 saveWeaverSlot(weaverSlot, handle, userId); 596 synchronizeWeaverFrpPassword(pwd, requestedQuality, userId, weaverSlot); 597 598 pwd.passwordHandle = null; 599 sid = GateKeeper.INVALID_SECURE_USER_ID; 600 applicationId = transformUnderWeaverSecret(pwdToken, weaverSecret); 601 } else { 602 // In case GK enrollment leaves persistent state around (in RPMB), this will nuke them 603 // to prevent them from accumulating and causing problems. 604 gatekeeper.clearSecureUserId(fakeUid(userId)); 605 // GateKeeper based user password 606 GateKeeperResponse response = gatekeeper.enroll(fakeUid(userId), null, null, 607 passwordTokenToGkInput(pwdToken)); 608 if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) { 609 Log.e(TAG, "Fail to enroll user password when creating SP for user " + userId); 610 return DEFAULT_HANDLE; 611 } 612 pwd.passwordHandle = response.getPayload(); 613 sid = sidFromPasswordHandle(pwd.passwordHandle); 614 applicationId = transformUnderSecdiscardable(pwdToken, 615 createSecdiscardable(handle, userId)); 616 synchronizeFrpPassword(pwd, requestedQuality, userId); 617 } 618 saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId); 619 620 createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken, 621 applicationId, sid, userId); 622 return handle; 623 } 624 625 public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper, 626 String userCredential, int credentialType, 627 ICheckCredentialProgressCallback progressCallback) throws RemoteException { 628 PersistentData persistentData = mStorage.readPersistentDataBlock(); 629 if (persistentData.type == PersistentData.TYPE_SP) { 630 PasswordData pwd = PasswordData.fromBytes(persistentData.payload); 631 byte[] pwdToken = computePasswordToken(userCredential, pwd); 632 633 GateKeeperResponse response = gatekeeper.verifyChallenge(fakeUid(persistentData.userId), 634 0 /* challenge */, pwd.passwordHandle, passwordTokenToGkInput(pwdToken)); 635 return VerifyCredentialResponse.fromGateKeeperResponse(response); 636 } else if (persistentData.type == PersistentData.TYPE_SP_WEAVER) { 637 PasswordData pwd = PasswordData.fromBytes(persistentData.payload); 638 byte[] pwdToken = computePasswordToken(userCredential, pwd); 639 int weaverSlot = persistentData.userId; 640 641 return weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken)).stripPayload(); 642 } else { 643 Log.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is " 644 + persistentData.type); 645 return VerifyCredentialResponse.ERROR; 646 } 647 } 648 649 650 public void migrateFrpPasswordLocked(long handle, UserInfo userInfo, int requestedQuality) { 651 if (mStorage.getPersistentDataBlock() != null 652 && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) { 653 PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, 654 userInfo.id)); 655 if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { 656 int weaverSlot = loadWeaverSlot(handle, userInfo.id); 657 if (weaverSlot != INVALID_WEAVER_SLOT) { 658 synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot); 659 } else { 660 synchronizeFrpPassword(pwd, requestedQuality, userInfo.id); 661 } 662 } 663 } 664 } 665 666 private void synchronizeFrpPassword(PasswordData pwd, 667 int requestedQuality, int userId) { 668 if (mStorage.getPersistentDataBlock() != null 669 && LockPatternUtils.userOwnsFrpCredential(mContext, 670 mUserManager.getUserInfo(userId))) { 671 if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { 672 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality, 673 pwd.toBytes()); 674 } else { 675 mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, userId, 0, null); 676 } 677 } 678 } 679 680 private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId, 681 int weaverSlot) { 682 if (mStorage.getPersistentDataBlock() != null 683 && LockPatternUtils.userOwnsFrpCredential(mContext, 684 mUserManager.getUserInfo(userId))) { 685 if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { 686 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot, 687 requestedQuality, pwd.toBytes()); 688 } else { 689 mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, 0, 0, null); 690 } 691 } 692 } 693 694 private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>(); 695 696 public long createTokenBasedSyntheticPassword(byte[] token, int userId) { 697 long handle = generateHandle(); 698 if (!tokenMap.containsKey(userId)) { 699 tokenMap.put(userId, new ArrayMap<>()); 700 } 701 TokenData tokenData = new TokenData(); 702 final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH); 703 if (isWeaverAvailable()) { 704 tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize); 705 tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret, 706 PERSONALISATION_WEAVER_TOKEN, secdiscardable); 707 } else { 708 tokenData.secdiscardableOnDisk = secdiscardable; 709 tokenData.weaverSecret = null; 710 } 711 tokenData.aggregatedSecret = transformUnderSecdiscardable(token, secdiscardable); 712 713 tokenMap.get(userId).put(handle, tokenData); 714 return handle; 715 } 716 717 public Set<Long> getPendingTokensForUser(int userId) { 718 if (!tokenMap.containsKey(userId)) { 719 return Collections.emptySet(); 720 } 721 return tokenMap.get(userId).keySet(); 722 } 723 724 public boolean removePendingToken(long handle, int userId) { 725 if (!tokenMap.containsKey(userId)) { 726 return false; 727 } 728 return tokenMap.get(userId).remove(handle) != null; 729 } 730 731 public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken, 732 int userId) { 733 if (!tokenMap.containsKey(userId)) { 734 return false; 735 } 736 TokenData tokenData = tokenMap.get(userId).get(handle); 737 if (tokenData == null) { 738 return false; 739 } 740 if (!loadEscrowData(authToken, userId)) { 741 Log.w(TAG, "User is not escrowable"); 742 return false; 743 } 744 if (isWeaverAvailable()) { 745 int slot = getNextAvailableWeaverSlot(); 746 try { 747 weaverEnroll(slot, null, tokenData.weaverSecret); 748 } catch (RemoteException e) { 749 Log.e(TAG, "Failed to enroll weaver secret when activating token", e); 750 return false; 751 } 752 saveWeaverSlot(slot, handle, userId); 753 } 754 saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId); 755 createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken, 756 tokenData.aggregatedSecret, 0L, userId); 757 tokenMap.get(userId).remove(handle); 758 return true; 759 } 760 761 private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken, 762 byte[] applicationId, long sid, int userId) { 763 final byte[] secret; 764 if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) { 765 secret = authToken.computeP0(); 766 } else { 767 secret = authToken.syntheticPassword.getBytes(); 768 } 769 byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid); 770 byte[] blob = new byte[content.length + 1 + 1]; 771 blob[0] = SYNTHETIC_PASSWORD_VERSION; 772 blob[1] = type; 773 System.arraycopy(content, 0, blob, 2, content.length); 774 saveState(SP_BLOB_NAME, blob, handle, userId); 775 } 776 777 /** 778 * Decrypt a synthetic password by supplying the user credential and corresponding password 779 * blob handle generated previously. If the decryption is successful, initiate a GateKeeper 780 * verification to referesh the SID & Auth token maintained by the system. 781 * Note: the credential type is not validated here since there are call sites where the type is 782 * unknown. Caller might choose to validate it by examining AuthenticationResult.credentialType 783 */ 784 public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper, 785 long handle, String credential, int userId) throws RemoteException { 786 if (credential == null) { 787 credential = DEFAULT_PASSWORD; 788 } 789 AuthenticationResult result = new AuthenticationResult(); 790 PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId)); 791 result.credentialType = pwd.passwordType; 792 byte[] pwdToken = computePasswordToken(credential, pwd); 793 794 final byte[] applicationId; 795 int weaverSlot = loadWeaverSlot(handle, userId); 796 if (weaverSlot != INVALID_WEAVER_SLOT) { 797 // Weaver based user password 798 if (!isWeaverAvailable()) { 799 Log.e(TAG, "No weaver service to unwrap password based SP"); 800 result.gkResponse = VerifyCredentialResponse.ERROR; 801 return result; 802 } 803 result.gkResponse = weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken)); 804 if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { 805 return result; 806 } 807 applicationId = transformUnderWeaverSecret(pwdToken, result.gkResponse.getPayload()); 808 } else { 809 byte[] gkPwdToken = passwordTokenToGkInput(pwdToken); 810 GateKeeperResponse response = gatekeeper.verifyChallenge(fakeUid(userId), 0L, 811 pwd.passwordHandle, gkPwdToken); 812 int responseCode = response.getResponseCode(); 813 if (responseCode == GateKeeperResponse.RESPONSE_OK) { 814 result.gkResponse = VerifyCredentialResponse.OK; 815 if (response.getShouldReEnroll()) { 816 GateKeeperResponse reenrollResponse = gatekeeper.enroll(fakeUid(userId), 817 pwd.passwordHandle, gkPwdToken, gkPwdToken); 818 if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) { 819 pwd.passwordHandle = reenrollResponse.getPayload(); 820 saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId); 821 synchronizeFrpPassword(pwd, 822 pwd.passwordType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN 823 ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING 824 : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC 825 /* TODO(roosa): keep the same password quality */, 826 userId); 827 } else { 828 Log.w(TAG, "Fail to re-enroll user password for user " + userId); 829 // continue the flow anyway 830 } 831 } 832 } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) { 833 result.gkResponse = new VerifyCredentialResponse(response.getTimeout()); 834 return result; 835 } else { 836 result.gkResponse = VerifyCredentialResponse.ERROR; 837 return result; 838 } 839 applicationId = transformUnderSecdiscardable(pwdToken, 840 loadSecdiscardable(handle, userId)); 841 } 842 843 result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, 844 applicationId, userId); 845 846 // Perform verifyChallenge to refresh auth tokens for GK if user password exists. 847 result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId); 848 return result; 849 } 850 851 /** 852 * Decrypt a synthetic password by supplying an escrow token and corresponding token 853 * blob handle generated previously. If the decryption is successful, initiate a GateKeeper 854 * verification to referesh the SID & Auth token maintained by the system. 855 */ 856 public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword( 857 IGateKeeperService gatekeeper, long handle, byte[] token, int userId) 858 throws RemoteException { 859 AuthenticationResult result = new AuthenticationResult(); 860 byte[] secdiscardable = loadSecdiscardable(handle, userId); 861 int slotId = loadWeaverSlot(handle, userId); 862 if (slotId != INVALID_WEAVER_SLOT) { 863 if (!isWeaverAvailable()) { 864 Log.e(TAG, "No weaver service to unwrap token based SP"); 865 result.gkResponse = VerifyCredentialResponse.ERROR; 866 return result; 867 } 868 VerifyCredentialResponse response = weaverVerify(slotId, null); 869 if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK || 870 response.getPayload() == null) { 871 Log.e(TAG, "Failed to retrieve weaver secret when unwrapping token"); 872 result.gkResponse = VerifyCredentialResponse.ERROR; 873 return result; 874 } 875 secdiscardable = SyntheticPasswordCrypto.decrypt(response.getPayload(), 876 PERSONALISATION_WEAVER_TOKEN, secdiscardable); 877 } 878 byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable); 879 result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, 880 applicationId, userId); 881 if (result.authToken != null) { 882 result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId); 883 if (result.gkResponse == null) { 884 // The user currently has no password. return OK with null payload so null 885 // is propagated to unlockUser() 886 result.gkResponse = VerifyCredentialResponse.OK; 887 } 888 } else { 889 result.gkResponse = VerifyCredentialResponse.ERROR; 890 } 891 return result; 892 } 893 894 private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type, 895 byte[] applicationId, int userId) { 896 byte[] blob = loadState(SP_BLOB_NAME, handle, userId); 897 if (blob == null) { 898 return null; 899 } 900 if (blob[0] != SYNTHETIC_PASSWORD_VERSION) { 901 throw new RuntimeException("Unknown blob version"); 902 } 903 if (blob[1] != type) { 904 throw new RuntimeException("Invalid blob type"); 905 } 906 byte[] secret = decryptSPBlob(getHandleName(handle), 907 Arrays.copyOfRange(blob, 2, blob.length), applicationId); 908 if (secret == null) { 909 Log.e(TAG, "Fail to decrypt SP for user " + userId); 910 return null; 911 } 912 AuthenticationToken result = new AuthenticationToken(); 913 if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) { 914 if (!loadEscrowData(result, userId)) { 915 Log.e(TAG, "User is not escrowable: " + userId); 916 return null; 917 } 918 result.recreate(secret); 919 } else { 920 result.syntheticPassword = new String(secret); 921 } 922 return result; 923 } 924 925 /** 926 * performs GK verifyChallenge and returns auth token, re-enrolling SP password handle 927 * if required. 928 * 929 * Normally performing verifyChallenge with an AuthenticationToken should always return 930 * RESPONSE_OK, since user authentication failures are detected earlier when trying to 931 * decrypt SP. 932 */ 933 public @Nullable VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper, 934 @NonNull AuthenticationToken auth, long challenge, int userId) throws RemoteException { 935 byte[] spHandle = loadSyntheticPasswordHandle(userId); 936 if (spHandle == null) { 937 // There is no password handle associated with the given user, i.e. the user is not 938 // secured by lockscreen and has no SID, so just return here; 939 return null; 940 } 941 VerifyCredentialResponse result; 942 GateKeeperResponse response = gatekeeper.verifyChallenge(userId, challenge, 943 spHandle, auth.deriveGkPassword()); 944 int responseCode = response.getResponseCode(); 945 if (responseCode == GateKeeperResponse.RESPONSE_OK) { 946 result = new VerifyCredentialResponse(response.getPayload()); 947 if (response.getShouldReEnroll()) { 948 response = gatekeeper.enroll(userId, spHandle, 949 spHandle, auth.deriveGkPassword()); 950 if (response.getResponseCode() == GateKeeperResponse.RESPONSE_OK) { 951 spHandle = response.getPayload(); 952 saveSyntheticPasswordHandle(spHandle, userId); 953 // Call self again to re-verify with updated handle 954 return verifyChallenge(gatekeeper, auth, challenge, userId); 955 } else { 956 Log.w(TAG, "Fail to re-enroll SP handle for user " + userId); 957 // Fall through, return existing handle 958 } 959 } 960 } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) { 961 result = new VerifyCredentialResponse(response.getTimeout()); 962 } else { 963 result = VerifyCredentialResponse.ERROR; 964 } 965 return result; 966 } 967 968 public boolean existsHandle(long handle, int userId) { 969 return hasState(SP_BLOB_NAME, handle, userId); 970 } 971 972 public void destroyTokenBasedSyntheticPassword(long handle, int userId) { 973 destroySyntheticPassword(handle, userId); 974 destroyState(SECDISCARDABLE_NAME, handle, userId); 975 } 976 977 public void destroyPasswordBasedSyntheticPassword(long handle, int userId) { 978 destroySyntheticPassword(handle, userId); 979 destroyState(SECDISCARDABLE_NAME, handle, userId); 980 destroyState(PASSWORD_DATA_NAME, handle, userId); 981 } 982 983 private void destroySyntheticPassword(long handle, int userId) { 984 destroyState(SP_BLOB_NAME, handle, userId); 985 destroySPBlobKey(getHandleName(handle)); 986 if (hasState(WEAVER_SLOT_NAME, handle, userId)) { 987 destroyWeaverSlot(handle, userId); 988 } 989 } 990 991 private byte[] transformUnderWeaverSecret(byte[] data, byte[] secret) { 992 byte[] weaverSecret = SyntheticPasswordCrypto.personalisedHash( 993 PERSONALISATION_WEAVER_PASSWORD, secret); 994 byte[] result = new byte[data.length + weaverSecret.length]; 995 System.arraycopy(data, 0, result, 0, data.length); 996 System.arraycopy(weaverSecret, 0, result, data.length, weaverSecret.length); 997 return result; 998 } 999 1000 private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) { 1001 byte[] secdiscardable = SyntheticPasswordCrypto.personalisedHash( 1002 PERSONALISATION_SECDISCARDABLE, rawSecdiscardable); 1003 byte[] result = new byte[data.length + secdiscardable.length]; 1004 System.arraycopy(data, 0, result, 0, data.length); 1005 System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length); 1006 return result; 1007 } 1008 1009 private byte[] createSecdiscardable(long handle, int userId) { 1010 byte[] data = secureRandom(SECDISCARDABLE_LENGTH); 1011 saveSecdiscardable(handle, data, userId); 1012 return data; 1013 } 1014 1015 private void saveSecdiscardable(long handle, byte[] secdiscardable, int userId) { 1016 saveState(SECDISCARDABLE_NAME, secdiscardable, handle, userId); 1017 } 1018 1019 private byte[] loadSecdiscardable(long handle, int userId) { 1020 return loadState(SECDISCARDABLE_NAME, handle, userId); 1021 } 1022 1023 private boolean hasState(String stateName, long handle, int userId) { 1024 return !ArrayUtils.isEmpty(loadState(stateName, handle, userId)); 1025 } 1026 1027 private byte[] loadState(String stateName, long handle, int userId) { 1028 return mStorage.readSyntheticPasswordState(userId, handle, stateName); 1029 } 1030 1031 private void saveState(String stateName, byte[] data, long handle, int userId) { 1032 mStorage.writeSyntheticPasswordState(userId, handle, stateName, data); 1033 } 1034 1035 private void destroyState(String stateName, long handle, int userId) { 1036 mStorage.deleteSyntheticPasswordState(userId, handle, stateName); 1037 } 1038 1039 protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) { 1040 return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, applicationId); 1041 } 1042 1043 protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) { 1044 return SyntheticPasswordCrypto.createBlob(blobKeyName, data, applicationId, sid); 1045 } 1046 1047 protected void destroySPBlobKey(String keyAlias) { 1048 SyntheticPasswordCrypto.destroyBlobKey(keyAlias); 1049 } 1050 1051 public static long generateHandle() { 1052 SecureRandom rng = new SecureRandom(); 1053 long result; 1054 do { 1055 result = rng.nextLong(); 1056 } while (result == DEFAULT_HANDLE); 1057 return result; 1058 } 1059 1060 private int fakeUid(int uid) { 1061 return 100000 + uid; 1062 } 1063 1064 protected static byte[] secureRandom(int length) { 1065 try { 1066 return SecureRandom.getInstance("SHA1PRNG").generateSeed(length); 1067 } catch (NoSuchAlgorithmException e) { 1068 e.printStackTrace(); 1069 return null; 1070 } 1071 } 1072 1073 private String getHandleName(long handle) { 1074 return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle); 1075 } 1076 1077 private byte[] computePasswordToken(String password, PasswordData data) { 1078 return scrypt(password, data.salt, 1 << data.scryptN, 1 << data.scryptR, 1 << data.scryptP, 1079 PASSWORD_TOKEN_LENGTH); 1080 } 1081 1082 private byte[] passwordTokenToGkInput(byte[] token) { 1083 return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_USER_GK_AUTH, token); 1084 } 1085 1086 private byte[] passwordTokenToWeaverKey(byte[] token) { 1087 byte[] key = SyntheticPasswordCrypto.personalisedHash(PERSONALISATION_WEAVER_KEY, token); 1088 if (key.length < mWeaverConfig.keySize) { 1089 throw new RuntimeException("weaver key length too small"); 1090 } 1091 return Arrays.copyOf(key, mWeaverConfig.keySize); 1092 } 1093 1094 protected long sidFromPasswordHandle(byte[] handle) { 1095 return nativeSidFromPasswordHandle(handle); 1096 } 1097 1098 protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) { 1099 return nativeScrypt(password.getBytes(), salt, N, r, p, outLen); 1100 } 1101 1102 native long nativeSidFromPasswordHandle(byte[] handle); 1103 native byte[] nativeScrypt(byte[] password, byte[] salt, int N, int r, int p, int outLen); 1104 1105 protected static ArrayList<Byte> toByteArrayList(byte[] data) { 1106 ArrayList<Byte> result = new ArrayList<Byte>(data.length); 1107 for (int i = 0; i < data.length; i++) { 1108 result.add(data[i]); 1109 } 1110 return result; 1111 } 1112 1113 protected static byte[] fromByteArrayList(ArrayList<Byte> data) { 1114 byte[] result = new byte[data.size()]; 1115 for (int i = 0; i < data.size(); i++) { 1116 result[i] = data.get(i); 1117 } 1118 return result; 1119 } 1120 1121 final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); 1122 public static String bytesToHex(byte[] bytes) { 1123 if (bytes == null) { 1124 return "null"; 1125 } 1126 char[] hexChars = new char[bytes.length * 2]; 1127 for ( int j = 0; j < bytes.length; j++ ) { 1128 int v = bytes[j] & 0xFF; 1129 hexChars[j * 2] = hexArray[v >>> 4]; 1130 hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 1131 } 1132 return new String(hexChars); 1133 } 1134 } 1135