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