| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.biometrics; |
| |
| import static android.Manifest.permission.USE_BIOMETRIC; |
| import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; |
| import static android.Manifest.permission.USE_FINGERPRINT; |
| import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; |
| import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; |
| import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS; |
| import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; |
| import static android.hardware.biometrics.BiometricManager.Authenticators; |
| |
| import android.app.ActivityManager; |
| import android.app.IActivityManager; |
| import android.app.UserSwitchObserver; |
| import android.app.trust.ITrustManager; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.database.ContentObserver; |
| import android.hardware.biometrics.BiometricAuthenticator; |
| import android.hardware.biometrics.BiometricConstants; |
| import android.hardware.biometrics.BiometricPrompt; |
| import android.hardware.biometrics.BiometricSourceType; |
| import android.hardware.biometrics.BiometricsProtoEnums; |
| import android.hardware.biometrics.IBiometricAuthenticator; |
| import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; |
| import android.hardware.biometrics.IBiometricService; |
| import android.hardware.biometrics.IBiometricServiceReceiver; |
| import android.hardware.biometrics.IBiometricServiceReceiverInternal; |
| import android.net.Uri; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.DeadObjectException; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| import android.security.KeyStore; |
| import android.text.TextUtils; |
| import android.util.Pair; |
| import android.util.Slog; |
| import android.util.StatsLog; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.os.SomeArgs; |
| import com.android.internal.statusbar.IStatusBarService; |
| import com.android.server.SystemService; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Random; |
| |
| /** |
| * System service that arbitrates the modality for BiometricPrompt to use. |
| */ |
| public class BiometricService extends SystemService { |
| |
| static final String TAG = "BiometricService"; |
| private static final boolean DEBUG = true; |
| |
| private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2; |
| private static final int MSG_ON_AUTHENTICATION_REJECTED = 3; |
| private static final int MSG_ON_ERROR = 4; |
| private static final int MSG_ON_ACQUIRED = 5; |
| private static final int MSG_ON_DISMISSED = 6; |
| private static final int MSG_ON_TRY_AGAIN_PRESSED = 7; |
| private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8; |
| private static final int MSG_AUTHENTICATE = 9; |
| private static final int MSG_CANCEL_AUTHENTICATION = 10; |
| private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11; |
| private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12; |
| |
| /** |
| * Authentication either just called and we have not transitioned to the CALLED state, or |
| * authentication terminated (success or error). |
| */ |
| static final int STATE_AUTH_IDLE = 0; |
| /** |
| * Authentication was called and we are waiting for the <Biometric>Services to return their |
| * cookies before starting the hardware and showing the BiometricPrompt. |
| */ |
| static final int STATE_AUTH_CALLED = 1; |
| /** |
| * Authentication started, BiometricPrompt is showing and the hardware is authenticating. |
| */ |
| static final int STATE_AUTH_STARTED = 2; |
| /** |
| * Authentication is paused, waiting for the user to press "try again" button. Only |
| * passive modalities such as Face or Iris should have this state. Note that for passive |
| * modalities, the HAL enters the idle state after onAuthenticated(false) which differs from |
| * fingerprint. |
| */ |
| static final int STATE_AUTH_PAUSED = 3; |
| /** |
| * Authentication is successful, but we're waiting for the user to press "confirm" button. |
| */ |
| static final int STATE_AUTH_PENDING_CONFIRM = 5; |
| /** |
| * Biometric authenticated, waiting for SysUI to finish animation |
| */ |
| static final int STATE_AUTHENTICATED_PENDING_SYSUI = 6; |
| /** |
| * Biometric error, waiting for SysUI to finish animation |
| */ |
| static final int STATE_ERROR_PENDING_SYSUI = 7; |
| /** |
| * Device credential in AuthController is showing |
| */ |
| static final int STATE_SHOWING_DEVICE_CREDENTIAL = 8; |
| |
| final class AuthSession { |
| // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from |
| // <Biometric>Services before we can start authenticating. Pairs that have been returned |
| // are moved to mModalitiesMatched. |
| final HashMap<Integer, Integer> mModalitiesWaiting; |
| // Pairs that have been matched. |
| final HashMap<Integer, Integer> mModalitiesMatched = new HashMap<>(); |
| |
| // The following variables are passed to authenticateInternal, which initiates the |
| // appropriate <Biometric>Services. |
| final IBinder mToken; |
| final long mSessionId; |
| final int mUserId; |
| // Original receiver from BiometricPrompt. |
| final IBiometricServiceReceiver mClientReceiver; |
| final String mOpPackageName; |
| // Info to be shown on BiometricDialog when all cookies are returned. |
| final Bundle mBundle; |
| final int mCallingUid; |
| final int mCallingPid; |
| final int mCallingUserId; |
| // Continue authentication with the same modality/modalities after "try again" is |
| // pressed |
| final int mModality; |
| final boolean mRequireConfirmation; |
| |
| // The current state, which can be either idle, called, or started |
| int mState = STATE_AUTH_IDLE; |
| // For explicit confirmation, do not send to keystore until the user has confirmed |
| // the authentication. |
| byte[] mTokenEscrow; |
| // Waiting for SystemUI to complete animation |
| int mErrorEscrow; |
| int mVendorCodeEscrow; |
| |
| // Timestamp when authentication started |
| private long mStartTimeMs; |
| // Timestamp when hardware authentication occurred |
| private long mAuthenticatedTimeMs; |
| |
| AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId, |
| int userId, IBiometricServiceReceiver receiver, String opPackageName, |
| Bundle bundle, int callingUid, int callingPid, int callingUserId, |
| int modality, boolean requireConfirmation) { |
| mModalitiesWaiting = modalities; |
| mToken = token; |
| mSessionId = sessionId; |
| mUserId = userId; |
| mClientReceiver = receiver; |
| mOpPackageName = opPackageName; |
| mBundle = bundle; |
| mCallingUid = callingUid; |
| mCallingPid = callingPid; |
| mCallingUserId = callingUserId; |
| mModality = modality; |
| mRequireConfirmation = requireConfirmation; |
| } |
| |
| boolean isCrypto() { |
| return mSessionId != 0; |
| } |
| |
| boolean containsCookie(int cookie) { |
| if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) { |
| return true; |
| } |
| if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) { |
| return true; |
| } |
| return false; |
| } |
| |
| boolean isAllowDeviceCredential() { |
| return Utils.isDeviceCredentialAllowed(mBundle); |
| } |
| } |
| |
| private final Injector mInjector; |
| @VisibleForTesting |
| final IBiometricService.Stub mImpl; |
| @VisibleForTesting |
| final SettingObserver mSettingObserver; |
| private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks; |
| private final Random mRandom = new Random(); |
| |
| @VisibleForTesting |
| IStatusBarService mStatusBarService; |
| @VisibleForTesting |
| KeyStore mKeyStore; |
| @VisibleForTesting |
| ITrustManager mTrustManager; |
| |
| // Get and cache the available authenticator (manager) classes. Used since aidl doesn't support |
| // polymorphism :/ |
| final ArrayList<AuthenticatorWrapper> mAuthenticators = new ArrayList<>(); |
| |
| BiometricStrengthController mBiometricStrengthController; |
| |
| // The current authentication session, null if idle/done. We need to track both the current |
| // and pending sessions since errors may be sent to either. |
| @VisibleForTesting |
| AuthSession mCurrentAuthSession; |
| @VisibleForTesting |
| AuthSession mPendingAuthSession; |
| |
| @VisibleForTesting |
| final Handler mHandler = new Handler(Looper.getMainLooper()) { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_ON_AUTHENTICATION_SUCCEEDED: { |
| SomeArgs args = (SomeArgs) msg.obj; |
| handleAuthenticationSucceeded( |
| (boolean) args.arg1 /* requireConfirmation */, |
| (byte[]) args.arg2 /* token */); |
| args.recycle(); |
| break; |
| } |
| |
| case MSG_ON_AUTHENTICATION_REJECTED: { |
| handleAuthenticationRejected(); |
| break; |
| } |
| |
| case MSG_ON_ERROR: { |
| SomeArgs args = (SomeArgs) msg.obj; |
| handleOnError( |
| args.argi1 /* cookie */, |
| args.argi2 /* modality */, |
| args.argi3 /* error */, |
| args.argi4 /* vendorCode */); |
| args.recycle(); |
| break; |
| } |
| |
| case MSG_ON_ACQUIRED: { |
| SomeArgs args = (SomeArgs) msg.obj; |
| handleOnAcquired( |
| args.argi1 /* acquiredInfo */, |
| (String) args.arg1 /* message */); |
| args.recycle(); |
| break; |
| } |
| |
| case MSG_ON_DISMISSED: { |
| handleOnDismissed(msg.arg1); |
| break; |
| } |
| |
| case MSG_ON_TRY_AGAIN_PRESSED: { |
| handleOnTryAgainPressed(); |
| break; |
| } |
| |
| case MSG_ON_READY_FOR_AUTHENTICATION: { |
| SomeArgs args = (SomeArgs) msg.obj; |
| handleOnReadyForAuthentication( |
| args.argi1 /* cookie */, |
| (boolean) args.arg1 /* requireConfirmation */, |
| args.argi2 /* userId */); |
| args.recycle(); |
| break; |
| } |
| |
| case MSG_AUTHENTICATE: { |
| SomeArgs args = (SomeArgs) msg.obj; |
| handleAuthenticate( |
| (IBinder) args.arg1 /* token */, |
| (long) args.arg2 /* sessionId */, |
| args.argi1 /* userid */, |
| (IBiometricServiceReceiver) args.arg3 /* receiver */, |
| (String) args.arg4 /* opPackageName */, |
| (Bundle) args.arg5 /* bundle */, |
| args.argi2 /* callingUid */, |
| args.argi3 /* callingPid */, |
| args.argi4 /* callingUserId */); |
| args.recycle(); |
| break; |
| } |
| |
| case MSG_CANCEL_AUTHENTICATION: { |
| SomeArgs args = (SomeArgs) msg.obj; |
| handleCancelAuthentication( |
| (IBinder) args.arg1 /* token */, |
| (String) args.arg2 /* opPackageName */); |
| args.recycle(); |
| break; |
| } |
| |
| case MSG_ON_AUTHENTICATION_TIMED_OUT: { |
| SomeArgs args = (SomeArgs) msg.obj; |
| handleAuthenticationTimedOut( |
| args.argi1 /* modality */, |
| args.argi2 /* error */, |
| args.argi3 /* vendorCode */); |
| args.recycle(); |
| break; |
| } |
| |
| case MSG_ON_DEVICE_CREDENTIAL_PRESSED: { |
| handleOnDeviceCredentialPressed(); |
| break; |
| } |
| |
| default: |
| Slog.e(TAG, "Unknown message: " + msg); |
| break; |
| } |
| } |
| }; |
| |
| /** |
| * Wraps IBiometricAuthenticator implementation and stores information about the authenticator. |
| * TODO(b/141025588): Consider refactoring the tests to not rely on this implementation detail. |
| */ |
| @VisibleForTesting |
| public static final class AuthenticatorWrapper { |
| public final int id; |
| public final int OEMStrength; // strength as configured by the OEM |
| private int updatedStrength; // strength updated by BiometricStrengthController |
| public final int modality; |
| public final IBiometricAuthenticator impl; |
| |
| AuthenticatorWrapper(int id, int modality, int strength, |
| IBiometricAuthenticator impl) { |
| this.id = id; |
| this.modality = modality; |
| this.OEMStrength = strength; |
| this.updatedStrength = strength; |
| this.impl = impl; |
| } |
| |
| /** |
| * Returns the actual strength, taking any updated strengths into effect. Since more bits |
| * means lower strength, the resulting strength is never stronger than the OEM's configured |
| * strength. |
| * @return a bitfield, see {@link Authenticators} |
| */ |
| public int getActualStrength() { |
| return OEMStrength | updatedStrength; |
| } |
| |
| /** |
| * Stores the updated strength, which takes effect whenever {@link #getActualStrength()} |
| * is checked. |
| * @param newStrength |
| */ |
| public void updateStrength(int newStrength) { |
| String log = "updateStrength: Before(" + toString() + ")"; |
| updatedStrength = newStrength; |
| log += " After(" + toString() + ")"; |
| Slog.d(TAG, log); |
| } |
| |
| @Override |
| public String toString() { |
| return "ID(" + id + ")" |
| + " OEMStrength: " + OEMStrength |
| + " updatedStrength: " + updatedStrength |
| + " modality " + modality |
| + " authenticator: " + impl; |
| } |
| } |
| |
| @VisibleForTesting |
| public static class SettingObserver extends ContentObserver { |
| |
| private static final boolean DEFAULT_KEYGUARD_ENABLED = true; |
| private static final boolean DEFAULT_APP_ENABLED = true; |
| private static final boolean DEFAULT_ALWAYS_REQUIRE_CONFIRMATION = false; |
| |
| private final Uri FACE_UNLOCK_KEYGUARD_ENABLED = |
| Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED); |
| private final Uri FACE_UNLOCK_APP_ENABLED = |
| Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED); |
| private final Uri FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION = |
| Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION); |
| |
| private final ContentResolver mContentResolver; |
| private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks; |
| |
| private final Map<Integer, Boolean> mFaceEnabledOnKeyguard = new HashMap<>(); |
| private final Map<Integer, Boolean> mFaceEnabledForApps = new HashMap<>(); |
| private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>(); |
| |
| /** |
| * Creates a content observer. |
| * |
| * @param handler The handler to run {@link #onChange} on, or null if none. |
| */ |
| public SettingObserver(Context context, Handler handler, |
| List<BiometricService.EnabledOnKeyguardCallback> callbacks) { |
| super(handler); |
| mContentResolver = context.getContentResolver(); |
| mCallbacks = callbacks; |
| updateContentObserver(); |
| } |
| |
| public void updateContentObserver() { |
| mContentResolver.unregisterContentObserver(this); |
| mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED, |
| false /* notifyForDescendents */, |
| this /* observer */, |
| UserHandle.USER_ALL); |
| mContentResolver.registerContentObserver(FACE_UNLOCK_APP_ENABLED, |
| false /* notifyForDescendents */, |
| this /* observer */, |
| UserHandle.USER_ALL); |
| mContentResolver.registerContentObserver(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, |
| false /* notifyForDescendents */, |
| this /* observer */, |
| UserHandle.USER_ALL); |
| } |
| |
| @Override |
| public void onChange(boolean selfChange, Uri uri, int userId) { |
| if (FACE_UNLOCK_KEYGUARD_ENABLED.equals(uri)) { |
| mFaceEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser( |
| mContentResolver, |
| Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED, |
| DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */, |
| userId) != 0); |
| |
| if (userId == ActivityManager.getCurrentUser() && !selfChange) { |
| notifyEnabledOnKeyguardCallbacks(userId); |
| } |
| } else if (FACE_UNLOCK_APP_ENABLED.equals(uri)) { |
| mFaceEnabledForApps.put(userId, Settings.Secure.getIntForUser( |
| mContentResolver, |
| Settings.Secure.FACE_UNLOCK_APP_ENABLED, |
| DEFAULT_APP_ENABLED ? 1 : 0 /* default */, |
| userId) != 0); |
| } else if (FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION.equals(uri)) { |
| mFaceAlwaysRequireConfirmation.put(userId, Settings.Secure.getIntForUser( |
| mContentResolver, |
| Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, |
| DEFAULT_ALWAYS_REQUIRE_CONFIRMATION ? 1 : 0 /* default */, |
| userId) != 0); |
| } |
| } |
| |
| public boolean getFaceEnabledOnKeyguard() { |
| final int user = ActivityManager.getCurrentUser(); |
| if (!mFaceEnabledOnKeyguard.containsKey(user)) { |
| onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, user); |
| } |
| return mFaceEnabledOnKeyguard.get(user); |
| } |
| |
| public boolean getFaceEnabledForApps(int userId) { |
| if (!mFaceEnabledForApps.containsKey(userId)) { |
| onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId); |
| } |
| return mFaceEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED); |
| } |
| |
| public boolean getFaceAlwaysRequireConfirmation(int userId) { |
| if (!mFaceAlwaysRequireConfirmation.containsKey(userId)) { |
| onChange(true /* selfChange */, FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, userId); |
| } |
| return mFaceAlwaysRequireConfirmation.get(userId); |
| } |
| |
| public void notifyEnabledOnKeyguardCallbacks(int userId) { |
| List<EnabledOnKeyguardCallback> callbacks = mCallbacks; |
| for (int i = 0; i < callbacks.size(); i++) { |
| callbacks.get(i).notify(BiometricSourceType.FACE, |
| mFaceEnabledOnKeyguard.getOrDefault(userId, DEFAULT_KEYGUARD_ENABLED), |
| userId); |
| } |
| } |
| } |
| |
| final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient { |
| |
| private final IBiometricEnabledOnKeyguardCallback mCallback; |
| |
| EnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) { |
| mCallback = callback; |
| try { |
| mCallback.asBinder().linkToDeath(EnabledOnKeyguardCallback.this, 0); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Unable to linkToDeath", e); |
| } |
| } |
| |
| void notify(BiometricSourceType sourceType, boolean enabled, int userId) { |
| try { |
| mCallback.onChanged(sourceType, enabled, userId); |
| } catch (DeadObjectException e) { |
| Slog.w(TAG, "Death while invoking notify", e); |
| mEnabledOnKeyguardCallbacks.remove(this); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Failed to invoke onChanged", e); |
| } |
| } |
| |
| @Override |
| public void binderDied() { |
| Slog.e(TAG, "Enabled callback binder died"); |
| mEnabledOnKeyguardCallbacks.remove(this); |
| } |
| } |
| |
| // Wrap the client's receiver so we can do things with the BiometricDialog first |
| @VisibleForTesting |
| final IBiometricServiceReceiverInternal mInternalReceiver = |
| new IBiometricServiceReceiverInternal.Stub() { |
| @Override |
| public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token) |
| throws RemoteException { |
| SomeArgs args = SomeArgs.obtain(); |
| args.arg1 = requireConfirmation; |
| args.arg2 = token; |
| mHandler.obtainMessage(MSG_ON_AUTHENTICATION_SUCCEEDED, args).sendToTarget(); |
| } |
| |
| @Override |
| public void onAuthenticationFailed() |
| throws RemoteException { |
| Slog.v(TAG, "onAuthenticationFailed"); |
| mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED).sendToTarget(); |
| } |
| |
| @Override |
| public void onError(int cookie, int modality, int error, int vendorCode) |
| throws RemoteException { |
| // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are |
| // soft errors and we should allow the user to try authenticating again instead of |
| // dismissing BiometricPrompt. |
| if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) { |
| SomeArgs args = SomeArgs.obtain(); |
| args.argi1 = modality; |
| args.argi2 = error; |
| args.argi3 = vendorCode; |
| mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, args).sendToTarget(); |
| } else { |
| SomeArgs args = SomeArgs.obtain(); |
| args.argi1 = cookie; |
| args.argi2 = modality; |
| args.argi3 = error; |
| args.argi4 = vendorCode; |
| mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget(); |
| } |
| } |
| |
| @Override |
| public void onAcquired(int acquiredInfo, String message) throws RemoteException { |
| SomeArgs args = SomeArgs.obtain(); |
| args.argi1 = acquiredInfo; |
| args.arg1 = message; |
| mHandler.obtainMessage(MSG_ON_ACQUIRED, args).sendToTarget(); |
| } |
| |
| @Override |
| public void onDialogDismissed(int reason) throws RemoteException { |
| mHandler.obtainMessage(MSG_ON_DISMISSED, reason, 0 /* arg2 */).sendToTarget(); |
| } |
| |
| @Override |
| public void onTryAgainPressed() { |
| mHandler.sendEmptyMessage(MSG_ON_TRY_AGAIN_PRESSED); |
| } |
| |
| @Override |
| public void onDeviceCredentialPressed() { |
| mHandler.sendEmptyMessage(MSG_ON_DEVICE_CREDENTIAL_PRESSED); |
| } |
| }; |
| |
| |
| /** |
| * This is just a pass-through service that wraps Fingerprint, Iris, Face services. This service |
| * should not carry any state. The reality is we need to keep a tiny amount of state so that |
| * cancelAuthentication() can go to the right place. |
| */ |
| private final class BiometricServiceWrapper extends IBiometricService.Stub { |
| @Override // Binder call |
| public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) { |
| checkInternalPermission(); |
| |
| SomeArgs args = SomeArgs.obtain(); |
| args.argi1 = cookie; |
| args.arg1 = requireConfirmation; |
| args.argi2 = userId; |
| mHandler.obtainMessage(MSG_ON_READY_FOR_AUTHENTICATION, args).sendToTarget(); |
| } |
| |
| @Override // Binder call |
| public void authenticate(IBinder token, long sessionId, int userId, |
| IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle) |
| throws RemoteException { |
| final int callingUid = Binder.getCallingUid(); |
| final int callingPid = Binder.getCallingPid(); |
| final int callingUserId = UserHandle.getCallingUserId(); |
| |
| // In the BiometricServiceBase, check do the AppOps and foreground check. |
| if (userId == callingUserId) { |
| // Check the USE_BIOMETRIC permission here. |
| checkPermission(); |
| } else { |
| // Only allow internal clients to authenticate with a different userId |
| Slog.w(TAG, "User " + callingUserId + " is requesting authentication of userid: " |
| + userId); |
| checkInternalPermission(); |
| } |
| |
| if (token == null || receiver == null || opPackageName == null || bundle == null) { |
| Slog.e(TAG, "Unable to authenticate, one or more null arguments"); |
| return; |
| } |
| |
| if (!Utils.isValidAuthenticatorConfig(bundle)) { |
| throw new SecurityException("Invalid authenticator configuration"); |
| } |
| |
| Utils.combineAuthenticatorBundles(bundle); |
| |
| // Check the usage of this in system server. Need to remove this check if it becomes a |
| // public API. |
| final boolean useDefaultTitle = |
| bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false); |
| if (useDefaultTitle) { |
| checkInternalPermission(); |
| // Set the default title if necessary |
| if (TextUtils.isEmpty(bundle.getCharSequence(BiometricPrompt.KEY_TITLE))) { |
| bundle.putCharSequence(BiometricPrompt.KEY_TITLE, |
| getContext().getString(R.string.biometric_dialog_default_title)); |
| } |
| } |
| |
| SomeArgs args = SomeArgs.obtain(); |
| args.arg1 = token; |
| args.arg2 = sessionId; |
| args.argi1 = userId; |
| args.arg3 = receiver; |
| args.arg4 = opPackageName; |
| args.arg5 = bundle; |
| args.argi2 = callingUid; |
| args.argi3 = callingPid; |
| args.argi4 = callingUserId; |
| |
| mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget(); |
| } |
| |
| @Override // Binder call |
| public void cancelAuthentication(IBinder token, String opPackageName) |
| throws RemoteException { |
| checkPermission(); |
| |
| SomeArgs args = SomeArgs.obtain(); |
| args.arg1 = token; |
| args.arg2 = opPackageName; |
| mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget(); |
| } |
| |
| @Override // Binder call |
| public int canAuthenticate(String opPackageName, int userId, |
| @Authenticators.Types int authenticators) { |
| Slog.d(TAG, "canAuthenticate: User=" + userId |
| + ", Caller=" + UserHandle.getCallingUserId() |
| + ", Authenticators=" + authenticators); |
| |
| if (userId != UserHandle.getCallingUserId()) { |
| checkInternalPermission(); |
| } else { |
| checkPermission(); |
| } |
| |
| |
| if (!Utils.isValidAuthenticatorConfig(authenticators)) { |
| throw new SecurityException("Invalid authenticator configuration"); |
| } |
| |
| final Bundle bundle = new Bundle(); |
| bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); |
| |
| int biometricConstantsResult = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| biometricConstantsResult = |
| checkAndGetAuthenticators(userId, bundle, opPackageName).second; |
| if (biometricConstantsResult != BiometricConstants.BIOMETRIC_SUCCESS |
| && Utils.isDeviceCredentialAllowed(bundle)) { |
| // If there's an issue with biometrics, but device credential is allowed and |
| // set up, return SUCCESS. If device credential isn't set up either, return |
| // ERROR_NO_DEVICE_CREDENTIAL. |
| if (mTrustManager.isDeviceSecure(userId)) { |
| biometricConstantsResult = BiometricConstants.BIOMETRIC_SUCCESS; |
| } else { |
| biometricConstantsResult = |
| BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL; |
| } |
| } |
| |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| |
| return Utils.biometricConstantsToBiometricManager(biometricConstantsResult); |
| } |
| |
| @Override |
| public boolean hasEnrolledBiometrics(int userId, String opPackageName) { |
| checkInternalPermission(); |
| |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| for (AuthenticatorWrapper authenticator : mAuthenticators) { |
| if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) { |
| return true; |
| } |
| } |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| return false; |
| } |
| |
| @Override |
| public void registerAuthenticator(int id, int modality, int strength, |
| IBiometricAuthenticator authenticator) { |
| checkInternalPermission(); |
| |
| Slog.d(TAG, "Registering ID: " + id |
| + " Modality: " + modality |
| + " Strength: " + strength); |
| |
| if (authenticator == null) { |
| throw new IllegalArgumentException("Authenticator must not be null." |
| + " Did you forget to modify the core/res/res/values/xml overlay for" |
| + " config_biometric_sensors?"); |
| } |
| |
| if (strength != Authenticators.BIOMETRIC_STRONG |
| && strength != Authenticators.BIOMETRIC_WEAK) { |
| throw new IllegalStateException("Unsupported strength"); |
| } |
| |
| for (AuthenticatorWrapper wrapper : mAuthenticators) { |
| if (wrapper.id == id) { |
| throw new IllegalStateException("Cannot register duplicate authenticator"); |
| } |
| } |
| |
| // This happens infrequently enough, not worth caching. |
| final String[] configs = mInjector.getConfiguration(getContext()); |
| boolean idFound = false; |
| for (int i = 0; i < configs.length; i++) { |
| SensorConfig config = new SensorConfig(configs[i]); |
| if (config.mId == id) { |
| idFound = true; |
| break; |
| } |
| } |
| if (!idFound) { |
| throw new IllegalStateException("Cannot register unknown id"); |
| } |
| |
| mAuthenticators.add(new AuthenticatorWrapper(id, modality, strength, authenticator)); |
| } |
| |
| @Override // Binder call |
| public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) |
| throws RemoteException { |
| checkInternalPermission(); |
| mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback)); |
| try { |
| callback.onChanged(BiometricSourceType.FACE, |
| mSettingObserver.getFaceEnabledOnKeyguard(), |
| UserHandle.getCallingUserId()); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Remote exception", e); |
| } |
| } |
| |
| @Override // Binder call |
| public void setActiveUser(int userId) { |
| checkInternalPermission(); |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| for (AuthenticatorWrapper authenticator : mAuthenticators) { |
| authenticator.impl.setActiveUser(userId); |
| } |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| @Override // Binder call |
| public void resetLockout(byte[] token) { |
| checkInternalPermission(); |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| for (AuthenticatorWrapper authenticator : mAuthenticators) { |
| authenticator.impl.resetLockout(token); |
| } |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| } |
| |
| private void checkInternalPermission() { |
| getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL, |
| "Must have USE_BIOMETRIC_INTERNAL permission"); |
| } |
| |
| private void checkPermission() { |
| if (getContext().checkCallingOrSelfPermission(USE_FINGERPRINT) |
| != PackageManager.PERMISSION_GRANTED) { |
| getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC, |
| "Must have USE_BIOMETRIC permission"); |
| } |
| } |
| |
| /** |
| * Class for injecting dependencies into BiometricService. |
| * TODO(b/141025588): Replace with a dependency injection framework (e.g. Guice, Dagger). |
| */ |
| @VisibleForTesting |
| public static class Injector { |
| |
| @VisibleForTesting |
| public IActivityManager getActivityManagerService() { |
| return ActivityManager.getService(); |
| } |
| |
| @VisibleForTesting |
| public ITrustManager getTrustManager() { |
| return ITrustManager.Stub.asInterface(ServiceManager.getService(Context.TRUST_SERVICE)); |
| } |
| |
| @VisibleForTesting |
| public IStatusBarService getStatusBarService() { |
| return IStatusBarService.Stub.asInterface( |
| ServiceManager.getService(Context.STATUS_BAR_SERVICE)); |
| } |
| |
| /** |
| * Allows to mock SettingObserver for testing. |
| */ |
| @VisibleForTesting |
| public SettingObserver getSettingObserver(Context context, Handler handler, |
| List<EnabledOnKeyguardCallback> callbacks) { |
| return new SettingObserver(context, handler, callbacks); |
| } |
| |
| @VisibleForTesting |
| public KeyStore getKeyStore() { |
| return KeyStore.getInstance(); |
| } |
| |
| /** |
| * Allows to enable/disable debug logs. |
| */ |
| @VisibleForTesting |
| public boolean isDebugEnabled(Context context, int userId) { |
| return Utils.isDebugEnabled(context, userId); |
| } |
| |
| /** |
| * Allows to stub publishBinderService(...) for testing. |
| */ |
| @VisibleForTesting |
| public void publishBinderService(BiometricService service, IBiometricService.Stub impl) { |
| service.publishBinderService(Context.BIOMETRIC_SERVICE, impl); |
| } |
| |
| /** |
| * Allows to mock BiometricStrengthController for testing. |
| */ |
| @VisibleForTesting |
| public BiometricStrengthController getBiometricStrengthController( |
| BiometricService service) { |
| return new BiometricStrengthController(service); |
| } |
| |
| /** |
| * Allows to test with various device sensor configurations. |
| * @param context System Server context |
| * @return the sensor configuration from core/res/res/values/config.xml |
| */ |
| @VisibleForTesting |
| public String[] getConfiguration(Context context) { |
| return context.getResources().getStringArray(R.array.config_biometric_sensors); |
| } |
| } |
| |
| /** |
| * Initializes the system service. |
| * <p> |
| * Subclasses must define a single argument constructor that accepts the context |
| * and passes it to super. |
| * </p> |
| * |
| * @param context The system server context. |
| */ |
| public BiometricService(Context context) { |
| this(context, new Injector()); |
| } |
| |
| @VisibleForTesting |
| BiometricService(Context context, Injector injector) { |
| super(context); |
| |
| mInjector = injector; |
| mImpl = new BiometricServiceWrapper(); |
| mEnabledOnKeyguardCallbacks = new ArrayList<>(); |
| mSettingObserver = mInjector.getSettingObserver(context, mHandler, |
| mEnabledOnKeyguardCallbacks); |
| |
| try { |
| injector.getActivityManagerService().registerUserSwitchObserver( |
| new UserSwitchObserver() { |
| @Override |
| public void onUserSwitchComplete(int newUserId) { |
| mSettingObserver.updateContentObserver(); |
| mSettingObserver.notifyEnabledOnKeyguardCallbacks(newUserId); |
| } |
| }, BiometricService.class.getName() |
| ); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Failed to register user switch observer", e); |
| } |
| } |
| |
| @Override |
| public void onStart() { |
| mKeyStore = mInjector.getKeyStore(); |
| mStatusBarService = mInjector.getStatusBarService(); |
| mTrustManager = mInjector.getTrustManager(); |
| mInjector.publishBinderService(this, mImpl); |
| mBiometricStrengthController = mInjector.getBiometricStrengthController(this); |
| mBiometricStrengthController.startListening(); |
| } |
| |
| /** |
| * Checks if there are any available biometrics, and returns the modality. This method also |
| * returns errors through the callback (no biometric feature, hardware not detected, no |
| * templates enrolled, etc). This service must not start authentication if errors are sent. |
| * |
| * @param userId the user to check for |
| * @param bundle passed from {@link BiometricPrompt} |
| * @param opPackageName see {@link android.app.AppOpsManager} |
| * |
| * @return A pair [Modality, Error] with Modality being one of |
| * {@link BiometricAuthenticator#TYPE_NONE}, |
| * {@link BiometricAuthenticator#TYPE_FINGERPRINT}, |
| * {@link BiometricAuthenticator#TYPE_IRIS}, |
| * {@link BiometricAuthenticator#TYPE_FACE} |
| * and the error containing one of the {@link BiometricConstants} errors. |
| * |
| * TODO(kchyn): Update this to handle DEVICE_CREDENTIAL better, reduce duplicate code in callers |
| */ |
| private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle, |
| String opPackageName) throws RemoteException { |
| if (!Utils.isBiometricAllowed(bundle) |
| && Utils.isDeviceCredentialAllowed(bundle) |
| && !mTrustManager.isDeviceSecure(userId)) { |
| // If only device credential is being checked, and the user doesn't have one set up |
| return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL); |
| } |
| |
| // Assuming that authenticators are listed in priority-order, the rest of this function |
| // will attempt to find the first authenticator that's as strong or stronger than the |
| // requested strength, available, enrolled, and enabled. The tricky part is returning the |
| // correct error. Error strings that are modality-specific should also respect the |
| // priority-order. |
| |
| // Find first authenticator that's strong enough, detected, enrolled, and enabled. |
| boolean hasSufficientStrength = false; |
| boolean isHardwareDetected = false; |
| boolean hasTemplatesEnrolled = false; |
| boolean enabledForApps = false; |
| |
| int modality = TYPE_NONE; |
| int firstHwAvailable = TYPE_NONE; |
| for (AuthenticatorWrapper authenticator : mAuthenticators) { |
| final int actualStrength = authenticator.getActualStrength(); |
| final int requestedStrength = Utils.getPublicBiometricStrength(bundle); |
| if (Utils.isAtLeastStrength(actualStrength, requestedStrength)) { |
| hasSufficientStrength = true; |
| modality = authenticator.modality; |
| if (authenticator.impl.isHardwareDetected(opPackageName)) { |
| isHardwareDetected = true; |
| if (firstHwAvailable == TYPE_NONE) { |
| // Store the first one since we want to return the error in correct |
| // priority order. |
| firstHwAvailable = modality; |
| } |
| if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) { |
| hasTemplatesEnrolled = true; |
| if (isEnabledForApp(modality, userId)) { |
| enabledForApps = true; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId |
| + " isHardwareDetected=" + isHardwareDetected |
| + " hasTemplatesEnrolled=" + hasTemplatesEnrolled |
| + " enabledForApps=" + enabledForApps); |
| |
| // Check error conditions |
| if (!hasSufficientStrength) { |
| return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); |
| } else if (!isHardwareDetected) { |
| return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); |
| } else if (!hasTemplatesEnrolled) { |
| // Return the modality here so the correct error string can be sent. This error is |
| // preferred over !enabledForApps |
| return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); |
| } else if (!enabledForApps) { |
| return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); |
| } |
| |
| return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); |
| } |
| |
| private boolean isEnabledForApp(int modality, int userId) { |
| switch (modality) { |
| case TYPE_FINGERPRINT: |
| return true; |
| case TYPE_IRIS: |
| return true; |
| case TYPE_FACE: |
| return mSettingObserver.getFaceEnabledForApps(userId); |
| default: |
| Slog.w(TAG, "Unsupported modality: " + modality); |
| return false; |
| } |
| } |
| |
| private void logDialogDismissed(int reason) { |
| if (reason == BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED) { |
| // Explicit auth, authentication confirmed. |
| // Latency in this case is authenticated -> confirmed. <Biometric>Service |
| // should have the first half (first acquired -> authenticated). |
| final long latency = System.currentTimeMillis() |
| - mCurrentAuthSession.mAuthenticatedTimeMs; |
| |
| if (LoggableMonitor.DEBUG) { |
| Slog.v(LoggableMonitor.TAG, "Confirmed! Modality: " + statsModality() |
| + ", User: " + mCurrentAuthSession.mUserId |
| + ", IsCrypto: " + mCurrentAuthSession.isCrypto() |
| + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT |
| + ", RequireConfirmation: " |
| + mCurrentAuthSession.mRequireConfirmation |
| + ", State: " + StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED |
| + ", Latency: " + latency); |
| } |
| |
| StatsLog.write(StatsLog.BIOMETRIC_AUTHENTICATED, |
| statsModality(), |
| mCurrentAuthSession.mUserId, |
| mCurrentAuthSession.isCrypto(), |
| BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, |
| mCurrentAuthSession.mRequireConfirmation, |
| StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED, |
| latency, |
| mInjector.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId)); |
| } else { |
| |
| final long latency = System.currentTimeMillis() - mCurrentAuthSession.mStartTimeMs; |
| |
| int error = reason == BiometricPrompt.DISMISSED_REASON_NEGATIVE |
| ? BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON |
| : reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL |
| ? BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED |
| : 0; |
| if (LoggableMonitor.DEBUG) { |
| Slog.v(LoggableMonitor.TAG, "Dismissed! Modality: " + statsModality() |
| + ", User: " + mCurrentAuthSession.mUserId |
| + ", IsCrypto: " + mCurrentAuthSession.isCrypto() |
| + ", Action: " + BiometricsProtoEnums.ACTION_AUTHENTICATE |
| + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT |
| + ", Error: " + error |
| + ", Latency: " + latency); |
| } |
| // Auth canceled |
| StatsLog.write(StatsLog.BIOMETRIC_ERROR_OCCURRED, |
| statsModality(), |
| mCurrentAuthSession.mUserId, |
| mCurrentAuthSession.isCrypto(), |
| BiometricsProtoEnums.ACTION_AUTHENTICATE, |
| BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, |
| error, |
| 0 /* vendorCode */, |
| mInjector.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId), |
| latency); |
| } |
| } |
| |
| private int statsModality() { |
| int modality = 0; |
| if (mCurrentAuthSession == null) { |
| return BiometricsProtoEnums.MODALITY_UNKNOWN; |
| } |
| if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FINGERPRINT) |
| != 0) { |
| modality |= BiometricsProtoEnums.MODALITY_FINGERPRINT; |
| } |
| if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_IRIS) != 0) { |
| modality |= BiometricsProtoEnums.MODALITY_IRIS; |
| } |
| if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FACE) != 0) { |
| modality |= BiometricsProtoEnums.MODALITY_FACE; |
| } |
| return modality; |
| } |
| |
| private void handleAuthenticationSucceeded(boolean requireConfirmation, byte[] token) { |
| try { |
| // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded |
| // after user dismissed/canceled dialog). |
| if (mCurrentAuthSession == null) { |
| Slog.e(TAG, "handleAuthenticationSucceeded: Auth session is null"); |
| return; |
| } |
| |
| // Store the auth token and submit it to keystore after the dialog is confirmed / |
| // animating away. |
| mCurrentAuthSession.mTokenEscrow = token; |
| if (!requireConfirmation) { |
| mCurrentAuthSession.mState = STATE_AUTHENTICATED_PENDING_SYSUI; |
| } else { |
| mCurrentAuthSession.mAuthenticatedTimeMs = System.currentTimeMillis(); |
| mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM; |
| } |
| |
| // Notify SysUI that the biometric has been authenticated. SysUI already knows |
| // the implicit/explicit state and will react accordingly. |
| mStatusBarService.onBiometricAuthenticated(); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } |
| } |
| |
| private void handleAuthenticationRejected() { |
| Slog.v(TAG, "handleAuthenticationRejected()"); |
| try { |
| // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded |
| // after user dismissed/canceled dialog). |
| if (mCurrentAuthSession == null) { |
| Slog.e(TAG, "handleAuthenticationRejected: Auth session is null"); |
| return; |
| } |
| |
| mStatusBarService.onBiometricError(TYPE_NONE, |
| BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */); |
| |
| // TODO: This logic will need to be updated if BP is multi-modal |
| if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) { |
| // Pause authentication. onBiometricAuthenticated(false) causes the |
| // dialog to show a "try again" button for passive modalities. |
| mCurrentAuthSession.mState = STATE_AUTH_PAUSED; |
| } |
| |
| mCurrentAuthSession.mClientReceiver.onAuthenticationFailed(); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } |
| } |
| |
| private void handleAuthenticationTimedOut(int modality, int error, int vendorCode) { |
| Slog.v(TAG, String.format("handleAuthenticationTimedOut(%d, %d, %d)", modality, error, |
| vendorCode)); |
| try { |
| // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded |
| // after user dismissed/canceled dialog). |
| if (mCurrentAuthSession == null) { |
| Slog.e(TAG, "handleAuthenticationTimedOut: Auth session is null"); |
| return; |
| } |
| |
| mStatusBarService.onBiometricError(modality, error, vendorCode); |
| mCurrentAuthSession.mState = STATE_AUTH_PAUSED; |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } |
| } |
| |
| private void handleOnError(int cookie, int modality, int error, int vendorCode) { |
| Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie); |
| // Errors can either be from the current auth session or the pending auth session. |
| // The pending auth session may receive errors such as ERROR_LOCKOUT before |
| // it becomes the current auth session. Similarly, the current auth session may |
| // receive errors such as ERROR_CANCELED while the pending auth session is preparing |
| // to be started. Thus we must match error messages with their cookies to be sure |
| // of their intended receivers. |
| try { |
| if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) { |
| mCurrentAuthSession.mErrorEscrow = error; |
| mCurrentAuthSession.mVendorCodeEscrow = vendorCode; |
| |
| if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { |
| final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT |
| || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; |
| if (mCurrentAuthSession.isAllowDeviceCredential() && errorLockout) { |
| // SystemUI handles transition from biometric to device credential. |
| mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL; |
| mStatusBarService.onBiometricError(modality, error, vendorCode); |
| } else if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { |
| mStatusBarService.hideAuthenticationDialog(); |
| // TODO: If multiple authenticators are simultaneously running, this will |
| // need to be modified. Send the error to the client here, instead of doing |
| // a round trip to SystemUI. |
| mCurrentAuthSession.mClientReceiver.onError(modality, error, vendorCode); |
| mCurrentAuthSession = null; |
| } else { |
| mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI; |
| mStatusBarService.onBiometricError(modality, error, vendorCode); |
| } |
| } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) { |
| // In the "try again" state, we should forward canceled errors to |
| // the client and and clean up. The only error we should get here is |
| // ERROR_CANCELED due to another client kicking us out. |
| mCurrentAuthSession.mClientReceiver.onError(modality, error, vendorCode); |
| mStatusBarService.hideAuthenticationDialog(); |
| mCurrentAuthSession = null; |
| } else if (mCurrentAuthSession.mState == STATE_SHOWING_DEVICE_CREDENTIAL) { |
| Slog.d(TAG, "Biometric canceled, ignoring from state: " |
| + mCurrentAuthSession.mState); |
| } else { |
| Slog.e(TAG, "Impossible session error state: " |
| + mCurrentAuthSession.mState); |
| } |
| } else if (mPendingAuthSession != null |
| && mPendingAuthSession.containsCookie(cookie)) { |
| if (mPendingAuthSession.mState == STATE_AUTH_CALLED) { |
| // If any error is received while preparing the auth session (lockout, etc), |
| // and if device credential is allowed, just show the credential UI. |
| if (mPendingAuthSession.isAllowDeviceCredential()) { |
| @Authenticators.Types int authenticators = |
| mPendingAuthSession.mBundle.getInt( |
| BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0); |
| // Disallow biometric and notify SystemUI to show the authentication prompt. |
| authenticators &= ~Authenticators.BIOMETRIC_WEAK; |
| mPendingAuthSession.mBundle.putInt( |
| BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, |
| authenticators); |
| |
| mCurrentAuthSession = mPendingAuthSession; |
| mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL; |
| mPendingAuthSession = null; |
| |
| mStatusBarService.showAuthenticationDialog( |
| mCurrentAuthSession.mBundle, |
| mInternalReceiver, |
| 0 /* biometricModality */, |
| false /* requireConfirmation */, |
| mCurrentAuthSession.mUserId, |
| mCurrentAuthSession.mOpPackageName); |
| } else { |
| mPendingAuthSession.mClientReceiver.onError(modality, error, vendorCode); |
| mPendingAuthSession = null; |
| } |
| } else { |
| Slog.e(TAG, "Impossible pending session error state: " |
| + mPendingAuthSession.mState); |
| } |
| } else { |
| Slog.e(TAG, "Unknown cookie: " + cookie); |
| } |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } |
| } |
| |
| private void handleOnAcquired(int acquiredInfo, String message) { |
| // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded |
| // after user dismissed/canceled dialog). |
| if (mCurrentAuthSession == null) { |
| Slog.e(TAG, "onAcquired(): Auth session is null"); |
| return; |
| } |
| |
| if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { |
| if (message == null) { |
| Slog.w(TAG, "Ignoring null message: " + acquiredInfo); |
| return; |
| } |
| try { |
| mStatusBarService.onBiometricHelp(message); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } |
| } |
| } |
| |
| private void handleOnDismissed(int reason) { |
| if (mCurrentAuthSession == null) { |
| Slog.e(TAG, "onDismissed: " + reason + ", auth session null"); |
| return; |
| } |
| |
| logDialogDismissed(reason); |
| |
| try { |
| switch (reason) { |
| case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED: |
| case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED: |
| case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED: |
| if (mCurrentAuthSession.mTokenEscrow != null) { |
| mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow); |
| } |
| mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); |
| break; |
| |
| case BiometricPrompt.DISMISSED_REASON_NEGATIVE: |
| mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason); |
| // Cancel authentication. Skip the token/package check since we are cancelling |
| // from system server. The interface is permission protected so this is fine. |
| cancelInternal(null /* token */, null /* package */, false /* fromClient */); |
| break; |
| |
| case BiometricPrompt.DISMISSED_REASON_USER_CANCEL: |
| mCurrentAuthSession.mClientReceiver.onError( |
| mCurrentAuthSession.mModality, |
| BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, |
| 0 /* vendorCode */ |
| ); |
| // Cancel authentication. Skip the token/package check since we are cancelling |
| // from system server. The interface is permission protected so this is fine. |
| cancelInternal(null /* token */, null /* package */, false /* fromClient */); |
| break; |
| |
| case BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED: |
| case BiometricPrompt.DISMISSED_REASON_ERROR: |
| mCurrentAuthSession.mClientReceiver.onError( |
| mCurrentAuthSession.mModality, |
| mCurrentAuthSession.mErrorEscrow, |
| mCurrentAuthSession.mVendorCodeEscrow |
| ); |
| break; |
| |
| default: |
| Slog.w(TAG, "Unhandled reason: " + reason); |
| break; |
| } |
| |
| // Dialog is gone, auth session is done. |
| mCurrentAuthSession = null; |
| |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } |
| } |
| |
| private void handleOnTryAgainPressed() { |
| Slog.d(TAG, "onTryAgainPressed"); |
| // No need to check permission, since it can only be invoked by SystemUI |
| // (or system server itself). |
| authenticateInternal(mCurrentAuthSession.mToken, |
| mCurrentAuthSession.mSessionId, |
| mCurrentAuthSession.mUserId, |
| mCurrentAuthSession.mClientReceiver, |
| mCurrentAuthSession.mOpPackageName, |
| mCurrentAuthSession.mBundle, |
| mCurrentAuthSession.mCallingUid, |
| mCurrentAuthSession.mCallingPid, |
| mCurrentAuthSession.mCallingUserId, |
| mCurrentAuthSession.mModality); |
| } |
| |
| private void handleOnDeviceCredentialPressed() { |
| Slog.d(TAG, "onDeviceCredentialPressed"); |
| if (mCurrentAuthSession == null) { |
| Slog.e(TAG, "Auth session null"); |
| return; |
| } |
| |
| // Cancel authentication. Skip the token/package check since we are cancelling |
| // from system server. The interface is permission protected so this is fine. |
| cancelInternal(null /* token */, null /* package */, false /* fromClient */); |
| |
| mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL; |
| } |
| |
| /** |
| * Invoked when each service has notified that its client is ready to be started. When |
| * all biometrics are ready, this invokes the SystemUI dialog through StatusBar. |
| */ |
| private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmation, |
| int userId) { |
| if (mPendingAuthSession == null) { |
| // Only should happen if a biometric was locked out when authenticate() was invoked. |
| // In that case, if device credentials are allowed, the UI is already showing. If not |
| // allowed, the error has already been returned to the caller. |
| Slog.w(TAG, "Pending auth session null"); |
| return; |
| } |
| |
| Iterator it = mPendingAuthSession.mModalitiesWaiting.entrySet().iterator(); |
| while (it.hasNext()) { |
| Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); |
| if (pair.getValue() == cookie) { |
| mPendingAuthSession.mModalitiesMatched.put(pair.getKey(), pair.getValue()); |
| mPendingAuthSession.mModalitiesWaiting.remove(pair.getKey()); |
| Slog.d(TAG, "Matched cookie: " + cookie + ", " |
| + mPendingAuthSession.mModalitiesWaiting.size() + " remaining"); |
| break; |
| } |
| } |
| |
| if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) { |
| final boolean continuing = mCurrentAuthSession != null |
| && mCurrentAuthSession.mState == STATE_AUTH_PAUSED; |
| |
| mCurrentAuthSession = mPendingAuthSession; |
| |
| // Time starts when lower layers are ready to start the client. |
| mCurrentAuthSession.mStartTimeMs = System.currentTimeMillis(); |
| mPendingAuthSession = null; |
| |
| mCurrentAuthSession.mState = STATE_AUTH_STARTED; |
| int modality = TYPE_NONE; |
| it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator(); |
| while (it.hasNext()) { |
| Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); |
| boolean foundAuthenticator = false; |
| for (AuthenticatorWrapper authenticator : mAuthenticators) { |
| if (authenticator.modality == pair.getKey()) { |
| foundAuthenticator = true; |
| try { |
| authenticator.impl.startPreparedClient(pair.getValue()); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } |
| } |
| } |
| if (!foundAuthenticator) { |
| Slog.e(TAG, "Unknown modality: " + pair.getKey()); |
| } |
| modality |= pair.getKey(); |
| } |
| |
| if (!continuing) { |
| try { |
| mStatusBarService.showAuthenticationDialog(mCurrentAuthSession.mBundle, |
| mInternalReceiver, modality, requireConfirmation, userId, |
| mCurrentAuthSession.mOpPackageName); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } |
| } |
| } |
| } |
| |
| private void handleAuthenticate(IBinder token, long sessionId, int userId, |
| IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, |
| int callingUid, int callingPid, int callingUserId) { |
| |
| mHandler.post(() -> { |
| int modality = TYPE_NONE; |
| int result; |
| |
| try { |
| final Pair<Integer, Integer> pair = checkAndGetAuthenticators(userId, bundle, |
| opPackageName); |
| modality = pair.first; |
| result = pair.second; |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| result = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; |
| } |
| |
| try { |
| if (result == BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL) { |
| // If the app allowed device credential but the user hasn't set it up yet, |
| // return this error. |
| receiver.onError(modality, result, 0 /* vendorCode */); |
| } else if (result != BiometricConstants.BIOMETRIC_SUCCESS) { |
| if (Utils.isDeviceCredentialAllowed(bundle)) { |
| // If there's a problem with biometrics but device credential is allowed, |
| // only show credential UI. |
| bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, |
| Authenticators.DEVICE_CREDENTIAL); |
| authenticateInternal(token, sessionId, userId, receiver, opPackageName, |
| bundle, callingUid, callingPid, callingUserId, modality); |
| } else { |
| receiver.onError(modality, result, 0 /* vendorCode */); |
| } |
| } else { |
| // BIOMETRIC_SUCCESS, proceed to authentication |
| authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, |
| callingUid, callingPid, callingUserId, modality); |
| } |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } |
| }); |
| } |
| |
| /** |
| * handleAuthenticate() (above) which is called from BiometricPrompt determines which |
| * modality/modalities to start authenticating with. authenticateInternal() should only be |
| * used for: |
| * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is, |
| * invoked, shortly after which BiometricPrompt is shown and authentication starts |
| * 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown |
| * and the user has pressed "try again" |
| */ |
| private void authenticateInternal(IBinder token, long sessionId, int userId, |
| IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, |
| int callingUid, int callingPid, int callingUserId, int modality) { |
| boolean requireConfirmation = bundle.getBoolean( |
| BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */); |
| if ((modality & TYPE_FACE) != 0) { |
| // Check if the user has forced confirmation to be required in Settings. |
| requireConfirmation = requireConfirmation |
| || mSettingObserver.getFaceAlwaysRequireConfirmation(userId); |
| } |
| // Generate random cookies to pass to the services that should prepare to start |
| // authenticating. Store the cookie here and wait for all services to "ack" |
| // with the cookie. Once all cookies are received, we can show the prompt |
| // and let the services start authenticating. The cookie should be non-zero. |
| final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; |
| final @Authenticators.Types int authenticators = bundle.getInt( |
| BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0); |
| Slog.d(TAG, "Creating auth session. Modality: " + modality |
| + ", cookie: " + cookie |
| + ", authenticators: " + authenticators); |
| final HashMap<Integer, Integer> modalities = new HashMap<>(); |
| |
| // If it's only device credential, we don't need to wait - LockSettingsService is |
| // always ready to check credential (SystemUI invokes that path). |
| if ((authenticators & ~Authenticators.DEVICE_CREDENTIAL) != 0) { |
| modalities.put(modality, cookie); |
| } |
| mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId, |
| receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, |
| modality, requireConfirmation); |
| |
| try { |
| if (authenticators == Authenticators.DEVICE_CREDENTIAL) { |
| mPendingAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL; |
| mCurrentAuthSession = mPendingAuthSession; |
| mPendingAuthSession = null; |
| |
| mStatusBarService.showAuthenticationDialog( |
| mCurrentAuthSession.mBundle, |
| mInternalReceiver, |
| 0 /* biometricModality */, |
| false /* requireConfirmation */, |
| mCurrentAuthSession.mUserId, |
| mCurrentAuthSession.mOpPackageName); |
| } else { |
| mPendingAuthSession.mState = STATE_AUTH_CALLED; |
| for (AuthenticatorWrapper authenticator : mAuthenticators) { |
| // TODO(b/141025588): use ids instead of modalities to avoid ambiguity. |
| if (authenticator.modality == modality) { |
| authenticator.impl.prepareForAuthentication(requireConfirmation, token, |
| sessionId, userId, mInternalReceiver, opPackageName, cookie, |
| callingUid, callingPid, callingUserId); |
| break; |
| } |
| } |
| } |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Unable to start authentication", e); |
| } |
| } |
| |
| private void handleCancelAuthentication(IBinder token, String opPackageName) { |
| if (token == null || opPackageName == null) { |
| Slog.e(TAG, "Unable to cancel, one or more null arguments"); |
| return; |
| } |
| |
| if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { |
| // We need to check the current authenticators state. If we're pending confirm |
| // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client, |
| // since we won't be getting an onError from the driver. |
| try { |
| // Send error to client |
| mCurrentAuthSession.mClientReceiver.onError( |
| mCurrentAuthSession.mModality, |
| BiometricConstants.BIOMETRIC_ERROR_CANCELED, |
| 0 /* vendorCode */ |
| ); |
| mCurrentAuthSession = null; |
| mStatusBarService.hideAuthenticationDialog(); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception", e); |
| } |
| } else { |
| cancelInternal(token, opPackageName, true /* fromClient */); |
| } |
| } |
| |
| void cancelInternal(IBinder token, String opPackageName, boolean fromClient) { |
| final int callingUid = Binder.getCallingUid(); |
| final int callingPid = Binder.getCallingPid(); |
| final int callingUserId = UserHandle.getCallingUserId(); |
| |
| if (mCurrentAuthSession == null) { |
| Slog.w(TAG, "Skipping cancelInternal"); |
| return; |
| } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) { |
| Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState); |
| return; |
| } |
| |
| // TODO: For multiple modalities, send a single ERROR_CANCELED only when all |
| // drivers have canceled authentication. |
| for (AuthenticatorWrapper authenticator : mAuthenticators) { |
| if ((authenticator.modality & mCurrentAuthSession.mModality) != 0) { |
| try { |
| authenticator.impl.cancelAuthenticationFromService(token, opPackageName, |
| callingUid, callingPid, callingUserId, fromClient); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Unable to cancel authentication"); |
| } |
| } |
| } |
| } |
| } |