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:
Kevin Chyn
2019-02-11 17:46:21 -08:00
parent 6737c57987
commit a38653cb34
13 changed files with 264 additions and 213 deletions

View File

@@ -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");
}
}

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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);

View File

@@ -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
*/

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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();
}