From 6601bc340331b2a08cdff3ea45e4520f648b5981 Mon Sep 17 00:00:00 2001 From: Curtis Belmonte Date: Thu, 9 Jan 2020 17:46:31 -0800 Subject: [PATCH] Add auth type to BiometricPrompt.AuthenticationResult Now that BiometricPrompt supports primary auth via device credential, we want the high-level type of authentication used to be available to a developer via the AuthenticationResult in onAuthenticationSucceeded. Rather than include information about the strength of the biometric sensor by reusing the existing Authenticators.Types constants, this commit adds new integer constants that provide similar information but at a lower level of granularity (device credential vs. biometric). Test: atest com.android.server.biometrics Test: atest com.android.systemui.biometrics Test: Manually inspect new AuthenticationResult object on Pixel 3/4 Bug: 80525177 Bug: 141025588 Change-Id: Ic09ffdff995afe374f11721e6e777632de9ae867 --- api/current.txt | 3 + .../biometrics/BiometricAuthenticator.java | 24 ++++++-- .../hardware/biometrics/BiometricPrompt.java | 57 ++++++++++++++++--- .../biometrics/IBiometricServiceReceiver.aidl | 4 +- .../server/biometrics/BiometricService.java | 3 +- .../com/android/server/biometrics/Utils.java | 27 +++++++++ .../biometrics/BiometricServiceTest.java | 6 +- .../android/server/biometrics/UtilsTest.java | 18 ++++++ 8 files changed, 124 insertions(+), 18 deletions(-) diff --git a/api/current.txt b/api/current.txt index 5ab1c0171c618..c81e60874d387 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16834,6 +16834,8 @@ package android.hardware.biometrics { method @Nullable public CharSequence getSubtitle(); method @NonNull public CharSequence getTitle(); method public boolean isConfirmationRequired(); + field public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; // 0x2 + field public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; // 0x1 field public static final int BIOMETRIC_ACQUIRED_GOOD = 0; // 0x0 field public static final int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 field public static final int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; // 0x2 @@ -16863,6 +16865,7 @@ package android.hardware.biometrics { } public static class BiometricPrompt.AuthenticationResult { + method public int getAuthenticationType(); method public android.hardware.biometrics.BiometricPrompt.CryptoObject getCryptoObject(); } diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index 698876b9c59ea..11cf2d6987306 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -18,6 +18,7 @@ package android.hardware.biometrics; import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; import android.os.CancellationSignal; import android.os.Parcelable; @@ -119,6 +120,7 @@ public interface BiometricAuthenticator { class AuthenticationResult { private Identifier mIdentifier; private CryptoObject mCryptoObject; + private @AuthenticationResultType int mAuthenticationType; private int mUserId; /** @@ -129,26 +131,40 @@ public interface BiometricAuthenticator { /** * Authentication result * @param crypto + * @param authenticationType * @param identifier * @param userId * @hide */ - public AuthenticationResult(CryptoObject crypto, Identifier identifier, + public AuthenticationResult(CryptoObject crypto, + @AuthenticationResultType int authenticationType, Identifier identifier, int userId) { mCryptoObject = crypto; + mAuthenticationType = authenticationType; mIdentifier = identifier; mUserId = userId; } /** - * Obtain the crypto object associated with this transaction - * @return crypto object provided to {@link BiometricAuthenticator#authenticate( - * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)} + * Provides the crypto object associated with this transaction. + * @return The crypto object provided to {@link BiometricPrompt#authenticate( + * BiometricPrompt.CryptoObject, CancellationSignal, Executor, + * BiometricPrompt.AuthenticationCallback)} */ public CryptoObject getCryptoObject() { return mCryptoObject; } + /** + * Provides the type of authentication (e.g. device credential or biometric) that was + * requested from and successfully provided by the user. + * + * @return An integer value representing the authentication method used. + */ + public @AuthenticationResultType int getAuthenticationType() { + return mAuthenticationType; + } + /** * Obtain the biometric identifier associated with this operation. Applications are strongly * discouraged from associating specific identifiers with specific applications or diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index cb8fc8b1cbb15..a695ce8e511fe 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.BiometricManager.Authenticators; import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -40,6 +41,8 @@ import android.util.Log; import com.android.internal.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.security.Signature; import java.util.concurrent.Executor; @@ -397,9 +400,11 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan new IBiometricServiceReceiver.Stub() { @Override - public void onAuthenticationSucceeded() throws RemoteException { + public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType) + throws RemoteException { mExecutor.execute(() -> { - final AuthenticationResult result = new AuthenticationResult(mCryptoObject); + final AuthenticationResult result = + new AuthenticationResult(mCryptoObject, authenticationType); mAuthenticationCallback.onAuthenticationSucceeded(result); }); } @@ -576,28 +581,62 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** - * Container for callback data from {@link #authenticate( CancellationSignal, Executor, + * Authentication type reported by {@link AuthenticationResult} when the user authenticated by + * entering their device PIN, pattern, or password. + */ + public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; + + /** + * Authentication type reported by {@link AuthenticationResult} when the user authenticated by + * presenting some form of biometric (e.g. fingerprint or face). + */ + public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; + + /** + * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC}) + public @interface AuthenticationResultType { + } + + /** + * Container for callback data from {@link #authenticate(CancellationSignal, Executor, * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor, - * AuthenticationCallback)} + * AuthenticationCallback)}. */ public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult { /** * Authentication result * @param crypto + * @param authenticationType * @hide */ - public AuthenticationResult(CryptoObject crypto) { + public AuthenticationResult(CryptoObject crypto, + @AuthenticationResultType int authenticationType) { // Identifier and userId is not used for BiometricPrompt. - super(crypto, null /* identifier */, 0 /* userId */); + super(crypto, authenticationType, null /* identifier */, 0 /* userId */); } + /** - * Obtain the crypto object associated with this transaction - * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal, - * Executor, AuthenticationCallback)} + * Provides the crypto object associated with this transaction. + * @return The crypto object provided to {@link #authenticate(CryptoObject, + * CancellationSignal, Executor, AuthenticationCallback)} */ public CryptoObject getCryptoObject() { return (CryptoObject) super.getCryptoObject(); } + + /** + * Provides the type of authentication (e.g. device credential or biometric) that was + * requested from and successfully provided by the user. + * + * @return An integer value representing the authentication method used. + */ + public @AuthenticationResultType int getAuthenticationType() { + return super.getAuthenticationType(); + } } /** diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl index c960049438f1c..1d43aa640b402 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl @@ -20,8 +20,8 @@ package android.hardware.biometrics; * @hide */ oneway interface IBiometricServiceReceiver { - // Notify BiometricPrompt that authentication was successful - void onAuthenticationSucceeded(); + // Notify BiometricPrompt that authentication was successful. + void onAuthenticationSucceeded(int authenticationType); // Noties that authentication failed. void onAuthenticationFailed(); // Notify BiometricPrompt that an error has occurred. diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index e1a9f3b97e9ac..8dd3242c947a5 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1400,7 +1400,8 @@ public class BiometricService extends SystemService { if (mCurrentAuthSession.mTokenEscrow != null) { mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow); } - mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded( + Utils.getAuthenticationTypeForResult(reason)); break; case BiometricPrompt.DISMISSED_REASON_NEGATIVE: diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 19f535876274c..389763b5377aa 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -22,6 +22,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; import android.os.Build; import android.os.Bundle; import android.os.UserHandle; @@ -210,4 +211,30 @@ public class Utils { } return biometricManagerCode; } + + /** + * Converts a {@link BiometricPrompt} dismissal reason to an authentication type at the level of + * granularity supported by {@link BiometricPrompt.AuthenticationResult}. + * + * @param reason The reason that the {@link BiometricPrompt} was dismissed. Must be one of: + * {@link BiometricPrompt#DISMISSED_REASON_CREDENTIAL_CONFIRMED}, + * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRMED}, or + * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED} + * @return An integer representing the authentication type for {@link + * BiometricPrompt.AuthenticationResult}. + * @throws IllegalArgumentException if given an invalid dismissal reason. + */ + public static @AuthenticationResultType int getAuthenticationTypeForResult(int reason) { + switch (reason) { + case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED: + return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL; + + case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED: + case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED: + return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC; + + default: + throw new IllegalArgumentException("Unsupported dismissal reason: " + reason); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index f96d9961d364b..bec265e6d62d8 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -411,7 +411,8 @@ public class BiometricServiceTest { // HAT sent to keystore verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); // Send onAuthenticated to client - verify(mReceiver1).onAuthenticationSucceeded(); + verify(mReceiver1).onAuthenticationSucceeded( + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); // Current session becomes null assertNull(mBiometricService.mCurrentAuthSession); } @@ -461,7 +462,8 @@ public class BiometricServiceTest { BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED); waitForIdle(); verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); - verify(mReceiver1).onAuthenticationSucceeded(); + verify(mReceiver1).onAuthenticationSucceeded( + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java index abe39f0934db5..312ff2ca84a16 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java @@ -223,4 +223,22 @@ public class UtilsTest { Utils.biometricConstantsToBiometricManager(testCases[i][0])); } } + + @Test + public void testGetAuthenticationTypeForResult_getsCorrectType() { + assertEquals(Utils.getAuthenticationTypeForResult( + BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED), + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL); + assertEquals(Utils.getAuthenticationTypeForResult( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED), + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); + assertEquals(Utils.getAuthenticationTypeForResult( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED), + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetAuthResultType_throwsForInvalidReason() { + Utils.getAuthenticationTypeForResult(BiometricPrompt.DISMISSED_REASON_NEGATIVE); + } }