1 /* 2 * Copyright (C) 2018 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 android.hardware.biometrics; 18 19 import static android.Manifest.permission.USE_BIOMETRIC; 20 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; 21 import static android.hardware.biometrics.BiometricManager.Authenticators; 22 23 import android.annotation.CallbackExecutor; 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.RequiresPermission; 28 import android.content.Context; 29 import android.content.DialogInterface; 30 import android.hardware.face.FaceManager; 31 import android.hardware.fingerprint.FingerprintManager; 32 import android.os.Binder; 33 import android.os.Bundle; 34 import android.os.CancellationSignal; 35 import android.os.IBinder; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.security.identity.IdentityCredential; 39 import android.security.keystore.KeyGenParameterSpec; 40 import android.security.keystore.KeyProperties; 41 import android.text.TextUtils; 42 import android.util.Log; 43 44 import com.android.internal.R; 45 46 import java.lang.annotation.Retention; 47 import java.lang.annotation.RetentionPolicy; 48 import java.security.Signature; 49 import java.util.concurrent.Executor; 50 51 import javax.crypto.Cipher; 52 import javax.crypto.Mac; 53 54 /** 55 * A class that manages a system-provided biometric dialog. 56 */ 57 public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants { 58 59 private static final String TAG = "BiometricPrompt"; 60 61 /** 62 * @hide 63 */ 64 public static final String KEY_TITLE = "title"; 65 /** 66 * @hide 67 */ 68 @RequiresPermission(USE_BIOMETRIC_INTERNAL) 69 public static final String KEY_USE_DEFAULT_TITLE = "use_default_title"; 70 /** 71 * @hide 72 */ 73 public static final String KEY_SUBTITLE = "subtitle"; 74 /** 75 * @hide 76 */ 77 public static final String KEY_DESCRIPTION = "description"; 78 /** 79 * @hide 80 */ 81 @RequiresPermission(USE_BIOMETRIC_INTERNAL) 82 public static final String KEY_DEVICE_CREDENTIAL_TITLE = "device_credential_title"; 83 /** 84 * @hide 85 */ 86 @RequiresPermission(USE_BIOMETRIC_INTERNAL) 87 public static final String KEY_DEVICE_CREDENTIAL_SUBTITLE = "device_credential_subtitle"; 88 /** 89 * @hide 90 */ 91 @RequiresPermission(USE_BIOMETRIC_INTERNAL) 92 public static final String KEY_DEVICE_CREDENTIAL_DESCRIPTION = "device_credential_description"; 93 /** 94 * @hide 95 */ 96 public static final String KEY_NEGATIVE_TEXT = "negative_text"; 97 /** 98 * @hide 99 */ 100 public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation"; 101 /** 102 * This is deprecated. Internally we should use {@link #KEY_AUTHENTICATORS_ALLOWED} 103 * @hide 104 */ 105 public static final String KEY_ALLOW_DEVICE_CREDENTIAL = "allow_device_credential"; 106 /** 107 * If this key is set, we will ignore {@link #KEY_ALLOW_DEVICE_CREDENTIAL} 108 * @hide 109 */ 110 public static final String KEY_AUTHENTICATORS_ALLOWED = "authenticators_allowed"; 111 /** 112 * If this is set, check the Device Policy Manager for allowed biometrics. 113 * @hide 114 */ 115 @RequiresPermission(USE_BIOMETRIC_INTERNAL) 116 public static final String EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS = "check_dpm"; 117 /** 118 * Request to receive system events, such as back gesture/button. See 119 * {@link AuthenticationCallback#onSystemEvent(int)} 120 * @hide 121 */ 122 @RequiresPermission(USE_BIOMETRIC_INTERNAL) 123 public static final String KEY_RECEIVE_SYSTEM_EVENTS = "receive_system_events"; 124 125 /** 126 * Error/help message will show for this amount of time. 127 * For error messages, the dialog will also be dismissed after this amount of time. 128 * Error messages will be propagated back to the application via AuthenticationCallback 129 * after this amount of time. 130 * @hide 131 */ 132 public static final int HIDE_DIALOG_DELAY = 2000; // ms 133 134 /** 135 * @hide 136 */ 137 public static final int DISMISSED_REASON_BIOMETRIC_CONFIRMED = 1; 138 139 /** 140 * Dialog is done animating away after user clicked on the button set via 141 * {@link BiometricPrompt.Builder#setNegativeButton(CharSequence, Executor, 142 * DialogInterface.OnClickListener)}. 143 * @hide 144 */ 145 public static final int DISMISSED_REASON_NEGATIVE = 2; 146 147 /** 148 * @hide 149 */ 150 public static final int DISMISSED_REASON_USER_CANCEL = 3; 151 152 /** 153 * Authenticated, confirmation not required. Dialog animated away. 154 * @hide 155 */ 156 public static final int DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED = 4; 157 158 /** 159 * Error message shown on SystemUI. When BiometricService receives this, the UI is already 160 * gone. 161 * @hide 162 */ 163 public static final int DISMISSED_REASON_ERROR = 5; 164 165 /** 166 * Dialog dismissal requested by BiometricService. 167 * @hide 168 */ 169 public static final int DISMISSED_REASON_SERVER_REQUESTED = 6; 170 171 /** 172 * @hide 173 */ 174 public static final int DISMISSED_REASON_CREDENTIAL_CONFIRMED = 7; 175 176 private static class ButtonInfo { 177 Executor executor; 178 DialogInterface.OnClickListener listener; ButtonInfo(Executor ex, DialogInterface.OnClickListener l)179 ButtonInfo(Executor ex, DialogInterface.OnClickListener l) { 180 executor = ex; 181 listener = l; 182 } 183 } 184 185 /** 186 * A builder that collects arguments to be shown on the system-provided biometric dialog. 187 */ 188 public static class Builder { 189 private final Bundle mBundle; 190 private ButtonInfo mPositiveButtonInfo; 191 private ButtonInfo mNegativeButtonInfo; 192 private Context mContext; 193 194 /** 195 * Creates a builder for a {@link BiometricPrompt} dialog. 196 * @param context The {@link Context} that will be used to build the prompt. 197 */ Builder(Context context)198 public Builder(Context context) { 199 mBundle = new Bundle(); 200 mContext = context; 201 } 202 203 /** 204 * Required: Sets the title that will be shown on the prompt. 205 * @param title The title to display. 206 * @return This builder. 207 */ 208 @NonNull setTitle(@onNull CharSequence title)209 public Builder setTitle(@NonNull CharSequence title) { 210 mBundle.putCharSequence(KEY_TITLE, title); 211 return this; 212 } 213 214 /** 215 * Shows a default, modality-specific title for the prompt if the title would otherwise be 216 * null or empty. Currently for internal use only. 217 * @return This builder. 218 * @hide 219 */ 220 @RequiresPermission(USE_BIOMETRIC_INTERNAL) 221 @NonNull setUseDefaultTitle()222 public Builder setUseDefaultTitle() { 223 mBundle.putBoolean(KEY_USE_DEFAULT_TITLE, true); 224 return this; 225 } 226 227 /** 228 * Optional: Sets a subtitle that will be shown on the prompt. 229 * @param subtitle The subtitle to display. 230 * @return This builder. 231 */ 232 @NonNull setSubtitle(@onNull CharSequence subtitle)233 public Builder setSubtitle(@NonNull CharSequence subtitle) { 234 mBundle.putCharSequence(KEY_SUBTITLE, subtitle); 235 return this; 236 } 237 238 /** 239 * Optional: Sets a description that will be shown on the prompt. 240 * @param description The description to display. 241 * @return This builder. 242 */ 243 @NonNull setDescription(@onNull CharSequence description)244 public Builder setDescription(@NonNull CharSequence description) { 245 mBundle.putCharSequence(KEY_DESCRIPTION, description); 246 return this; 247 } 248 249 /** 250 * Sets an optional title, subtitle, and/or description that will override other text when 251 * the user is authenticating with PIN/pattern/password. Currently for internal use only. 252 * @return This builder. 253 * @hide 254 */ 255 @RequiresPermission(USE_BIOMETRIC_INTERNAL) 256 @NonNull setTextForDeviceCredential( @ullable CharSequence title, @Nullable CharSequence subtitle, @Nullable CharSequence description)257 public Builder setTextForDeviceCredential( 258 @Nullable CharSequence title, 259 @Nullable CharSequence subtitle, 260 @Nullable CharSequence description) { 261 if (title != null) { 262 mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_TITLE, title); 263 } 264 if (subtitle != null) { 265 mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_SUBTITLE, subtitle); 266 } 267 if (description != null) { 268 mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_DESCRIPTION, description); 269 } 270 return this; 271 } 272 273 /** 274 * Required: Sets the text, executor, and click listener for the negative button on the 275 * prompt. This is typically a cancel button, but may be also used to show an alternative 276 * method for authentication, such as a screen that asks for a backup password. 277 * 278 * <p>Note that this setting is not required, and in fact is explicitly disallowed, if 279 * device credential authentication is enabled via {@link #setAllowedAuthenticators(int)} or 280 * {@link #setDeviceCredentialAllowed(boolean)}. 281 * 282 * @param text Text to be shown on the negative button for the prompt. 283 * @param executor Executor that will be used to run the on click callback. 284 * @param listener Listener containing a callback to be run when the button is pressed. 285 * @return This builder. 286 */ 287 @NonNull setNegativeButton(@onNull CharSequence text, @NonNull @CallbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener)288 public Builder setNegativeButton(@NonNull CharSequence text, 289 @NonNull @CallbackExecutor Executor executor, 290 @NonNull DialogInterface.OnClickListener listener) { 291 if (TextUtils.isEmpty(text)) { 292 throw new IllegalArgumentException("Text must be set and non-empty"); 293 } 294 if (executor == null) { 295 throw new IllegalArgumentException("Executor must not be null"); 296 } 297 if (listener == null) { 298 throw new IllegalArgumentException("Listener must not be null"); 299 } 300 mBundle.putCharSequence(KEY_NEGATIVE_TEXT, text); 301 mNegativeButtonInfo = new ButtonInfo(executor, listener); 302 return this; 303 } 304 305 /** 306 * Optional: Sets a hint to the system for whether to require user confirmation after 307 * authentication. For example, implicit modalities like face and iris are passive, meaning 308 * they don't require an explicit user action to complete authentication. If set to true, 309 * these modalities should require the user to take some action (e.g. press a button) 310 * before {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is 311 * called. Defaults to true. 312 * 313 * <p>A typical use case for not requiring confirmation would be for low-risk transactions, 314 * such as re-authenticating a recently authenticated application. A typical use case for 315 * requiring confirmation would be for authorizing a purchase. 316 * 317 * <p>Note that this just passes a hint to the system, which the system may then ignore. For 318 * example, a value of false may be ignored if the user has disabled implicit authentication 319 * in Settings, or if it does not apply to a particular modality (e.g. fingerprint). 320 * 321 * @param requireConfirmation true if explicit user confirmation should be required, or 322 * false otherwise. 323 * @return This builder. 324 */ 325 @NonNull setConfirmationRequired(boolean requireConfirmation)326 public Builder setConfirmationRequired(boolean requireConfirmation) { 327 mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation); 328 return this; 329 } 330 331 /** 332 * Optional: If enabled, the user will be given the option to authenticate with their device 333 * PIN, pattern, or password. Developers should first check {@link 334 * BiometricManager#canAuthenticate(int)} for {@link Authenticators#DEVICE_CREDENTIAL} 335 * before enabling. If the device is not secured with a credential, 336 * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} will be invoked 337 * with {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL}. Defaults to false. 338 * 339 * <p>Note that enabling this option replaces the negative button on the prompt with one 340 * that allows the user to authenticate with their device credential, making it an error to 341 * call {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. 342 * 343 * @param allowed true if the prompt should fall back to asking for the user's device 344 * credential (PIN/pattern/password), or false otherwise. 345 * @return This builder. 346 * 347 * @deprecated Replaced by {@link #setAllowedAuthenticators(int)}. 348 */ 349 @Deprecated 350 @NonNull setDeviceCredentialAllowed(boolean allowed)351 public Builder setDeviceCredentialAllowed(boolean allowed) { 352 mBundle.putBoolean(KEY_ALLOW_DEVICE_CREDENTIAL, allowed); 353 return this; 354 } 355 356 /** 357 * Optional: Specifies the type(s) of authenticators that may be invoked by 358 * {@link BiometricPrompt} to authenticate the user. Available authenticator types are 359 * defined in {@link Authenticators} and can be combined via bitwise OR. Defaults to: 360 * <ul> 361 * <li>{@link Authenticators#BIOMETRIC_WEAK} for non-crypto authentication, or</li> 362 * <li>{@link Authenticators#BIOMETRIC_STRONG} for crypto-based authentication.</li> 363 * </ul> 364 * 365 * <p>If this method is used and no authenticator of any of the specified types is available 366 * at the time <code>BiometricPrompt#authenticate(...)</code> is called, authentication will 367 * be canceled and {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} 368 * will be invoked with an appropriate error code. 369 * 370 * <p>This method should be preferred over {@link #setDeviceCredentialAllowed(boolean)} and 371 * overrides the latter if both are used. Using this method to enable device credential 372 * authentication (with {@link Authenticators#DEVICE_CREDENTIAL}) will replace the negative 373 * button on the prompt, making it an error to also call 374 * {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. 375 * 376 * <p>If unlocking cryptographic operation(s), it is the application's responsibility to 377 * request authentication with the proper set of authenticators (e.g. match the 378 * authenticators specified during key generation). 379 * 380 * @see KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int) 381 * @see KeyProperties#AUTH_BIOMETRIC_STRONG 382 * @see KeyProperties#AUTH_DEVICE_CREDENTIAL 383 * 384 * @param authenticators A bit field representing all valid authenticator types that may be 385 * invoked by the prompt. 386 * @return This builder. 387 */ 388 @NonNull setAllowedAuthenticators(@uthenticators.Types int authenticators)389 public Builder setAllowedAuthenticators(@Authenticators.Types int authenticators) { 390 mBundle.putInt(KEY_AUTHENTICATORS_ALLOWED, authenticators); 391 return this; 392 } 393 394 /** 395 * If set check the Device Policy Manager for disabled biometrics. 396 * 397 * @param checkDevicePolicyManager 398 * @return This builder. 399 * @hide 400 */ 401 @NonNull setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager)402 public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) { 403 mBundle.putBoolean(EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, 404 checkDevicePolicyManager); 405 return this; 406 } 407 408 /** 409 * If set, receive internal events via {@link AuthenticationCallback#onSystemEvent(int)} 410 * @param set 411 * @return This builder. 412 * @hide 413 */ 414 @NonNull setReceiveSystemEvents(boolean set)415 public Builder setReceiveSystemEvents(boolean set) { 416 mBundle.putBoolean(KEY_RECEIVE_SYSTEM_EVENTS, set); 417 return this; 418 } 419 420 /** 421 * Creates a {@link BiometricPrompt}. 422 * 423 * @return An instance of {@link BiometricPrompt}. 424 * 425 * @throws IllegalArgumentException If any required fields are unset, or if given any 426 * invalid combination of field values. 427 */ 428 @NonNull build()429 public BiometricPrompt build() { 430 final CharSequence title = mBundle.getCharSequence(KEY_TITLE); 431 final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT); 432 final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE, false); 433 final boolean deviceCredentialAllowed = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL); 434 final @Authenticators.Types int authenticators = 435 mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED, 0); 436 final boolean willShowDeviceCredentialButton = deviceCredentialAllowed 437 || (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0; 438 439 if (TextUtils.isEmpty(title) && !useDefaultTitle) { 440 throw new IllegalArgumentException("Title must be set and non-empty"); 441 } else if (TextUtils.isEmpty(negative) && !willShowDeviceCredentialButton) { 442 throw new IllegalArgumentException("Negative text must be set and non-empty"); 443 } else if (!TextUtils.isEmpty(negative) && willShowDeviceCredentialButton) { 444 throw new IllegalArgumentException("Can't have both negative button behavior" 445 + " and device credential enabled"); 446 } 447 return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo); 448 } 449 } 450 451 private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener { 452 @Override onCancel()453 public void onCancel() { 454 cancelAuthentication(); 455 } 456 } 457 458 private final IBinder mToken = new Binder(); 459 private final Context mContext; 460 private final IAuthService mService; 461 private final Bundle mBundle; 462 private final ButtonInfo mPositiveButtonInfo; 463 private final ButtonInfo mNegativeButtonInfo; 464 465 private CryptoObject mCryptoObject; 466 private Executor mExecutor; 467 private AuthenticationCallback mAuthenticationCallback; 468 469 private final IBiometricServiceReceiver mBiometricServiceReceiver = 470 new IBiometricServiceReceiver.Stub() { 471 472 @Override 473 public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType) 474 throws RemoteException { 475 mExecutor.execute(() -> { 476 final AuthenticationResult result = 477 new AuthenticationResult(mCryptoObject, authenticationType); 478 mAuthenticationCallback.onAuthenticationSucceeded(result); 479 }); 480 } 481 482 @Override 483 public void onAuthenticationFailed() throws RemoteException { 484 mExecutor.execute(() -> { 485 mAuthenticationCallback.onAuthenticationFailed(); 486 }); 487 } 488 489 @Override 490 public void onError(int modality, int error, int vendorCode) throws RemoteException { 491 mExecutor.execute(() -> { 492 String errorMessage; 493 switch (modality) { 494 case TYPE_FACE: 495 errorMessage = FaceManager.getErrorString(mContext, error, vendorCode); 496 break; 497 498 case TYPE_FINGERPRINT: 499 errorMessage = FingerprintManager.getErrorString(mContext, error, 500 vendorCode); 501 break; 502 503 default: 504 errorMessage = ""; 505 } 506 mAuthenticationCallback.onAuthenticationError(error, errorMessage); 507 }); 508 } 509 510 @Override 511 public void onAcquired(int acquireInfo, String message) throws RemoteException { 512 mExecutor.execute(() -> { 513 mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message); 514 }); 515 } 516 517 @Override 518 public void onDialogDismissed(int reason) throws RemoteException { 519 // Check the reason and invoke OnClickListener(s) if necessary 520 if (reason == DISMISSED_REASON_BIOMETRIC_CONFIRMED) { 521 mPositiveButtonInfo.executor.execute(() -> { 522 mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); 523 }); 524 } else if (reason == DISMISSED_REASON_NEGATIVE) { 525 mNegativeButtonInfo.executor.execute(() -> { 526 mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); 527 }); 528 } 529 } 530 531 @Override 532 public void onSystemEvent(int event) throws RemoteException { 533 mExecutor.execute(() -> { 534 mAuthenticationCallback.onSystemEvent(event); 535 }); 536 } 537 }; 538 BiometricPrompt(Context context, Bundle bundle, ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo)539 private BiometricPrompt(Context context, Bundle bundle, 540 ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) { 541 mContext = context; 542 mBundle = bundle; 543 mPositiveButtonInfo = positiveButtonInfo; 544 mNegativeButtonInfo = negativeButtonInfo; 545 mService = IAuthService.Stub.asInterface( 546 ServiceManager.getService(Context.AUTH_SERVICE)); 547 } 548 549 /** 550 * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}. 551 * @return The title of the prompt, which is guaranteed to be non-null. 552 */ 553 @NonNull getTitle()554 public CharSequence getTitle() { 555 return mBundle.getCharSequence(KEY_TITLE, ""); 556 } 557 558 /** 559 * Whether to use a default modality-specific title. For internal use only. 560 * @return See {@link Builder#setUseDefaultTitle()}. 561 * @hide 562 */ 563 @RequiresPermission(USE_BIOMETRIC_INTERNAL) shouldUseDefaultTitle()564 public boolean shouldUseDefaultTitle() { 565 return mBundle.getBoolean(KEY_USE_DEFAULT_TITLE, false); 566 } 567 568 /** 569 * Gets the subtitle for the prompt, as set by {@link Builder#setSubtitle(CharSequence)}. 570 * @return The subtitle for the prompt, or null if the prompt has no subtitle. 571 */ 572 @Nullable getSubtitle()573 public CharSequence getSubtitle() { 574 return mBundle.getCharSequence(KEY_SUBTITLE); 575 } 576 577 /** 578 * Gets the description for the prompt, as set by {@link Builder#setDescription(CharSequence)}. 579 * @return The description for the prompt, or null if the prompt has no description. 580 */ 581 @Nullable getDescription()582 public CharSequence getDescription() { 583 return mBundle.getCharSequence(KEY_DESCRIPTION); 584 } 585 586 /** 587 * Gets the negative button text for the prompt, as set by 588 * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. 589 * @return The negative button text for the prompt, or null if no negative button text was set. 590 */ 591 @Nullable getNegativeButtonText()592 public CharSequence getNegativeButtonText() { 593 return mBundle.getCharSequence(KEY_NEGATIVE_TEXT); 594 } 595 596 /** 597 * Determines if explicit user confirmation is required by the prompt, as set by 598 * {@link Builder#setConfirmationRequired(boolean)}. 599 * 600 * @return true if explicit user confirmation is required, or false otherwise. 601 */ isConfirmationRequired()602 public boolean isConfirmationRequired() { 603 return mBundle.getBoolean(KEY_REQUIRE_CONFIRMATION, true); 604 } 605 606 /** 607 * Gets the type(s) of authenticators that may be invoked by the prompt to authenticate the 608 * user, as set by {@link Builder#setAllowedAuthenticators(int)}. 609 * 610 * @return A bit field representing the type(s) of authenticators that may be invoked by the 611 * prompt (as defined by {@link Authenticators}), or 0 if this field was not set. 612 */ 613 @Nullable getAllowedAuthenticators()614 public int getAllowedAuthenticators() { 615 return mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED, 0); 616 } 617 618 /** 619 * A wrapper class for the cryptographic operations supported by BiometricPrompt. 620 * 621 * <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, and 622 * {@link IdentityCredential}. 623 * 624 * <p>Cryptographic operations in Android can be split into two categories: auth-per-use and 625 * time-based. This is specified during key creation via the timeout parameter of the 626 * {@link KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int)} API. 627 * 628 * <p>CryptoObjects are used to unlock auth-per-use keys via 629 * {@link BiometricPrompt#authenticate(CryptoObject, CancellationSignal, Executor, 630 * AuthenticationCallback)}, whereas time-based keys are unlocked for their specified duration 631 * any time the user authenticates with the specified authenticators (e.g. unlocking keyguard). 632 * If a time-based key is not available for use (i.e. none of the allowed authenticators have 633 * been unlocked recently), applications can prompt the user to authenticate via 634 * {@link BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} 635 * 636 * @see Builder#setAllowedAuthenticators(int) 637 */ 638 public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { CryptoObject(@onNull Signature signature)639 public CryptoObject(@NonNull Signature signature) { 640 super(signature); 641 } 642 CryptoObject(@onNull Cipher cipher)643 public CryptoObject(@NonNull Cipher cipher) { 644 super(cipher); 645 } 646 CryptoObject(@onNull Mac mac)647 public CryptoObject(@NonNull Mac mac) { 648 super(mac); 649 } 650 CryptoObject(@onNull IdentityCredential credential)651 public CryptoObject(@NonNull IdentityCredential credential) { 652 super(credential); 653 } 654 655 /** 656 * Get {@link Signature} object. 657 * @return {@link Signature} object or null if this doesn't contain one. 658 */ getSignature()659 public Signature getSignature() { 660 return super.getSignature(); 661 } 662 663 /** 664 * Get {@link Cipher} object. 665 * @return {@link Cipher} object or null if this doesn't contain one. 666 */ getCipher()667 public Cipher getCipher() { 668 return super.getCipher(); 669 } 670 671 /** 672 * Get {@link Mac} object. 673 * @return {@link Mac} object or null if this doesn't contain one. 674 */ getMac()675 public Mac getMac() { 676 return super.getMac(); 677 } 678 679 /** 680 * Get {@link IdentityCredential} object. 681 * @return {@link IdentityCredential} object or null if this doesn't contain one. 682 */ getIdentityCredential()683 public @Nullable IdentityCredential getIdentityCredential() { 684 return super.getIdentityCredential(); 685 } 686 } 687 688 /** 689 * Authentication type reported by {@link AuthenticationResult} when the user authenticated by 690 * entering their device PIN, pattern, or password. 691 */ 692 public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; 693 694 /** 695 * Authentication type reported by {@link AuthenticationResult} when the user authenticated by 696 * presenting some form of biometric (e.g. fingerprint or face). 697 */ 698 public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; 699 700 /** 701 * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}. 702 * @hide 703 */ 704 @Retention(RetentionPolicy.SOURCE) 705 @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC}) 706 public @interface AuthenticationResultType { 707 } 708 709 /** 710 * Container for callback data from {@link #authenticate(CancellationSignal, Executor, 711 * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor, 712 * AuthenticationCallback)}. 713 */ 714 public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult { 715 /** 716 * Authentication result 717 * @param crypto 718 * @param authenticationType 719 * @hide 720 */ AuthenticationResult(CryptoObject crypto, @AuthenticationResultType int authenticationType)721 public AuthenticationResult(CryptoObject crypto, 722 @AuthenticationResultType int authenticationType) { 723 // Identifier and userId is not used for BiometricPrompt. 724 super(crypto, authenticationType, null /* identifier */, 0 /* userId */); 725 } 726 727 /** 728 * Provides the crypto object associated with this transaction. 729 * @return The crypto object provided to {@link #authenticate(CryptoObject, 730 * CancellationSignal, Executor, AuthenticationCallback)} 731 */ getCryptoObject()732 public CryptoObject getCryptoObject() { 733 return (CryptoObject) super.getCryptoObject(); 734 } 735 736 /** 737 * Provides the type of authentication (e.g. device credential or biometric) that was 738 * requested from and successfully provided by the user. 739 * 740 * @return An integer value representing the authentication method used. 741 */ getAuthenticationType()742 public @AuthenticationResultType int getAuthenticationType() { 743 return super.getAuthenticationType(); 744 } 745 } 746 747 /** 748 * Callback structure provided to {@link BiometricPrompt#authenticate(CancellationSignal, 749 * Executor, AuthenticationCallback)} or {@link BiometricPrompt#authenticate(CryptoObject, 750 * CancellationSignal, Executor, AuthenticationCallback)}. Users must provide an implementation 751 * of this for listening to authentication events. 752 */ 753 public abstract static class AuthenticationCallback extends 754 BiometricAuthenticator.AuthenticationCallback { 755 /** 756 * Called when an unrecoverable error has been encountered and the operation is complete. 757 * No further actions will be made on this object. 758 * @param errorCode An integer identifying the error message 759 * @param errString A human-readable error string that can be shown on an UI 760 */ 761 @Override onAuthenticationError(int errorCode, CharSequence errString)762 public void onAuthenticationError(int errorCode, CharSequence errString) {} 763 764 /** 765 * Called when a recoverable error has been encountered during authentication. The help 766 * string is provided to give the user guidance for what went wrong, such as "Sensor dirty, 767 * please clean it." 768 * @param helpCode An integer identifying the error message 769 * @param helpString A human-readable string that can be shown on an UI 770 */ 771 @Override onAuthenticationHelp(int helpCode, CharSequence helpString)772 public void onAuthenticationHelp(int helpCode, CharSequence helpString) {} 773 774 /** 775 * Called when a biometric is recognized. 776 * @param result An object containing authentication-related data 777 */ onAuthenticationSucceeded(AuthenticationResult result)778 public void onAuthenticationSucceeded(AuthenticationResult result) {} 779 780 /** 781 * Called when a biometric is valid but not recognized. 782 */ 783 @Override onAuthenticationFailed()784 public void onAuthenticationFailed() {} 785 786 /** 787 * Called when a biometric has been acquired, but hasn't been processed yet. 788 * @hide 789 */ 790 @Override onAuthenticationAcquired(int acquireInfo)791 public void onAuthenticationAcquired(int acquireInfo) {} 792 793 /** 794 * Receiver for internal system events. See {@link Builder#setReceiveSystemEvents(boolean)} 795 * @hide 796 */ onSystemEvent(int event)797 public void onSystemEvent(int event) {} 798 } 799 800 /** 801 * Authenticates for the given user. 802 * 803 * @param cancel An object that can be used to cancel authentication 804 * @param executor An executor to handle callback events 805 * @param callback An object to receive authentication events 806 * @param userId The user to authenticate 807 * 808 * @hide 809 */ 810 @RequiresPermission(USE_BIOMETRIC_INTERNAL) authenticateUser(@onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId)811 public void authenticateUser(@NonNull CancellationSignal cancel, 812 @NonNull @CallbackExecutor Executor executor, 813 @NonNull AuthenticationCallback callback, 814 int userId) { 815 if (cancel == null) { 816 throw new IllegalArgumentException("Must supply a cancellation signal"); 817 } 818 if (executor == null) { 819 throw new IllegalArgumentException("Must supply an executor"); 820 } 821 if (callback == null) { 822 throw new IllegalArgumentException("Must supply a callback"); 823 } 824 authenticateInternal(null /* crypto */, cancel, executor, callback, userId); 825 } 826 827 /** 828 * This call warms up the biometric hardware, displays a system-provided dialog, and starts 829 * scanning for a biometric. It terminates when {@link 830 * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link 831 * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)}, or when the user 832 * dismisses the system-provided dialog, at which point the crypto object becomes invalid. This 833 * operation can be canceled by using the provided cancel object. The application will receive 834 * authentication errors through {@link AuthenticationCallback}, and button events through the 835 * corresponding callback set in {@link Builder#setNegativeButton(CharSequence, Executor, 836 * DialogInterface.OnClickListener)}. It is safe to reuse the {@link BiometricPrompt} object, 837 * and calling {@link BiometricPrompt#authenticate(CancellationSignal, Executor, 838 * AuthenticationCallback)} while an existing authentication attempt is occurring will stop the 839 * previous client and start a new authentication. The interrupted client will receive a 840 * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int, 841 * CharSequence)}. 842 * 843 * <p>Note: Applications generally should not cancel and start authentication in quick 844 * succession. For example, to properly handle authentication across configuration changes, it's 845 * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, 846 * the application will not need to cancel/restart authentication during the configuration 847 * change. 848 * 849 * <p>Per the Android CDD, only biometric authenticators that meet or exceed the requirements 850 * for <strong>Strong</strong> are permitted to integrate with Keystore to perform related 851 * cryptographic operations. Therefore, it is an error to call this method after explicitly 852 * calling {@link Builder#setAllowedAuthenticators(int)} with any biometric strength other than 853 * {@link Authenticators#BIOMETRIC_STRONG}. 854 * 855 * @throws IllegalArgumentException If any argument is null, or if the allowed biometric 856 * authenticator strength is explicitly set to {@link Authenticators#BIOMETRIC_WEAK}. Prior to 857 * {@link android.os.Build.VERSION_CODES#R}, this exception is also thrown if 858 * {@link Builder#setDeviceCredentialAllowed(boolean)} was explicitly set to true. 859 * 860 * @param crypto A cryptographic operation to be unlocked after successful authentication. 861 * @param cancel An object that can be used to cancel authentication. 862 * @param executor An executor to handle callback events. 863 * @param callback An object to receive authentication events. 864 */ 865 @RequiresPermission(USE_BIOMETRIC) authenticate(@onNull CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)866 public void authenticate(@NonNull CryptoObject crypto, 867 @NonNull CancellationSignal cancel, 868 @NonNull @CallbackExecutor Executor executor, 869 @NonNull AuthenticationCallback callback) { 870 if (crypto == null) { 871 throw new IllegalArgumentException("Must supply a crypto object"); 872 } 873 if (cancel == null) { 874 throw new IllegalArgumentException("Must supply a cancellation signal"); 875 } 876 if (executor == null) { 877 throw new IllegalArgumentException("Must supply an executor"); 878 } 879 if (callback == null) { 880 throw new IllegalArgumentException("Must supply a callback"); 881 } 882 883 // Disallow explicitly setting any non-Strong biometric authenticator types. 884 final @Authenticators.Types int authenticators = mBundle.getInt( 885 KEY_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_STRONG); 886 final int biometricStrength = authenticators & Authenticators.BIOMETRIC_WEAK; 887 if ((biometricStrength & ~Authenticators.BIOMETRIC_STRONG) != 0) { 888 throw new IllegalArgumentException("Only Strong biometrics supported with crypto"); 889 } 890 891 authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId()); 892 } 893 894 /** 895 * This call warms up the biometric hardware, displays a system-provided dialog, and starts 896 * scanning for a biometric. It terminates when {@link 897 * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link 898 * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)} is called, or when 899 * the user dismisses the system-provided dialog. This operation can be canceled by using the 900 * provided cancel object. The application will receive authentication errors through {@link 901 * AuthenticationCallback}, and button events through the corresponding callback set in {@link 902 * Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. It is 903 * safe to reuse the {@link BiometricPrompt} object, and calling {@link 904 * BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} while 905 * an existing authentication attempt is occurring will stop the previous client and start a new 906 * authentication. The interrupted client will receive a cancelled notification through {@link 907 * AuthenticationCallback#onAuthenticationError(int, CharSequence)}. 908 * 909 * <p>Note: Applications generally should not cancel and start authentication in quick 910 * succession. For example, to properly handle authentication across configuration changes, it's 911 * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, 912 * the application will not need to cancel/restart authentication during the configuration 913 * change. 914 * 915 * @throws IllegalArgumentException If any of the arguments are null. 916 * 917 * @param cancel An object that can be used to cancel authentication. 918 * @param executor An executor to handle callback events. 919 * @param callback An object to receive authentication events. 920 */ 921 @RequiresPermission(USE_BIOMETRIC) authenticate(@onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)922 public void authenticate(@NonNull CancellationSignal cancel, 923 @NonNull @CallbackExecutor Executor executor, 924 @NonNull AuthenticationCallback callback) { 925 if (cancel == null) { 926 throw new IllegalArgumentException("Must supply a cancellation signal"); 927 } 928 if (executor == null) { 929 throw new IllegalArgumentException("Must supply an executor"); 930 } 931 if (callback == null) { 932 throw new IllegalArgumentException("Must supply a callback"); 933 } 934 authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId()); 935 } 936 cancelAuthentication()937 private void cancelAuthentication() { 938 if (mService != null) { 939 try { 940 mService.cancelAuthentication(mToken, mContext.getOpPackageName()); 941 } catch (RemoteException e) { 942 Log.e(TAG, "Unable to cancel authentication", e); 943 } 944 } 945 } 946 authenticateInternal(@ullable CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId)947 private void authenticateInternal(@Nullable CryptoObject crypto, 948 @NonNull CancellationSignal cancel, 949 @NonNull @CallbackExecutor Executor executor, 950 @NonNull AuthenticationCallback callback, 951 int userId) { 952 try { 953 if (cancel.isCanceled()) { 954 Log.w(TAG, "Authentication already canceled"); 955 return; 956 } else { 957 cancel.setOnCancelListener(new OnAuthenticationCancelListener()); 958 } 959 960 mCryptoObject = crypto; 961 mExecutor = executor; 962 mAuthenticationCallback = callback; 963 final long sessionId = crypto != null ? crypto.getOpId() : 0; 964 965 final Bundle bundle; 966 if (crypto != null) { 967 // Allowed authenticators should default to BIOMETRIC_STRONG for crypto auth. 968 // Note that we use a new bundle here so as to not overwrite the application's 969 // preference, since it is possible that the same prompt configuration be used 970 // without a crypto object later. 971 bundle = new Bundle(mBundle); 972 bundle.putInt(KEY_AUTHENTICATORS_ALLOWED, 973 mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED, 974 Authenticators.BIOMETRIC_STRONG)); 975 } else { 976 bundle = mBundle; 977 } 978 979 mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver, 980 mContext.getOpPackageName(), bundle); 981 982 } catch (RemoteException e) { 983 Log.e(TAG, "Remote exception while authenticating", e); 984 mExecutor.execute(() -> { 985 callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE, 986 mContext.getString(R.string.biometric_error_hw_unavailable)); 987 }); 988 } 989 } 990 } 991