Actually use HardwareAuthToken to resetLockout where applicable
Lockout reset now happens at the source of truth, where the HAT is received from Gatekeeper. Fixes: 121198195 Test: Lockout is reset properly Change-Id: Icd72a20494a65f0e48cff1258109d82fb58cdc98
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,8 +49,8 @@ interface IBiometricService {
|
||||
// Client lifecycle is still managed in <Biometric>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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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<Byte> 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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<LockoutResetMonitor> mLockoutMonitors = new ArrayList<>();
|
||||
|
||||
protected final IStatusBarService mStatusBarService;
|
||||
protected final Map<Integer, Long> 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<Integer> 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<ActivityManager.RunningAppProcessInfo> 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);
|
||||
|
||||
@@ -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<Byte> 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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user