diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index d257c03772656..a696eeb6bcc7b 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -156,21 +156,21 @@ public class BiometricManager { } /** - * Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password) + * Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password) * * @param token an opaque token returned by password confirmation. * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void resetTimeout(byte[] token) { + public void resetLockout(byte[] token) { if (mService != null) { try { - mService.resetTimeout(token); + mService.resetLockout(token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } else { - Slog.w(TAG, "resetTimeout(): Service not connected"); + Slog.w(TAG, "resetLockout(): Service not connected"); } } diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index a20e2bf3d067a..4971911eb87c7 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -49,8 +49,8 @@ interface IBiometricService { // Client lifecycle is still managed in Service. void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId); - // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password) - void resetTimeout(in byte [] token); + // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password) + void resetLockout(in byte [] token); // TODO(b/123378871): Remove when moved. // CDCA needs to send results to BiometricService if it was invoked using BiometricPrompt's diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 7b7d513660e49..55b340f267a66 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -478,25 +478,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan return 0; } - /** - * Reset the lockout timer when asked to do so by keyguard. - * - * @param token an opaque token returned by password confirmation. - * @hide - */ - @RequiresPermission(MANAGE_BIOMETRIC) - public void resetTimeout(byte[] token) { - if (mService != null) { - try { - mService.resetTimeout(token); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - Log.w(TAG, "resetTimeout(): Service not connected!"); - } - } - /** * @hide */ diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index f67760a8fafc7..9609e99275c9e 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -86,8 +86,8 @@ interface IFaceService { // Gets the authenticator ID for face long getAuthenticatorId(String opPackageName); - // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password) - void resetTimeout(in byte [] cryptoToken); + // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password) + void resetLockout(in byte [] token); // Add a callback which gets notified when the face lockout period expired. void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback); diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 80d404d03c75d..d0622c88b4caf 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -18,7 +18,6 @@ package android.hardware.fingerprint; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.MANAGE_FINGERPRINT; -import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_FINGERPRINT; @@ -723,26 +722,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return 0; } - /** - * Reset the lockout timer when asked to do so by keyguard. - * - * @param token an opaque token returned by password confirmation. - * - * @hide - */ - @RequiresPermission(RESET_FINGERPRINT_LOCKOUT) - public void resetTimeout(byte[] token) { - if (mService != null) { - try { - mService.resetTimeout(token); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - Slog.w(TAG, "resetTimeout(): Service not connected!"); - } - } - /** * @hide */ diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index bd4acdbf4070f..7d4ac592a9dfe 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -34,7 +34,7 @@ public abstract class AuthenticationClient extends ClientMonitor { private long mOpId; public abstract int handleFailedAttempt(); - public abstract void resetFailedAttempts(); + public void resetFailedAttempts() {} public static final int LOCKOUT_NONE = 0; public static final int LOCKOUT_TIMED = 1; @@ -42,6 +42,11 @@ public abstract class AuthenticationClient extends ClientMonitor { private final boolean mRequireConfirmation; + // We need to track this state since it's possible for applications to request for + // authentication while the device is already locked out. In that case, the client is created + // but not started yet. The user shouldn't receive the error haptics in this case. + private boolean mStarted; + /** * This method is called when authentication starts. */ @@ -53,6 +58,11 @@ public abstract class AuthenticationClient extends ClientMonitor { */ public abstract void onStop(); + /** + * @return true if the framework should handle lockout. + */ + public abstract boolean shouldFrameworkHandleLockout(); + public AuthenticationClient(Context context, Metrics metrics, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId, @@ -90,6 +100,23 @@ public abstract class AuthenticationClient extends ClientMonitor { return mOpId != 0; } + @Override + public boolean onError(long deviceId, int error, int vendorCode) { + if (!shouldFrameworkHandleLockout()) { + switch (error) { + case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT: + case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT: + if (mStarted) { + vibrateError(); + } + break; + default: + break; + } + } + return super.onError(deviceId, error, vendorCode); + } + @Override public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList token) { @@ -113,7 +140,9 @@ public abstract class AuthenticationClient extends ClientMonitor { vibrateSuccess(); } result = true; - resetFailedAttempts(); + if (shouldFrameworkHandleLockout()) { + resetFailedAttempts(); + } onStop(); final byte[] byteToken = new byte[token.size()]; @@ -147,9 +176,10 @@ public abstract class AuthenticationClient extends ClientMonitor { if (listener != null) { vibrateError(); } + // Allow system-defined limit of number of attempts before giving up final int lockoutMode = handleFailedAttempt(); - if (lockoutMode != LOCKOUT_NONE) { + if (lockoutMode != LOCKOUT_NONE && shouldFrameworkHandleLockout()) { Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode(" + lockoutMode + ")"); stop(false); @@ -170,7 +200,7 @@ public abstract class AuthenticationClient extends ClientMonitor { } } } - result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode + result = lockoutMode != LOCKOUT_NONE; // in a lockout mode } } catch (RemoteException e) { Slog.e(getLogTag(), "Remote exception", e); @@ -184,6 +214,7 @@ public abstract class AuthenticationClient extends ClientMonitor { */ @Override public int start() { + mStarted = true; onStart(); try { final int result = getDaemonWrapper().authenticate(mOpId, getGroupId()); @@ -209,6 +240,8 @@ public abstract class AuthenticationClient extends ClientMonitor { return 0; } + mStarted = false; + onStop(); try { diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index bca84f7b7217f..8ab59e4c7c6bb 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1029,7 +1029,7 @@ public class BiometricService extends SystemService { } @Override // Binder call - public void resetTimeout(byte[] token) { + public void resetLockout(byte[] token) { checkInternalPermission(); final long ident = Binder.clearCallingIdentity(); try { @@ -1037,7 +1037,7 @@ public class BiometricService extends SystemService { mFingerprintService.resetTimeout(token); } if (mFaceService != null) { - mFaceService.resetTimeout(token); + mFaceService.resetLockout(token); } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index b93db1a6857aa..92a8d9359b587 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -20,17 +20,12 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREG import android.app.ActivityManager; import android.app.ActivityTaskManager; -import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.IActivityTaskManager; -import android.app.PendingIntent; import android.app.SynchronousUserSwitchObserver; import android.app.TaskStackListener; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; @@ -54,8 +49,6 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; -import android.util.SparseBooleanArray; -import android.util.SparseIntArray; import android.util.StatsLog; import com.android.internal.logging.MetricsLogger; @@ -82,28 +75,21 @@ public abstract class BiometricServiceBase extends SystemService private static final boolean CLEANUP_UNKNOWN_TEMPLATES = true; private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user"; private static final int MSG_USER_SWITCHING = 10; - private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30 * 1000; private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms private final Context mContext; private final String mKeyguardPackage; - private final SparseBooleanArray mTimedLockoutCleared; - private final SparseIntArray mFailedAttempts; private final IActivityTaskManager mActivityTaskManager; - private final AlarmManager mAlarmManager; private final PowerManager mPowerManager; private final UserManager mUserManager; private final MetricsLogger mMetricsLogger; private final BiometricTaskStackListener mTaskStackListener = new BiometricTaskStackListener(); private final ResetClientStateRunnable mResetClientState = new ResetClientStateRunnable(); - private final LockoutReceiver mLockoutReceiver = new LockoutReceiver(); private final ArrayList mLockoutMonitors = new ArrayList<>(); protected final IStatusBarService mStatusBarService; protected final Map mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>()); - protected final ResetFailedAttemptsForUserRunnable mResetFailedAttemptsForCurrentUserRunnable = - new ResetFailedAttemptsForUserRunnable(); protected final AppOpsManager mAppOps; protected final H mHandler = new H(); @@ -148,18 +134,6 @@ public abstract class BiometricServiceBase extends SystemService */ protected abstract BiometricUtils getBiometricUtils(); - /** - * @return the number of failed attempts after which the user will be temporarily locked out - * from using the biometric. A strong auth (pin/pattern/pass) clears this counter. - */ - protected abstract int getFailedAttemptsLockoutTimed(); - - /** - * @return the number of failed attempts after which the user will be permanently locked out - * from using the biometric. A strong auth (pin/pattern/pass) clears this counter. - */ - protected abstract int getFailedAttemptsLockoutPermanent(); - /** * @return the metrics constants for a biometric implementation. */ @@ -229,6 +203,11 @@ public abstract class BiometricServiceBase extends SystemService protected abstract int statsModality(); + /** + * @return one of the AuthenticationClient LOCKOUT constants + */ + protected abstract int getLockoutMode(); + protected abstract class AuthenticationClientImpl extends AuthenticationClient { // Used to check if the public API that was invoked was from FingerprintManager. Only @@ -275,12 +254,6 @@ public abstract class BiometricServiceBase extends SystemService } } - @Override - public void resetFailedAttempts() { - resetFailedAttemptsForUser(true /* clearAttemptCounter */, - ActivityManager.getCurrentUser()); - } - @Override public void notifyUserActivity() { userActivity(); @@ -288,9 +261,6 @@ public abstract class BiometricServiceBase extends SystemService @Override public int handleFailedAttempt() { - final int currentUser = ActivityManager.getCurrentUser(); - mFailedAttempts.put(currentUser, mFailedAttempts.get(currentUser, 0) + 1); - mTimedLockoutCleared.put(ActivityManager.getCurrentUser(), false); final int lockoutMode = getLockoutMode(); if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) { mPerformanceStats.permanentLockout++; @@ -300,7 +270,6 @@ public abstract class BiometricServiceBase extends SystemService // Failing multiple times will continue to push out the lockout time if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) { - scheduleLockoutResetForUser(currentUser); return lockoutMode; } return AuthenticationClient.LOCKOUT_NONE; @@ -505,8 +474,9 @@ public abstract class BiometricServiceBase extends SystemService int cancel() throws RemoteException; int remove(int groupId, int biometricId) throws RemoteException; int enumerate() throws RemoteException; - int enroll(byte[] cryptoToken, int groupId, int timeout, + int enroll(byte[] token, int groupId, int timeout, ArrayList disabledFeatures) throws RemoteException; + void resetLockout(byte[] token) throws RemoteException; } /** @@ -577,24 +547,7 @@ public abstract class BiometricServiceBase extends SystemService } } - private final class LockoutReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Slog.v(getTag(), "Resetting lockout: " + intent.getAction()); - if (getLockoutResetIntent().equals(intent.getAction())) { - final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0); - resetFailedAttemptsForUser(false /* clearAttemptCounter */, user); - } - } - } - private final class ResetFailedAttemptsForUserRunnable implements Runnable { - @Override - public void run() { - resetFailedAttemptsForUser(true /* clearAttemptCounter */, - ActivityManager.getCurrentUser()); - } - } private final class LockoutResetMonitor implements IBinder.DeathRecipient { private static final long WAKELOCK_TIMEOUT_MS = 2000; @@ -683,16 +636,11 @@ public abstract class BiometricServiceBase extends SystemService mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString( com.android.internal.R.string.config_keyguardComponent)).getPackageName(); mAppOps = context.getSystemService(AppOpsManager.class); - mTimedLockoutCleared = new SparseBooleanArray(); - mFailedAttempts = new SparseIntArray(); mActivityTaskManager = ((ActivityTaskManager) context.getSystemService( Context.ACTIVITY_TASK_SERVICE)).getService(); mPowerManager = mContext.getSystemService(PowerManager.class); - mAlarmManager = mContext.getSystemService(AlarmManager.class); mUserManager = UserManager.get(mContext); mMetricsLogger = new MetricsLogger(); - mContext.registerReceiver(mLockoutReceiver, new IntentFilter(getLockoutResetIntent()), - getLockoutBroadcastPermission(), null /* handler */); } @Override @@ -1041,19 +989,6 @@ public abstract class BiometricServiceBase extends SystemService return mKeyguardPackage.equals(clientPackage); } - protected int getLockoutMode() { - final int currentUser = ActivityManager.getCurrentUser(); - final int failedAttempts = mFailedAttempts.get(currentUser, 0); - if (failedAttempts >= getFailedAttemptsLockoutPermanent()) { - return AuthenticationClient.LOCKOUT_PERMANENT; - } else if (failedAttempts > 0 && - mTimedLockoutCleared.get(currentUser, false) == false - && (failedAttempts % getFailedAttemptsLockoutTimed() == 0)) { - return AuthenticationClient.LOCKOUT_TIMED; - } - return AuthenticationClient.LOCKOUT_NONE; - } - private boolean isForegroundActivity(int uid, int pid) { try { List procs = @@ -1300,7 +1235,7 @@ public abstract class BiometricServiceBase extends SystemService * This method is called when the user switches. Implementations should probably notify the * HAL. */ - private void handleUserSwitching(int userId) { + protected void handleUserSwitching(int userId) { if (getCurrentClient() instanceof InternalRemovalClient || getCurrentClient() instanceof InternalEnumerateClient) { Slog.w(getTag(), "User switched while performing cleanup"); @@ -1311,16 +1246,10 @@ public abstract class BiometricServiceBase extends SystemService doTemplateCleanupForUser(userId); } - private void scheduleLockoutResetForUser(int userId) { - mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, - getLockoutResetIntentForUser(userId)); - } - - private PendingIntent getLockoutResetIntentForUser(int userId) { - return PendingIntent.getBroadcast(mContext, userId, - new Intent(getLockoutResetIntent()).putExtra(KEY_LOCKOUT_RESET_USER, userId), - PendingIntent.FLAG_UPDATE_CURRENT); + protected void notifyLockoutResetMonitors() { + for (int i = 0; i < mLockoutMonitors.size(); i++) { + mLockoutMonitors.get(i).sendLockoutReset(); + } } private void userActivity() { @@ -1356,25 +1285,6 @@ public abstract class BiometricServiceBase extends SystemService return userId; } - // Attempt counter should only be cleared when Keyguard goes away or when - // a biometric is successfully authenticated. - private void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) { - if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) { - Slog.v(getTag(), "Reset biometric lockout, clearAttemptCounter=" + clearAttemptCounter); - } - if (clearAttemptCounter) { - mFailedAttempts.put(userId, 0); - } - mTimedLockoutCleared.put(userId, true); - // If we're asked to reset failed attempts externally (i.e. from Keyguard), - // the alarm might still be pending; remove it. - cancelLockoutResetForUser(userId); - notifyLockoutResetMonitors(); - } - - private void cancelLockoutResetForUser(int userId) { - mAlarmManager.cancel(getLockoutResetIntentForUser(userId)); - } private void listenForUserSwitches() { try { @@ -1391,12 +1301,6 @@ public abstract class BiometricServiceBase extends SystemService } } - private void notifyLockoutResetMonitors() { - for (int i = 0; i < mLockoutMonitors.size(); i++) { - mLockoutMonitors.get(i).sendLockoutReset(); - } - } - private void removeLockoutResetCallback( LockoutResetMonitor monitor) { mLockoutMonitors.remove(monitor); diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 79f0a7fddc49b..da635b1a7e26d 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -51,6 +51,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; +import com.android.server.biometrics.AuthenticationClient; import com.android.server.biometrics.BiometricServiceBase; import com.android.server.biometrics.BiometricUtils; import com.android.server.biometrics.ClientMonitor; @@ -82,8 +83,6 @@ public class FaceService extends BiometricServiceBase { private static final String FACE_DATA_DIR = "facedata"; private static final String ACTION_LOCKOUT_RESET = "com.android.server.biometrics.face.ACTION_LOCKOUT_RESET"; - private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 3; - private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 12; private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes private final class FaceAuthClient extends AuthenticationClientImpl { @@ -99,6 +98,11 @@ public class FaceService extends BiometricServiceBase { protected int statsModality() { return FaceService.this.statsModality(); } + + @Override + public boolean shouldFrameworkHandleLockout() { + return false; + } } /** @@ -109,6 +113,7 @@ public class FaceService extends BiometricServiceBase { /** * The following methods contain common code which is shared in biometrics/common. */ + @Override // Binder call public long generateChallenge(IBinder token) { checkPermission(MANAGE_BIOMETRIC); @@ -356,10 +361,13 @@ public class FaceService extends BiometricServiceBase { } @Override // Binder call - public void resetTimeout(byte[] token) { + public void resetLockout(byte[] token) { checkPermission(MANAGE_BIOMETRIC); - // TODO: confirm security token when we move timeout management into the HAL layer. - mHandler.post(mResetFailedAttemptsForCurrentUserRunnable); + try { + mDaemonWrapper.resetLockout(token); + } catch (RemoteException e) { + Slog.e(getTag(), "Unable to reset lockout", e); + } } @Override @@ -523,6 +531,8 @@ public class FaceService extends BiometricServiceBase { @GuardedBy("this") private IBiometricsFace mDaemon; + // One of the AuthenticationClient constants + private int mCurrentUserLockoutMode; /** * Receives callbacks from the HAL. @@ -606,7 +616,20 @@ public class FaceService extends BiometricServiceBase { @Override public void onLockoutChanged(long duration) { + Slog.d(TAG, "onLockoutChanged: " + duration); + if (duration == 0) { + mCurrentUserLockoutMode = AuthenticationClient.LOCKOUT_NONE; + } else if (duration == Long.MAX_VALUE) { + mCurrentUserLockoutMode = AuthenticationClient.LOCKOUT_PERMANENT; + } else { + mCurrentUserLockoutMode = AuthenticationClient.LOCKOUT_TIMED; + } + mHandler.post(() -> { + if (duration == 0) { + notifyLockoutResetMonitors(); + } + }); } }; @@ -669,6 +692,20 @@ public class FaceService extends BiometricServiceBase { } return daemon.enroll(token, timeout, disabledFeatures); } + + @Override + public void resetLockout(byte[] cryptoToken) throws RemoteException { + IBiometricsFace daemon = getFaceDaemon(); + if (daemon == null) { + Slog.w(TAG, "resetLockout(): no face HAL!"); + return; + } + final ArrayList token = new ArrayList<>(); + for (int i = 0; i < cryptoToken.length; i++) { + token.add(cryptoToken[i]); + } + daemon.resetLockout(token); + } }; @@ -698,16 +735,6 @@ public class FaceService extends BiometricServiceBase { return FaceUtils.getInstance(); } - @Override - protected int getFailedAttemptsLockoutTimed() { - return MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED; - } - - @Override - protected int getFailedAttemptsLockoutPermanent() { - return MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT; - } - @Override protected Metrics getMetrics() { return mFaceMetrics; @@ -783,6 +810,13 @@ public class FaceService extends BiometricServiceBase { return mHalDeviceId; } + @Override + protected void handleUserSwitching(int userId) { + super.handleUserSwitching(userId); + // Will be updated when we get the callback from HAL + mCurrentUserLockoutMode = AuthenticationClient.LOCKOUT_NONE; + } + @Override protected boolean hasEnrolledBiometrics(int userId) { if (userId != UserHandle.getCallingUserId()) { @@ -822,6 +856,11 @@ public class FaceService extends BiometricServiceBase { return BiometricsProtoEnums.MODALITY_FACE; } + @Override + protected int getLockoutMode() { + return mCurrentUserLockoutMode; + } + /** Gets the face daemon */ private synchronized IBiometricsFace getFaceDaemon() { if (mDaemon == null) { diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 9c511574f94d6..164468e999673 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -24,8 +24,13 @@ import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_FINGERPRINT; import android.app.ActivityManager; +import android.app.AlarmManager; import android.app.AppOpsManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; @@ -46,15 +51,19 @@ import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; import android.os.SELinux; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; +import com.android.server.biometrics.AuthenticationClient; import com.android.server.biometrics.BiometricServiceBase; import com.android.server.biometrics.BiometricUtils; import com.android.server.biometrics.ClientMonitor; @@ -90,6 +99,27 @@ public class FingerprintService extends BiometricServiceBase { "com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET"; private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5; private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20; + private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30 * 1000; + private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user"; + + private final class ResetFailedAttemptsForUserRunnable implements Runnable { + @Override + public void run() { + resetFailedAttemptsForUser(true /* clearAttemptCounter */, + ActivityManager.getCurrentUser()); + } + } + + private final class LockoutReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Slog.v(getTag(), "Resetting lockout: " + intent.getAction()); + if (getLockoutResetIntent().equals(intent.getAction())) { + final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0); + resetFailedAttemptsForUser(false /* clearAttemptCounter */, user); + } + } + } private final class FingerprintAuthClient extends AuthenticationClientImpl { @Override @@ -110,6 +140,30 @@ public class FingerprintService extends BiometricServiceBase { protected int statsModality() { return FingerprintService.this.statsModality(); } + + @Override + public void resetFailedAttempts() { + resetFailedAttemptsForUser(true /* clearAttemptCounter */, + ActivityManager.getCurrentUser()); + } + + @Override + public boolean shouldFrameworkHandleLockout() { + return true; + } + + @Override + public int handleFailedAttempt() { + final int currentUser = ActivityManager.getCurrentUser(); + mFailedAttempts.put(currentUser, mFailedAttempts.get(currentUser, 0) + 1); + mTimedLockoutCleared.put(ActivityManager.getCurrentUser(), false); + + if (getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) { + scheduleLockoutResetForUser(currentUser); + } + + return super.handleFailedAttempt(); + } } /** @@ -503,6 +557,12 @@ public class FingerprintService extends BiometricServiceBase { @GuardedBy("this") private IBiometricsFingerprint mDaemon; + private final SparseBooleanArray mTimedLockoutCleared; + private final SparseIntArray mFailedAttempts; + private final AlarmManager mAlarmManager; + private final LockoutReceiver mLockoutReceiver = new LockoutReceiver(); + protected final ResetFailedAttemptsForUserRunnable mResetFailedAttemptsForCurrentUserRunnable = + new ResetFailedAttemptsForUserRunnable(); /** * Receives callbacks from the HAL. @@ -629,10 +689,22 @@ public class FingerprintService extends BiometricServiceBase { } return daemon.enroll(cryptoToken, groupId, timeout); } + + @Override + public void resetLockout(byte[] token) throws RemoteException { + // TODO: confirm security token when we move timeout management into the HAL layer. + Slog.e(TAG, "Not supported"); + return; + } }; public FingerprintService(Context context) { super(context); + mTimedLockoutCleared = new SparseBooleanArray(); + mFailedAttempts = new SparseIntArray(); + mAlarmManager = context.getSystemService(AlarmManager.class); + context.registerReceiver(mLockoutReceiver, new IntentFilter(getLockoutResetIntent()), + getLockoutBroadcastPermission(), null /* handler */); } @Override @@ -657,16 +729,6 @@ public class FingerprintService extends BiometricServiceBase { return FingerprintUtils.getInstance(); } - @Override - protected int getFailedAttemptsLockoutTimed() { - return MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED; - } - - @Override - protected int getFailedAttemptsLockoutPermanent() { - return MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT; - } - @Override protected Metrics getMetrics() { return mFingerprintMetrics; @@ -808,6 +870,20 @@ public class FingerprintService extends BiometricServiceBase { return BiometricsProtoEnums.MODALITY_FINGERPRINT; } + @Override + protected int getLockoutMode() { + final int currentUser = ActivityManager.getCurrentUser(); + final int failedAttempts = mFailedAttempts.get(currentUser, 0); + if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) { + return AuthenticationClient.LOCKOUT_PERMANENT; + } else if (failedAttempts > 0 + && !mTimedLockoutCleared.get(currentUser, false) + && (failedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) { + return AuthenticationClient.LOCKOUT_TIMED; + } + return AuthenticationClient.LOCKOUT_NONE; + } + /** Gets the fingerprint daemon */ private synchronized IBiometricsFingerprint getFingerprintDaemon() { if (mDaemon == null) { @@ -875,6 +951,40 @@ public class FingerprintService extends BiometricServiceBase { return 0; } + // Attempt counter should only be cleared when Keyguard goes away or when + // a biometric is successfully authenticated. Lockout should eventually be done below the HAL. + // See AuthenticationClient#shouldFrameworkHandleLockout(). + private void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) { + if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) { + Slog.v(getTag(), "Reset biometric lockout, clearAttemptCounter=" + clearAttemptCounter); + } + if (clearAttemptCounter) { + mFailedAttempts.put(userId, 0); + } + mTimedLockoutCleared.put(userId, true); + // If we're asked to reset failed attempts externally (i.e. from Keyguard), + // the alarm might still be pending; remove it. + cancelLockoutResetForUser(userId); + notifyLockoutResetMonitors(); + } + + + private void cancelLockoutResetForUser(int userId) { + mAlarmManager.cancel(getLockoutResetIntentForUser(userId)); + } + + private void scheduleLockoutResetForUser(int userId) { + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, + getLockoutResetIntentForUser(userId)); + } + + private PendingIntent getLockoutResetIntentForUser(int userId) { + return PendingIntent.getBroadcast(getContext(), userId, + new Intent(getLockoutResetIntent()).putExtra(KEY_LOCKOUT_RESET_USER, userId), + PendingIntent.FLAG_UPDATE_CURRENT); + } + private void dumpInternal(PrintWriter pw) { JSONObject dump = new JSONObject(); try { diff --git a/services/core/java/com/android/server/biometrics/iris/IrisService.java b/services/core/java/com/android/server/biometrics/iris/IrisService.java index d42a9b7808ccb..cb8a772356cc0 100644 --- a/services/core/java/com/android/server/biometrics/iris/IrisService.java +++ b/services/core/java/com/android/server/biometrics/iris/IrisService.java @@ -20,6 +20,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricsProtoEnums; +import com.android.server.biometrics.AuthenticationClient; import com.android.server.biometrics.BiometricServiceBase; import com.android.server.biometrics.BiometricUtils; import com.android.server.biometrics.Metrics; @@ -73,16 +74,6 @@ public class IrisService extends BiometricServiceBase { return null; } - @Override - protected int getFailedAttemptsLockoutTimed() { - return 0; - } - - @Override - protected int getFailedAttemptsLockoutPermanent() { - return 0; - } - @Override protected Metrics getMetrics() { return null; @@ -142,4 +133,9 @@ public class IrisService extends BiometricServiceBase { protected int statsModality() { return BiometricsProtoEnums.MODALITY_IRIS; } + + @Override + protected int getLockoutMode() { + return AuthenticationClient.LOCKOUT_NONE; + } } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index a9ae74f67de7d..4b4788cd4a164 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -55,6 +55,8 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.database.sqlite.SQLiteDatabase; import android.hardware.authsecret.V1_0.IAuthSecret; +import android.hardware.biometrics.BiometricManager; +import android.hardware.face.FaceManager; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -671,7 +673,6 @@ public class LockSettingsService extends ILockSettings.Stub { mDeviceProvisionedObserver.onSystemReady(); // TODO: maybe skip this for split system user mode. mStorage.prefetchUser(UserHandle.USER_SYSTEM); - mStrongAuth.systemReady(); } private void migrateOldData() { @@ -2375,6 +2376,14 @@ public class LockSettingsService extends ILockSettings.Stub { userCredential = null; } + final PackageManager pm = mContext.getPackageManager(); + // TODO: When lockout is handled under the HAL for all biometrics (fingerprint), + // we need to generate challenge for each one, have it signed by GK and reset lockout + // for each modality. + if (!hasChallenge && pm.hasSystemFeature(PackageManager.FEATURE_FACE)) { + challenge = mContext.getSystemService(FaceManager.class).generateChallenge(); + } + final AuthenticationResult authResult; VerifyCredentialResponse response; synchronized (mSpManager) { @@ -2413,6 +2422,17 @@ public class LockSettingsService extends ILockSettings.Stub { if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { notifyActivePasswordMetricsAvailable(userCredential, userId); unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId); + // Reset lockout + if (BiometricManager.hasBiometrics(mContext)) { + BiometricManager bm = mContext.getSystemService(BiometricManager.class); + Slog.i(TAG, "Resetting lockout, length: " + + authResult.gkResponse.getPayload().length); + bm.resetLockout(authResult.gkResponse.getPayload()); + + if (!hasChallenge && pm.hasSystemFeature(PackageManager.FEATURE_FACE)) { + mContext.getSystemService(FaceManager.class).revokeChallenge(); + } + } final byte[] secret = authResult.authToken.deriveDiskEncryptionKey(); Slog.i(TAG, "Unlocking user " + userId + " with secret only, length " + secret.length); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java index 44804350309d3..a84306c97dc0f 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java @@ -16,15 +16,16 @@ package com.android.server.locksettings; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker + .STRONG_AUTH_NOT_REQUIRED; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker + .STRONG_AUTH_REQUIRED_AFTER_TIMEOUT; import android.app.AlarmManager; import android.app.AlarmManager.OnAlarmListener; import android.app.admin.DevicePolicyManager; import android.app.trust.IStrongAuthTracker; import android.content.Context; -import android.hardware.biometrics.BiometricManager; import android.os.Handler; import android.os.Message; import android.os.RemoteCallbackList; @@ -61,7 +62,6 @@ public class LockSettingsStrongAuth { private final Context mContext; private AlarmManager mAlarmManager; - private BiometricManager mBiometricManager; public LockSettingsStrongAuth(Context context) { mContext = context; @@ -69,12 +69,6 @@ public class LockSettingsStrongAuth { mAlarmManager = context.getSystemService(AlarmManager.class); } - public void systemReady() { - if (BiometricManager.hasBiometrics(mContext)) { - mBiometricManager = mContext.getSystemService(BiometricManager.class); - } - } - private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) { mTrackers.register(tracker); @@ -185,11 +179,6 @@ public class LockSettingsStrongAuth { } public void reportSuccessfulStrongAuthUnlock(int userId) { - if (mBiometricManager != null) { - byte[] token = null; /* TODO: pass real auth token once HAL supports it */ - mBiometricManager.resetTimeout(token); - } - final int argNotUsed = 0; mHandler.obtainMessage(MSG_SCHEDULE_STRONG_AUTH_TIMEOUT, userId, argNotUsed).sendToTarget(); }