KeyStore.java revision e35d49f0d2853b79470ec890113bf4dcef03ab88
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.security; 18 19import android.app.ActivityThread; 20import android.app.Application; 21import android.app.KeyguardManager; 22import com.android.org.conscrypt.NativeConstants; 23 24import android.content.Context; 25import android.hardware.fingerprint.FingerprintManager; 26import android.os.Binder; 27import android.os.IBinder; 28import android.os.Process; 29import android.os.RemoteException; 30import android.os.ServiceManager; 31import android.os.UserHandle; 32import android.security.keymaster.ExportResult; 33import android.security.keymaster.KeyCharacteristics; 34import android.security.keymaster.KeymasterArguments; 35import android.security.keymaster.KeymasterBlob; 36import android.security.keymaster.KeymasterDefs; 37import android.security.keymaster.OperationResult; 38import android.security.keystore.KeyExpiredException; 39import android.security.keystore.KeyNotYetValidException; 40import android.security.keystore.KeyPermanentlyInvalidatedException; 41import android.security.keystore.KeyProperties; 42import android.security.keystore.UserNotAuthenticatedException; 43import android.util.Log; 44 45import java.security.InvalidKeyException; 46import java.util.List; 47import java.util.Locale; 48 49/** 50 * @hide This should not be made public in its present form because it 51 * assumes that private and secret key bytes are available and would 52 * preclude the use of hardware crypto. 53 */ 54public class KeyStore { 55 private static final String TAG = "KeyStore"; 56 57 // ResponseCodes 58 public static final int NO_ERROR = 1; 59 public static final int LOCKED = 2; 60 public static final int UNINITIALIZED = 3; 61 public static final int SYSTEM_ERROR = 4; 62 public static final int PROTOCOL_ERROR = 5; 63 public static final int PERMISSION_DENIED = 6; 64 public static final int KEY_NOT_FOUND = 7; 65 public static final int VALUE_CORRUPTED = 8; 66 public static final int UNDEFINED_ACTION = 9; 67 public static final int WRONG_PASSWORD = 10; 68 69 /** 70 * Per operation authentication is needed before this operation is valid. 71 * This is returned from {@link #begin} when begin succeeds but the operation uses 72 * per-operation authentication and must authenticate before calling {@link #update} or 73 * {@link #finish}. 74 */ 75 public static final int OP_AUTH_NEEDED = 15; 76 77 // Used for UID field to indicate the calling UID. 78 public static final int UID_SELF = -1; 79 80 // Flags for "put" "import" and "generate" 81 public static final int FLAG_NONE = 0; 82 83 /** 84 * Indicates that this key (or key pair) must be encrypted at rest. This will protect the key 85 * (or key pair) with the secure lock screen credential (e.g., password, PIN, or pattern). 86 * 87 * <p>Note that this requires that the secure lock screen (e.g., password, PIN, pattern) is set 88 * up, otherwise key (or key pair) generation or import will fail. Moreover, this key (or key 89 * pair) will be deleted when the secure lock screen is disabled or reset (e.g., by the user or 90 * a Device Administrator). Finally, this key (or key pair) cannot be used until the user 91 * unlocks the secure lock screen after boot. 92 * 93 * @see KeyguardManager#isDeviceSecure() 94 */ 95 public static final int FLAG_ENCRYPTED = 1; 96 97 // States 98 public enum State { UNLOCKED, LOCKED, UNINITIALIZED }; 99 100 private int mError = NO_ERROR; 101 102 private final IKeystoreService mBinder; 103 private final Context mContext; 104 105 private IBinder mToken; 106 107 private KeyStore(IKeystoreService binder) { 108 mBinder = binder; 109 mContext = getApplicationContext(); 110 } 111 112 public static Context getApplicationContext() { 113 ActivityThread activityThread = ActivityThread.currentActivityThread(); 114 if (activityThread == null) { 115 throw new IllegalStateException( 116 "Failed to obtain application Context: no ActivityThread"); 117 } 118 Application application = activityThread.getApplication(); 119 if (application == null) { 120 throw new IllegalStateException( 121 "Failed to obtain application Context: no Application"); 122 } 123 return application; 124 } 125 126 public static KeyStore getInstance() { 127 IKeystoreService keystore = IKeystoreService.Stub.asInterface(ServiceManager 128 .getService("android.security.keystore")); 129 return new KeyStore(keystore); 130 } 131 132 private synchronized IBinder getToken() { 133 if (mToken == null) { 134 mToken = new Binder(); 135 } 136 return mToken; 137 } 138 139 public static int getKeyTypeForAlgorithm(@KeyProperties.KeyAlgorithmEnum String keyType) { 140 if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyType)) { 141 return NativeConstants.EVP_PKEY_RSA; 142 } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyType)) { 143 return NativeConstants.EVP_PKEY_EC; 144 } else { 145 return -1; 146 } 147 } 148 149 public State state(int userId) { 150 final int ret; 151 try { 152 ret = mBinder.getState(userId); 153 } catch (RemoteException e) { 154 Log.w(TAG, "Cannot connect to keystore", e); 155 throw new AssertionError(e); 156 } 157 158 switch (ret) { 159 case NO_ERROR: return State.UNLOCKED; 160 case LOCKED: return State.LOCKED; 161 case UNINITIALIZED: return State.UNINITIALIZED; 162 default: throw new AssertionError(mError); 163 } 164 } 165 166 public State state() { 167 return state(UserHandle.myUserId()); 168 } 169 170 public boolean isUnlocked() { 171 return state() == State.UNLOCKED; 172 } 173 174 public byte[] get(String key) { 175 try { 176 return mBinder.get(key); 177 } catch (RemoteException e) { 178 Log.w(TAG, "Cannot connect to keystore", e); 179 return null; 180 } 181 } 182 183 public boolean put(String key, byte[] value, int uid, int flags) { 184 try { 185 return mBinder.insert(key, value, uid, flags) == NO_ERROR; 186 } catch (RemoteException e) { 187 Log.w(TAG, "Cannot connect to keystore", e); 188 return false; 189 } 190 } 191 192 public boolean delete(String key, int uid) { 193 try { 194 return mBinder.del(key, uid) == NO_ERROR; 195 } catch (RemoteException e) { 196 Log.w(TAG, "Cannot connect to keystore", e); 197 return false; 198 } 199 } 200 201 public boolean delete(String key) { 202 return delete(key, UID_SELF); 203 } 204 205 public boolean contains(String key, int uid) { 206 try { 207 return mBinder.exist(key, uid) == NO_ERROR; 208 } catch (RemoteException e) { 209 Log.w(TAG, "Cannot connect to keystore", e); 210 return false; 211 } 212 } 213 214 public boolean contains(String key) { 215 return contains(key, UID_SELF); 216 } 217 218 /** 219 * List all entries in the keystore for {@code uid} starting with {@code prefix}. 220 */ 221 public String[] list(String prefix, int uid) { 222 try { 223 return mBinder.list(prefix, uid); 224 } catch (RemoteException e) { 225 Log.w(TAG, "Cannot connect to keystore", e); 226 return null; 227 } 228 } 229 230 public String[] list(String prefix) { 231 return list(prefix, UID_SELF); 232 } 233 234 public String[] saw(String prefix, int uid) { 235 return list(prefix, uid); 236 } 237 238 public String[] saw(String prefix) { 239 return saw(prefix, UID_SELF); 240 } 241 242 public boolean reset() { 243 try { 244 return mBinder.reset() == NO_ERROR; 245 } catch (RemoteException e) { 246 Log.w(TAG, "Cannot connect to keystore", e); 247 return false; 248 } 249 } 250 251 /** 252 * Attempt to lock the keystore for {@code user}. 253 * 254 * @param user Android user to lock. 255 * @return whether {@code user}'s keystore was locked. 256 */ 257 public boolean lock(int userId) { 258 try { 259 return mBinder.lock(userId) == NO_ERROR; 260 } catch (RemoteException e) { 261 Log.w(TAG, "Cannot connect to keystore", e); 262 return false; 263 } 264 } 265 266 public boolean lock() { 267 return lock(UserHandle.myUserId()); 268 } 269 270 /** 271 * Attempt to unlock the keystore for {@code user} with the password {@code password}. 272 * This is required before keystore entries created with FLAG_ENCRYPTED can be accessed or 273 * created. 274 * 275 * @param user Android user ID to operate on 276 * @param password user's keystore password. Should be the most recent value passed to 277 * {@link #onUserPasswordChanged} for the user. 278 * 279 * @return whether the keystore was unlocked. 280 */ 281 public boolean unlock(int userId, String password) { 282 try { 283 mError = mBinder.unlock(userId, password); 284 return mError == NO_ERROR; 285 } catch (RemoteException e) { 286 Log.w(TAG, "Cannot connect to keystore", e); 287 return false; 288 } 289 } 290 291 public boolean unlock(String password) { 292 return unlock(UserHandle.getUserId(Process.myUid()), password); 293 } 294 295 /** 296 * Check if the keystore for {@code userId} is empty. 297 */ 298 public boolean isEmpty(int userId) { 299 try { 300 return mBinder.isEmpty(userId) != 0; 301 } catch (RemoteException e) { 302 Log.w(TAG, "Cannot connect to keystore", e); 303 return false; 304 } 305 } 306 307 public boolean isEmpty() { 308 return isEmpty(UserHandle.myUserId()); 309 } 310 311 public boolean generate(String key, int uid, int keyType, int keySize, int flags, 312 byte[][] args) { 313 try { 314 return mBinder.generate(key, uid, keyType, keySize, flags, 315 new KeystoreArguments(args)) == NO_ERROR; 316 } catch (RemoteException e) { 317 Log.w(TAG, "Cannot connect to keystore", e); 318 return false; 319 } 320 } 321 322 public boolean importKey(String keyName, byte[] key, int uid, int flags) { 323 try { 324 return mBinder.import_key(keyName, key, uid, flags) == NO_ERROR; 325 } catch (RemoteException e) { 326 Log.w(TAG, "Cannot connect to keystore", e); 327 return false; 328 } 329 } 330 331 public byte[] getPubkey(String key) { 332 try { 333 return mBinder.get_pubkey(key); 334 } catch (RemoteException e) { 335 Log.w(TAG, "Cannot connect to keystore", e); 336 return null; 337 } 338 } 339 340 public boolean delKey(String key, int uid) { 341 return delete(key, uid); 342 } 343 344 public boolean delKey(String key) { 345 return delKey(key, UID_SELF); 346 } 347 348 public byte[] sign(String key, byte[] data) { 349 try { 350 return mBinder.sign(key, data); 351 } catch (RemoteException e) { 352 Log.w(TAG, "Cannot connect to keystore", e); 353 return null; 354 } 355 } 356 357 public boolean verify(String key, byte[] data, byte[] signature) { 358 try { 359 return mBinder.verify(key, data, signature) == NO_ERROR; 360 } catch (RemoteException e) { 361 Log.w(TAG, "Cannot connect to keystore", e); 362 return false; 363 } 364 } 365 366 public boolean grant(String key, int uid) { 367 try { 368 return mBinder.grant(key, uid) == NO_ERROR; 369 } catch (RemoteException e) { 370 Log.w(TAG, "Cannot connect to keystore", e); 371 return false; 372 } 373 } 374 375 public boolean ungrant(String key, int uid) { 376 try { 377 return mBinder.ungrant(key, uid) == NO_ERROR; 378 } catch (RemoteException e) { 379 Log.w(TAG, "Cannot connect to keystore", e); 380 return false; 381 } 382 } 383 384 /** 385 * Returns the last modification time of the key in milliseconds since the 386 * epoch. Will return -1L if the key could not be found or other error. 387 */ 388 public long getmtime(String key) { 389 try { 390 final long millis = mBinder.getmtime(key); 391 if (millis == -1L) { 392 return -1L; 393 } 394 395 return millis * 1000L; 396 } catch (RemoteException e) { 397 Log.w(TAG, "Cannot connect to keystore", e); 398 return -1L; 399 } 400 } 401 402 public boolean duplicate(String srcKey, int srcUid, String destKey, int destUid) { 403 try { 404 return mBinder.duplicate(srcKey, srcUid, destKey, destUid) == NO_ERROR; 405 } catch (RemoteException e) { 406 Log.w(TAG, "Cannot connect to keystore", e); 407 return false; 408 } 409 } 410 411 // TODO remove this when it's removed from Settings 412 public boolean isHardwareBacked() { 413 return isHardwareBacked("RSA"); 414 } 415 416 public boolean isHardwareBacked(String keyType) { 417 try { 418 return mBinder.is_hardware_backed(keyType.toUpperCase(Locale.US)) == NO_ERROR; 419 } catch (RemoteException e) { 420 Log.w(TAG, "Cannot connect to keystore", e); 421 return false; 422 } 423 } 424 425 public boolean clearUid(int uid) { 426 try { 427 return mBinder.clear_uid(uid) == NO_ERROR; 428 } catch (RemoteException e) { 429 Log.w(TAG, "Cannot connect to keystore", e); 430 return false; 431 } 432 } 433 434 public int getLastError() { 435 return mError; 436 } 437 438 public boolean addRngEntropy(byte[] data) { 439 try { 440 return mBinder.addRngEntropy(data) == NO_ERROR; 441 } catch (RemoteException e) { 442 Log.w(TAG, "Cannot connect to keystore", e); 443 return false; 444 } 445 } 446 447 public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid, 448 int flags, KeyCharacteristics outCharacteristics) { 449 try { 450 return mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics); 451 } catch (RemoteException e) { 452 Log.w(TAG, "Cannot connect to keystore", e); 453 return SYSTEM_ERROR; 454 } 455 } 456 457 public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int flags, 458 KeyCharacteristics outCharacteristics) { 459 return generateKey(alias, args, entropy, UID_SELF, flags, outCharacteristics); 460 } 461 462 public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId, 463 KeyCharacteristics outCharacteristics) { 464 try { 465 return mBinder.getKeyCharacteristics(alias, clientId, appId, outCharacteristics); 466 } catch (RemoteException e) { 467 Log.w(TAG, "Cannot connect to keystore", e); 468 return SYSTEM_ERROR; 469 } 470 } 471 472 public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData, 473 int uid, int flags, KeyCharacteristics outCharacteristics) { 474 try { 475 return mBinder.importKey(alias, args, format, keyData, uid, flags, 476 outCharacteristics); 477 } catch (RemoteException e) { 478 Log.w(TAG, "Cannot connect to keystore", e); 479 return SYSTEM_ERROR; 480 } 481 } 482 483 public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData, 484 int flags, KeyCharacteristics outCharacteristics) { 485 return importKey(alias, args, format, keyData, UID_SELF, flags, outCharacteristics); 486 } 487 488 public ExportResult exportKey(String alias, int format, KeymasterBlob clientId, 489 KeymasterBlob appId) { 490 try { 491 return mBinder.exportKey(alias, format, clientId, appId); 492 } catch (RemoteException e) { 493 Log.w(TAG, "Cannot connect to keystore", e); 494 return null; 495 } 496 } 497 498 public OperationResult begin(String alias, int purpose, boolean pruneable, 499 KeymasterArguments args, byte[] entropy, KeymasterArguments outArgs) { 500 try { 501 return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, outArgs); 502 } catch (RemoteException e) { 503 Log.w(TAG, "Cannot connect to keystore", e); 504 return null; 505 } 506 } 507 508 public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) { 509 try { 510 return mBinder.update(token, arguments, input); 511 } catch (RemoteException e) { 512 Log.w(TAG, "Cannot connect to keystore", e); 513 return null; 514 } 515 } 516 517 public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature) { 518 try { 519 return mBinder.finish(token, arguments, signature); 520 } catch (RemoteException e) { 521 Log.w(TAG, "Cannot connect to keystore", e); 522 return null; 523 } 524 } 525 526 public int abort(IBinder token) { 527 try { 528 return mBinder.abort(token); 529 } catch (RemoteException e) { 530 Log.w(TAG, "Cannot connect to keystore", e); 531 return SYSTEM_ERROR; 532 } 533 } 534 535 /** 536 * Check if the operation referenced by {@code token} is currently authorized. 537 * 538 * @param token An operation token returned by a call to 539 * {@link #begin(String, int, boolean, KeymasterArguments, byte[], KeymasterArguments) begin}. 540 */ 541 public boolean isOperationAuthorized(IBinder token) { 542 try { 543 return mBinder.isOperationAuthorized(token); 544 } catch (RemoteException e) { 545 Log.w(TAG, "Cannot connect to keystore", e); 546 return false; 547 } 548 } 549 550 /** 551 * Add an authentication record to the keystore authorization table. 552 * 553 * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster. 554 * @return {@code KeyStore.NO_ERROR} on success, otherwise an error value corresponding to 555 * a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode. 556 */ 557 public int addAuthToken(byte[] authToken) { 558 try { 559 return mBinder.addAuthToken(authToken); 560 } catch (RemoteException e) { 561 Log.w(TAG, "Cannot connect to keystore", e); 562 return SYSTEM_ERROR; 563 } 564 } 565 566 /** 567 * Notify keystore that a user's password has changed. 568 * 569 * @param userId the user whose password changed. 570 * @param newPassword the new password or "" if the password was removed. 571 */ 572 public boolean onUserPasswordChanged(int userId, String newPassword) { 573 // Parcel.cpp doesn't support deserializing null strings and treats them as "". Make that 574 // explicit here. 575 if (newPassword == null) { 576 newPassword = ""; 577 } 578 try { 579 return mBinder.onUserPasswordChanged(userId, newPassword) == NO_ERROR; 580 } catch (RemoteException e) { 581 Log.w(TAG, "Cannot connect to keystore", e); 582 return false; 583 } 584 } 585 586 /** 587 * Notify keystore that a user was added. 588 * 589 * @param userId the new user. 590 * @param parentId the parent of the new user, or -1 if the user has no parent. If parentId is 591 * specified then the new user's keystore will be intialized with the same secure lockscreen 592 * password as the parent. 593 */ 594 public void onUserAdded(int userId, int parentId) { 595 try { 596 mBinder.onUserAdded(userId, parentId); 597 } catch (RemoteException e) { 598 Log.w(TAG, "Cannot connect to keystore", e); 599 } 600 } 601 602 /** 603 * Notify keystore that a user was added. 604 * 605 * @param userId the new user. 606 */ 607 public void onUserAdded(int userId) { 608 onUserAdded(userId, -1); 609 } 610 611 /** 612 * Notify keystore that a user was removed. 613 * 614 * @param userId the removed user. 615 */ 616 public void onUserRemoved(int userId) { 617 try { 618 mBinder.onUserRemoved(userId); 619 } catch (RemoteException e) { 620 Log.w(TAG, "Cannot connect to keystore", e); 621 } 622 } 623 624 public boolean onUserPasswordChanged(String newPassword) { 625 return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword); 626 } 627 628 /** 629 * Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error 630 * code. 631 */ 632 public static KeyStoreException getKeyStoreException(int errorCode) { 633 if (errorCode > 0) { 634 // KeyStore layer error 635 switch (errorCode) { 636 case NO_ERROR: 637 return new KeyStoreException(errorCode, "OK"); 638 case LOCKED: 639 return new KeyStoreException(errorCode, "User authentication required"); 640 case UNINITIALIZED: 641 return new KeyStoreException(errorCode, "Keystore not initialized"); 642 case SYSTEM_ERROR: 643 return new KeyStoreException(errorCode, "System error"); 644 case PERMISSION_DENIED: 645 return new KeyStoreException(errorCode, "Permission denied"); 646 case KEY_NOT_FOUND: 647 return new KeyStoreException(errorCode, "Key not found"); 648 case VALUE_CORRUPTED: 649 return new KeyStoreException(errorCode, "Key blob corrupted"); 650 case OP_AUTH_NEEDED: 651 return new KeyStoreException(errorCode, "Operation requires authorization"); 652 default: 653 return new KeyStoreException(errorCode, String.valueOf(errorCode)); 654 } 655 } else { 656 // Keymaster layer error 657 switch (errorCode) { 658 case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT: 659 // The name of this parameter significantly differs between Keymaster and 660 // framework APIs. Use the framework wording to make life easier for developers. 661 return new KeyStoreException(errorCode, 662 "Invalid user authentication validity duration"); 663 default: 664 return new KeyStoreException(errorCode, 665 KeymasterDefs.getErrorMessage(errorCode)); 666 } 667 } 668 } 669 670 /** 671 * Returns an {@link InvalidKeyException} corresponding to the provided 672 * {@link KeyStoreException}. 673 */ 674 public InvalidKeyException getInvalidKeyException( 675 String keystoreKeyAlias, KeyStoreException e) { 676 switch (e.getErrorCode()) { 677 case LOCKED: 678 return new UserNotAuthenticatedException(); 679 case KeymasterDefs.KM_ERROR_KEY_EXPIRED: 680 return new KeyExpiredException(); 681 case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID: 682 return new KeyNotYetValidException(); 683 case KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED: 684 case OP_AUTH_NEEDED: 685 { 686 // We now need to determine whether the key/operation can become usable if user 687 // authentication is performed, or whether it can never become usable again. 688 // User authentication requirements are contained in the key's characteristics. We 689 // need to check whether these requirements can be be satisfied by asking the user 690 // to authenticate. 691 KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); 692 int getKeyCharacteristicsErrorCode = 693 getKeyCharacteristics(keystoreKeyAlias, null, null, keyCharacteristics); 694 if (getKeyCharacteristicsErrorCode != NO_ERROR) { 695 return new InvalidKeyException( 696 "Failed to obtained key characteristics", 697 getKeyStoreException(getKeyCharacteristicsErrorCode)); 698 } 699 List<Long> keySids = 700 keyCharacteristics.getLongs(KeymasterDefs.KM_TAG_USER_SECURE_ID); 701 if (keySids.isEmpty()) { 702 // Key is not bound to any SIDs -- no amount of authentication will help here. 703 return new KeyPermanentlyInvalidatedException(); 704 } 705 long rootSid = GateKeeper.getSecureUserId(); 706 if ((rootSid != 0) && (keySids.contains(Long.valueOf(rootSid)))) { 707 // One of the key's SIDs is the current root SID -- user can be authenticated 708 // against that SID. 709 return new UserNotAuthenticatedException(); 710 } 711 712 long fingerprintOnlySid = getFingerprintOnlySid(); 713 if ((fingerprintOnlySid != 0) 714 && (keySids.contains(Long.valueOf(fingerprintOnlySid)))) { 715 // One of the key's SIDs is the current fingerprint SID -- user can be 716 // authenticated against that SID. 717 return new UserNotAuthenticatedException(); 718 } 719 720 // None of the key's SIDs can ever be authenticated 721 return new KeyPermanentlyInvalidatedException(); 722 } 723 default: 724 return new InvalidKeyException("Keystore operation failed", e); 725 } 726 } 727 728 private long getFingerprintOnlySid() { 729 FingerprintManager fingerprintManager = 730 mContext.getSystemService(FingerprintManager.class); 731 if (fingerprintManager == null) { 732 return 0; 733 } 734 735 if (!fingerprintManager.isHardwareDetected()) { 736 return 0; 737 } 738 739 return fingerprintManager.getAuthenticatorId(); 740 } 741 742 /** 743 * Returns an {@link InvalidKeyException} corresponding to the provided keystore/keymaster error 744 * code. 745 */ 746 public InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int errorCode) { 747 return getInvalidKeyException(keystoreKeyAlias, getKeyStoreException(errorCode)); 748 } 749} 750