3/n: Move generateChallenge/resetLockout/revokeChallenge off critical path

1) Introduces BiometricDeferredQueue in LockSettings package to move
   slow operations off of the critical unlock path.

2) Changes generateChallenge to require sensorId. Callers to
   generateChallengeBlocking are currently not affected. Their path
   will need to be updated in the future.

3) Adds resetLockoutRequiresHardwareAuthToken for fingerprint sensor
   properties, since IBiometricsFingerprint@2.1 and its derivatives
   do not require the HAT yet

Fixes: 145978626

Test: atest com.android.server.biometrics
Test: Able to enroll after entering password
Test: Reset lockout on face device with single-profile-per-user
Test: Reset lockout on face device with managed profile + unified
      credential
Test: Reset lockout on face device with managed profile and separate
      credential (both owner and managed profile)
Test: Reset lockout for secondary user

Change-Id: Id4d7c39274a52ef61709161b6f24ec4f5d76720e
This commit is contained in:
Kevin Chyn
2020-07-17 13:51:04 -07:00
parent 509f1c79df
commit d1bb072d96
26 changed files with 492 additions and 314 deletions

View File

@@ -299,26 +299,6 @@ public class BiometricManager {
}
}
/**
* Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
*
* @param userId this operation takes effect for.
* @param hardwareAuthToken an opaque token returned by password confirmation.
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void resetLockout(int userId, byte[] hardwareAuthToken) {
if (mService != null) {
try {
mService.resetLockout(userId, hardwareAuthToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} else {
Slog.w(TAG, "resetLockout(): Service not connected");
}
}
/**
* Get a list of AuthenticatorIDs for biometric authenticators which have 1) enrolled templates,
* and 2) meet the requirements for integrating with Keystore. The AuthenticatorIDs are known

View File

@@ -46,9 +46,6 @@ interface IAuthService {
// Register callback for when keyguard biometric eligibility changes.
void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
// Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
void resetLockout(int userId, in byte [] hardwareAuthToken);
// Get a list of AuthenticatorIDs for authenticators which have enrolled templates and meet
// the requirements for integrating with Keystore. The AuthenticatorID are known in Keystore
// land as SIDs, and are used during key generation.

View File

@@ -53,9 +53,6 @@ interface IBiometricAuthenticator {
// Return the LockoutTracker status for the specified user
int getLockoutModeForUser(int userId);
// Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
void resetLockout(int userId, in byte [] hardwareAuthToken);
// Gets the authenticator ID representing the current set of enrolled templates
long getAuthenticatorId(int callingUserId);
}

View File

@@ -56,9 +56,6 @@ interface IBiometricService {
// Client lifecycle is still managed in <Biometric>Service.
void onReadyForAuthentication(int cookie);
// Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
void resetLockout(int userId, in byte [] hardwareAuthToken);
// Get a list of AuthenticatorIDs for authenticators which have enrolled templates and meet
// the requirements for integrating with Keystore. The AuthenticatorID are known in Keystore
// land as SIDs, and are used during key generation.

View File

@@ -143,14 +143,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
@Override
public void onChallengeGenerated(long challenge) {
if (mGenerateChallengeCallback instanceof InternalGenerateChallengeCallback) {
// Perform this on system_server thread, since the application's thread is
// blocked waiting for the result
mGenerateChallengeCallback.onGenerateChallengeResult(challenge);
} else {
mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, challenge).sendToTarget();
}
public void onChallengeGenerated(int sensorId, long challenge) {
mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, 0, challenge)
.sendToTarget();
}
@Override
@@ -416,35 +411,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
}
/**
* Same as {@link #generateChallenge(GenerateChallengeCallback)}, except blocks until the
* TEE/hardware operation is complete.
* @return challenge generated in the TEE/hardware
* @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
public long generateChallengeBlocking() {
final AtomicReference<Long> result = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
final GenerateChallengeCallback callback = new InternalGenerateChallengeCallback() {
@Override
public void onGenerateChallengeResult(long challenge) {
result.set(challenge);
latch.countDown();
}
};
generateChallenge(callback);
try {
latch.await(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Slog.e(TAG, "Interrupted while generatingChallenge", e);
e.printStackTrace();
}
return result.get();
}
/**
* Generates a unique random challenge in the TEE. A typical use case is to have it wrapped in a
* HardwareAuthenticationToken, minted by Gatekeeper upon PIN/Pattern/Password verification.
@@ -458,11 +424,12 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
* @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
public void generateChallenge(GenerateChallengeCallback callback) {
public void generateChallenge(int sensorId, GenerateChallengeCallback callback) {
if (mService != null) {
try {
mGenerateChallengeCallback = callback;
mService.generateChallenge(mToken, mServiceReceiver, mContext.getOpPackageName());
mService.generateChallenge(mToken, sensorId, mServiceReceiver,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -470,15 +437,66 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
* Invalidates the current auth token.
* Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
* enumerated sensor.
* @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
public void generateChallenge(GenerateChallengeCallback callback) {
final List<FaceSensorProperties> faceSensorProperties = getSensorProperties();
if (faceSensorProperties.isEmpty()) {
Slog.e(TAG, "No sensors");
return;
}
final int sensorId = faceSensorProperties.get(0).sensorId;
generateChallenge(sensorId, callback);
}
/**
* Invalidates the current challenge.
*
* @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
public void revokeChallenge() {
final List<FaceSensorProperties> faceSensorProperties = getSensorProperties();
if (faceSensorProperties.isEmpty()) {
Slog.e(TAG, "No sensors during revokeChallenge");
}
revokeChallenge(faceSensorProperties.get(0).sensorId);
}
/**
* Invalidates the current challenge.
*
* @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
public void revokeChallenge(int sensorId) {
if (mService != null) {
try {
mService.revokeChallenge(mToken, mContext.getOpPackageName());
mService.revokeChallenge(mToken, sensorId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
* Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
*
* @param sensorId Sensor ID that this operation takes effect for
* @param userId User ID that this operation takes effect for.
* @param hardwareAuthToken An opaque token returned by password confirmation.
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void resetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
if (mService != null) {
try {
mService.resetLockout(mToken, sensorId, userId, hardwareAuthToken,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1083,18 +1101,18 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
* Callback structure provided to {@link #generateChallenge(GenerateChallengeCallback)}.
* Callback structure provided to {@link #generateChallenge(int, GenerateChallengeCallback)}.
* @hide
*/
public interface GenerateChallengeCallback {
/**
* Invoked when a challenge has been generated.
*/
void onGenerateChallengeResult(long challenge);
void onGenerateChallengeResult(int sensorId, long challenge);
/**
* Invoked if the challenge has not been revoked and a subsequent caller/owner invokes
* {@link #generateChallenge(GenerateChallengeCallback)}, but
* {@link #generateChallenge(int, GenerateChallengeCallback)}, but
*/
default void onChallengeInterrupted(int sensorId) {}
@@ -1104,9 +1122,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
default void onChallengeInterruptFinished(int sensorId) {}
}
private abstract static class InternalGenerateChallengeCallback
implements GenerateChallengeCallback {}
private class OnEnrollCancelListener implements OnCancelListener {
@Override
public void onCancel() {
@@ -1178,7 +1193,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
args.recycle();
break;
case MSG_CHALLENGE_GENERATED:
sendChallengeGenerated((long) msg.obj /* challenge */);
sendChallengeGenerated(msg.arg1 /* sensorId */, (long) msg.obj /* challenge */);
break;
case MSG_FACE_DETECTED:
sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
@@ -1211,11 +1226,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
mGetFeatureCallback.onCompleted(success, feature, value);
}
private void sendChallengeGenerated(long challenge) {
private void sendChallengeGenerated(int sensorId, long challenge) {
if (mGenerateChallengeCallback == null) {
return;
}
mGenerateChallengeCallback.onGenerateChallengeResult(challenge);
mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, challenge);
}
private void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {

View File

@@ -83,10 +83,10 @@ interface IFaceService {
boolean isHardwareDetected(String opPackageName);
// Get a pre-enrollment authentication token
void generateChallenge(IBinder token, IFaceServiceReceiver receiver, String opPackageName);
void generateChallenge(IBinder token, int sensorId, IFaceServiceReceiver receiver, String opPackageName);
// Finish an enrollment sequence and invalidate the authentication token
void revokeChallenge(IBinder token, String opPackageName);
void revokeChallenge(IBinder token, int sensorId, String opPackageName);
// Determine if a user has at least one enrolled face
boolean hasEnrolledFaces(int userId, String opPackageName);
@@ -98,7 +98,7 @@ interface IFaceService {
long getAuthenticatorId(int callingUserId);
// Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
void resetLockout(int userId, in byte [] hardwareAuthToken);
void resetLockout(IBinder token, int sensorId, int userId, in byte [] hardwareAuthToken, String opPackageName);
// Add a callback which gets notified when the face lockout period expired.
void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback, String opPackageName);

View File

@@ -31,7 +31,7 @@ oneway interface IFaceServiceReceiver {
void onRemoved(in Face face, int remaining);
void onFeatureSet(boolean success, int feature);
void onFeatureGet(boolean success, int feature, boolean value);
void onChallengeGenerated(long challenge);
void onChallengeGenerated(int sensorId, long challenge);
void onChallengeInterrupted(int sensorId);
void onChallengeInterruptFinished(int sensorId);
}

View File

@@ -18,6 +18,7 @@ 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_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
@@ -378,12 +379,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
* @hide
*/
public interface GenerateChallengeCallback {
void onChallengeGenerated(long challenge);
void onChallengeGenerated(int sensorId, long challenge);
}
private abstract static class InternalGenerateChallengeCallback
implements GenerateChallengeCallback {}
/**
* Request authentication of a crypto object. This call warms up the fingerprint hardware
* and starts scanning for a fingerprint. It terminates when
@@ -593,15 +591,33 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void generateChallenge(GenerateChallengeCallback callback) {
public void generateChallenge(int sensorId, GenerateChallengeCallback callback) {
if (mService != null) try {
mGenerateChallengeCallback = callback;
mService.generateChallenge(mToken, mServiceReceiver, mContext.getOpPackageName());
mService.generateChallenge(mToken, sensorId, mServiceReceiver,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
* enumerated sensor.
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void generateChallenge(GenerateChallengeCallback callback) {
final List<FingerprintSensorProperties> fingerprintSensorProperties = getSensorProperties();
if (fingerprintSensorProperties.isEmpty()) {
Slog.e(TAG, "No sensors");
return;
}
final int sensorId = fingerprintSensorProperties.get(0).sensorId;
generateChallenge(sensorId, callback);
}
/**
* Finishes enrollment and cancels the current auth token.
* @hide
@@ -615,6 +631,26 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
/**
* Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
*
* @param sensorId Sensor ID that this operation takes effect for
* @param userId User ID that this operation takes effect for.
* @param hardwareAuthToken An opaque token returned by password confirmation.
* @hide
*/
@RequiresPermission(RESET_FINGERPRINT_LOCKOUT)
public void resetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
if (mService != null) {
try {
mService.resetLockout(mToken, sensorId, userId, hardwareAuthToken,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
* Remove given fingerprint template from fingerprint hardware and/or protected storage.
* @param fp the fingerprint item to remove
@@ -901,7 +937,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
break;
case MSG_CHALLENGE_GENERATED:
sendChallengeGenerated((long) msg.obj /* challenge */);
sendChallengeGenerated(msg.arg1 /* sensorId */, (long) msg.obj /* challenge */);
break;
case MSG_FINGERPRINT_DETECTED:
sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
@@ -989,12 +1025,12 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
private void sendChallengeGenerated(long challenge) {
private void sendChallengeGenerated(int sensorId, long challenge) {
if (mGenerateChallengeCallback == null) {
Slog.e(TAG, "sendChallengeGenerated, callback null");
return;
}
mGenerateChallengeCallback.onChallengeGenerated(challenge);
mGenerateChallengeCallback.onChallengeGenerated(sensorId, challenge);
}
private void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
@@ -1178,14 +1214,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
@Override // binder call
public void onChallengeGenerated(long challenge) {
if (mGenerateChallengeCallback instanceof InternalGenerateChallengeCallback) {
// Perform this on system_server thread, since the application's thread is
// blocked waiting for the result
mGenerateChallengeCallback.onChallengeGenerated(challenge);
} else {
mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, challenge).sendToTarget();
}
public void onChallengeGenerated(int sensorId, long challenge) {
mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, 0, challenge)
.sendToTarget();
}
};

View File

@@ -45,18 +45,24 @@ public class FingerprintSensorProperties implements Parcelable {
public final int sensorId;
public final @SensorType int sensorType;
// IBiometricsFingerprint@2.1 does not manage timeout below the HAL, so the Gatekeeper HAT
// cannot be checked
public final boolean resetLockoutRequiresHardwareAuthToken;
/**
* Initializes SensorProperties with specified values
*/
public FingerprintSensorProperties(int sensorId, @SensorType int sensorType) {
public FingerprintSensorProperties(int sensorId, @SensorType int sensorType,
boolean resetLockoutRequiresHardwareAuthToken) {
this.sensorId = sensorId;
this.sensorType = sensorType;
this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
}
protected FingerprintSensorProperties(Parcel in) {
sensorId = in.readInt();
sensorType = in.readInt();
resetLockoutRequiresHardwareAuthToken = in.readBoolean();
}
public static final Creator<FingerprintSensorProperties> CREATOR =
@@ -81,5 +87,6 @@ public class FingerprintSensorProperties implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(sensorId);
dest.writeInt(sensorType);
dest.writeBoolean(resetLockoutRequiresHardwareAuthToken);
}
}

View File

@@ -88,7 +88,7 @@ interface IFingerprintService {
boolean isHardwareDetected(String opPackageName);
// Get a pre-enrollment authentication token
void generateChallenge(IBinder token, IFingerprintServiceReceiver receiver, String opPackageName);
void generateChallenge(IBinder token, int sensorId, IFingerprintServiceReceiver receiver, String opPackageName);
// Finish an enrollment sequence and invalidate the authentication token
void revokeChallenge(IBinder token, String opPackageName);
@@ -103,7 +103,7 @@ interface IFingerprintService {
long getAuthenticatorId(int callingUserId);
// Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password)
void resetLockout(int userId, in byte [] hardwareAuthToken);
void resetLockout(IBinder token, int sensorId, int userId, in byte[] hardwareAuthToken, String opPackageNAame);
// Add a callback which gets notified when the fingerprint lockout period expired.
void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback, String opPackageName);

View File

@@ -29,5 +29,5 @@ oneway interface IFingerprintServiceReceiver {
void onAuthenticationFailed();
void onError(int error, int vendorCode);
void onRemoved(in Fingerprint fp, int remaining);
void onChallengeGenerated(long challenge);
void onChallengeGenerated(int sensorId, long challenge);
}

View File

@@ -258,17 +258,6 @@ public class AuthService extends SystemService {
}
}
@Override
public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
checkInternalPermission();
final long identity = Binder.clearCallingIdentity();
try {
mBiometricService.resetLockout(userId, hardwareAuthToken);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public long[] getAuthenticatorIds() throws RemoteException {
// In this method, we're not checking whether the caller is permitted to use face

View File

@@ -637,19 +637,6 @@ public class BiometricService extends SystemService {
}
}
@Override // Binder call
public void resetLockout(int userId, byte[] hardwareAuthToken) {
checkInternalPermission();
try {
for (BiometricSensor sensor : mSensors) {
sensor.impl.resetLockout(userId, hardwareAuthToken);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
@Override // Binder call
public long[] getAuthenticatorIds(int callingUserId) {
checkInternalPermission();

View File

@@ -128,11 +128,11 @@ public final class ClientMonitorCallbackConverter {
}
}
public void onChallengeGenerated(long challenge) throws RemoteException {
public void onChallengeGenerated(int sensorId, long challenge) throws RemoteException {
if (mFaceServiceReceiver != null) {
mFaceServiceReceiver.onChallengeGenerated(challenge);
mFaceServiceReceiver.onChallengeGenerated(sensorId, challenge);
} else if (mFingerprintServiceReceiver != null) {
mFingerprintServiceReceiver.onChallengeGenerated(challenge);
mFingerprintServiceReceiver.onChallengeGenerated(sensorId, challenge);
}
}

View File

@@ -40,7 +40,7 @@ public abstract class GenerateChallengeClient<T> extends ClientMonitor<T> {
@Override
public void unableToStart() {
try {
getListener().onChallengeGenerated(0L);
getListener().onChallengeGenerated(getSensorId(), 0L);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send error", e);
}
@@ -52,7 +52,7 @@ public abstract class GenerateChallengeClient<T> extends ClientMonitor<T> {
startHalOperation();
try {
getListener().onChallengeGenerated(mChallenge);
getListener().onChallengeGenerated(getSensorId(), mChallenge);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);

View File

@@ -29,6 +29,7 @@ import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
import android.hardware.face.Face;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Build;
@@ -464,6 +465,21 @@ class Face10 implements IHwBinder.DeathRecipient {
});
}
/**
* {@link IBiometricsFace} only supports a single in-flight challenge. In cases where two
* callers both need challenges (e.g. resetLockout right before enrollment), we need to ensure
* that either:
* 1) generateChallenge/operation/revokeChallenge is complete before the next generateChallenge
* is processed by the scheduler, or
* 2) the generateChallenge callback provides a mechanism for notifying the caller that its
* challenge has been invalidated by a subsequent caller, as well as a mechanism for
* notifying the previous caller that the interrupting operation is complete (e.g. the
* interrupting client's challenge has been revoked, so that the interrupted client can
* start retry logic if necessary). See
* {@link FaceManager.GenerateChallengeCallback#onChallengeInterruptFinished(int)}
* The only case of conflicting challenges is currently resetLockout --> enroll. So, the second
* option seems better as it prioritizes the new operation, which is user-facing.
*/
void scheduleGenerateChallenge(@NonNull IBinder token, @NonNull IFaceServiceReceiver receiver,
@NonNull String opPackageName) {
mHandler.post(() -> {
@@ -498,7 +514,8 @@ class Face10 implements IHwBinder.DeathRecipient {
void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String owner) {
mHandler.post(() -> {
if (!mCurrentChallengeOwner.getOwnerString().contentEquals(owner)) {
Slog.e(TAG, "Package: " + owner + " attempting to revoke challenge owned by: "
Slog.e(TAG, "scheduleRevokeChallenge, package: " + owner
+ " attempting to revoke challenge owned by: "
+ mCurrentChallengeOwner.getOwnerString());
return;
}

View File

@@ -74,11 +74,6 @@ public final class FaceAuthenticator extends IBiometricAuthenticator.Stub {
return mFaceService.getLockoutModeForUser(userId);
}
@Override
public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
mFaceService.resetLockout(userId, hardwareAuthToken);
}
@Override
public long getAuthenticatorId(int callingUserId) throws RemoteException {
return mFaceService.getAuthenticatorId(callingUserId);

View File

@@ -80,16 +80,27 @@ public class FaceService extends SystemService {
}
@Override // Binder call
public void generateChallenge(IBinder token, IFaceServiceReceiver receiver,
public void generateChallenge(IBinder token, int sensorId, IFaceServiceReceiver receiver,
String opPackageName) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
mFace10.scheduleGenerateChallenge(token, receiver, opPackageName);
if (sensorId == mFace10.getFaceSensorProperties().sensorId) {
mFace10.scheduleGenerateChallenge(token, receiver, opPackageName);
return;
}
Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
}
@Override // Binder call
public void revokeChallenge(IBinder token, String owner) {
public void revokeChallenge(IBinder token, int sensorId, String owner) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
mFace10.scheduleRevokeChallenge(token, owner);
if (sensorId == mFace10.getFaceSensorProperties().sensorId) {
mFace10.scheduleRevokeChallenge(token, owner);
return;
}
Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
}
@Override // Binder call
@@ -267,9 +278,16 @@ public class FaceService extends SystemService {
}
@Override // Binder call
public void resetLockout(int userId, byte[] hardwareAuthToken) {
public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
mFace10.scheduleResetLockout(userId, hardwareAuthToken);
if (sensorId == mFace10.getFaceSensorProperties().sensorId) {
mFace10.scheduleResetLockout(userId, hardwareAuthToken);
return;
}
Slog.w(TAG, "No matching sensor for resetLockout, sensorId: " + sensorId);
}
@Override

View File

@@ -336,7 +336,10 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
final @FingerprintSensorProperties.SensorType int sensorType =
isUdfps ? FingerprintSensorProperties.TYPE_UDFPS
: FingerprintSensorProperties.TYPE_REAR;
mSensorProperties = new FingerprintSensorProperties(sensorId, sensorType);
// resetLockout is controlled by the framework, so hardwareAuthToken is not required
final boolean resetLockoutRequiresHardwareAuthToken = false;
mSensorProperties = new FingerprintSensorProperties(sensorId, sensorType,
resetLockoutRequiresHardwareAuthToken);
}
static Fingerprint21 newInstance(@NonNull Context context, int sensorId,
@@ -469,7 +472,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
});
}
void scheduleResetLockout(int userId, byte[] hardwareAuthToken) {
void scheduleResetLockout(int userId) {
// Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
// thread.
mHandler.post(() -> {

View File

@@ -406,8 +406,10 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
mScheduler = scheduler;
mScheduler.init(this);
mHandler = handler;
// resetLockout is controlled by the framework, so hardwareAuthToken is not required
final boolean resetLockoutRequiresHardwareAuthToken = false;
mSensorProperties = new FingerprintSensorProperties(sensorId,
FingerprintSensorProperties.TYPE_UDFPS);
FingerprintSensorProperties.TYPE_UDFPS, resetLockoutRequiresHardwareAuthToken);
mMockHalResultController = controller;
mUserHasTrust = new SparseBooleanArray();
mTrustManager = context.getSystemService(TrustManager.class);

View File

@@ -74,11 +74,6 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub
return mFingerprintService.getLockoutModeForUser(userId);
}
@Override
public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
mFingerprintService.resetLockout(userId, hardwareAuthToken);
}
@Override
public long getAuthenticatorId(int callingUserId) throws RemoteException {
return mFingerprintService.getAuthenticatorId(callingUserId);

View File

@@ -25,6 +25,7 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -97,10 +98,16 @@ public class FingerprintService extends SystemService {
}
@Override // Binder call
public void generateChallenge(IBinder token, IFingerprintServiceReceiver receiver,
String opPackageName) {
public void generateChallenge(IBinder token, int sensorId,
IFingerprintServiceReceiver receiver, String opPackageName) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
mFingerprint21.scheduleGenerateChallenge(token, receiver, opPackageName);
if (sensorId == mFingerprint21.getFingerprintSensorProperties().sensorId) {
mFingerprint21.scheduleGenerateChallenge(token, receiver, opPackageName);
return;
}
Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
}
@Override // Binder call
@@ -346,9 +353,16 @@ public class FingerprintService extends SystemService {
}
@Override // Binder call
public void resetLockout(int userId, byte [] hardwareAuthToken) {
public void resetLockout(IBinder token, int sensorId, int userId,
@Nullable byte [] hardwareAuthToken, String opPackageName) {
Utils.checkPermission(getContext(), RESET_FINGERPRINT_LOCKOUT);
mFingerprint21.scheduleResetLockout(userId, hardwareAuthToken);
if (sensorId == mFingerprint21.getFingerprintSensorProperties().sensorId) {
mFingerprint21.scheduleResetLockout(userId);
return;
}
Slog.w(TAG, "No matching sensor for resetLockout, sensorId: " + sensorId);
}
@Override

View File

@@ -69,10 +69,6 @@ public final class IrisAuthenticator extends IBiometricAuthenticator.Stub {
return LockoutTracker.LOCKOUT_NONE;
}
@Override
public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
}
@Override
public long getAuthenticatorId(int callingUserId) throws RemoteException {
return 0;

View File

@@ -0,0 +1,252 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.locksettings;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.os.Handler;
import android.os.IBinder;
import android.os.ServiceManager;
import android.service.gatekeeper.IGateKeeperService;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.widget.VerifyCredentialResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Class that handles biometric-related work in the {@link LockSettingsService} area, for example
* resetLockout.
*/
@SuppressWarnings("deprecation")
public class BiometricDeferredQueue {
private static final String TAG = "BiometricDeferredQueue";
@NonNull private final Context mContext;
@NonNull private final SyntheticPasswordManager mSpManager;
@NonNull private final Handler mHandler;
@Nullable private FingerprintManager mFingerprintManager;
@Nullable private FaceManager mFaceManager;
// Entries added by LockSettingsService once a user's synthetic password is known. At this point
// things are still keyed by userId.
@NonNull private final ArrayList<UserAuthInfo> mPendingResetLockouts;
/**
* Authentication info for a successful user unlock via Synthetic Password. This can be used to
* perform multiple operations (e.g. resetLockout for multiple HALs/Sensors) by sending the
* Gatekeeper Password to Gatekeer multiple times, each with a sensor-specific challenge.
*/
private static class UserAuthInfo {
final int userId;
@NonNull final byte[] gatekeeperPassword;
UserAuthInfo(int userId, @NonNull byte[] gatekeeperPassword) {
this.userId = userId;
this.gatekeeperPassword = gatekeeperPassword;
}
}
/**
* Per-authentication callback.
*/
private static class FaceResetLockoutTask implements FaceManager.GenerateChallengeCallback {
interface FinishCallback {
void onFinished();
}
@NonNull FinishCallback finishCallback;
@NonNull FaceManager faceManager;
@NonNull SyntheticPasswordManager spManager;
@NonNull Set<Integer> sensorIds; // IDs of sensors waiting for challenge
@NonNull List<UserAuthInfo> pendingResetLockuts;
FaceResetLockoutTask(
@NonNull FinishCallback finishCallback,
@NonNull FaceManager faceManager,
@NonNull SyntheticPasswordManager spManager,
@NonNull Set<Integer> sensorIds,
@NonNull List<UserAuthInfo> pendingResetLockouts) {
this.finishCallback = finishCallback;
this.faceManager = faceManager;
this.spManager = spManager;
this.sensorIds = sensorIds;
this.pendingResetLockuts = pendingResetLockouts;
}
@Override
public void onChallengeInterrupted(int sensorId) {
Slog.w(TAG, "Challenge interrupted, sensor: " + sensorId);
// Consider re-attempting generateChallenge/resetLockout/revokeChallenge
// when onChallengeInterruptFinished is invoked
}
@Override
public void onChallengeInterruptFinished(int sensorId) {
Slog.w(TAG, "Challenge interrupt finished, sensor: " + sensorId);
}
@Override
public void onGenerateChallengeResult(int sensorId, long challenge) {
if (!sensorIds.contains(sensorId)) {
Slog.e(TAG, "Unknown sensorId received: " + sensorId);
return;
}
// Challenge received for a sensor. For each sensor, reset lockout for all users.
for (UserAuthInfo userAuthInfo : pendingResetLockuts) {
Slog.d(TAG, "Resetting face lockout for sensor: " + sensorId
+ ", user: " + userAuthInfo.userId);
final VerifyCredentialResponse response = spManager.verifyChallengeInternal(
getGatekeeperService(), userAuthInfo.gatekeeperPassword, challenge,
userAuthInfo.userId);
if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
Slog.wtf(TAG, "VerifyChallenge failed, response: "
+ response.getResponseCode());
}
faceManager.resetLockout(sensorId, userAuthInfo.userId,
response.getGatekeeperHAT());
}
sensorIds.remove(sensorId);
faceManager.revokeChallenge(sensorId);
if (sensorIds.isEmpty()) {
Slog.d(TAG, "Done requesting resetLockout for all face sensors");
finishCallback.onFinished();
}
}
synchronized IGateKeeperService getGatekeeperService() {
final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
if (service == null) {
Slog.e(TAG, "Unable to acquire GateKeeperService");
return null;
}
return IGateKeeperService.Stub.asInterface(service);
}
}
@Nullable private FaceResetLockoutTask mFaceResetLockoutTask;
private final FaceResetLockoutTask.FinishCallback mFaceFinishCallback = () -> {
mFaceResetLockoutTask = null;
};
BiometricDeferredQueue(@NonNull Context context, @NonNull SyntheticPasswordManager spManager,
@NonNull Handler handler) {
mContext = context;
mSpManager = spManager;
mHandler = handler;
mPendingResetLockouts = new ArrayList<>();
}
public void systemReady() {
mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
mFaceManager = mContext.getSystemService(FaceManager.class);
}
/**
* Adds a request for resetLockout on all biometric sensors for the user specified. The queue
* owner must invoke {@link #processPendingLockoutResets()} at some point to kick off the
* operations.
*
* Note that this should only ever be invoked for successful authentications, otherwise it will
* consume a Gatekeeper authentication attempt and potentially wipe the user/device.
*
* @param userId The user that the operation will apply for.
* @param gatekeeperPassword The Gatekeeper Password
*/
void addPendingLockoutResetForUser(int userId, @NonNull byte[] gatekeeperPassword) {
mHandler.post(() -> {
Slog.d(TAG, "addPendingLockoutResetForUser: " + userId);
mPendingResetLockouts.add(new UserAuthInfo(userId, gatekeeperPassword));
});
}
void processPendingLockoutResets() {
mHandler.post(() -> {
Slog.d(TAG, "processPendingLockoutResets: " + mPendingResetLockouts.size());
processPendingLockoutsForFingerprint(new ArrayList<>(mPendingResetLockouts));
processPendingLockoutsForFace(new ArrayList<>(mPendingResetLockouts));
mPendingResetLockouts.clear();
});
}
private void processPendingLockoutsForFingerprint(List<UserAuthInfo> pendingResetLockouts) {
if (mFingerprintManager != null) {
final List<FingerprintSensorProperties> fingerprintSensorProperties =
mFingerprintManager.getSensorProperties();
for (FingerprintSensorProperties prop : fingerprintSensorProperties) {
if (!prop.resetLockoutRequiresHardwareAuthToken) {
for (UserAuthInfo user : pendingResetLockouts) {
mFingerprintManager.resetLockout(prop.sensorId, user.userId,
null /* hardwareAuthToken */);
}
} else {
Slog.e(TAG, "Fingerprint resetLockout with HAT not supported yet");
// TODO(b/152414803): Implement this when resetLockout is implemented below
// the framework.
}
}
}
}
/**
* For devices on {@link android.hardware.biometrics.face.V1_0} which only support a single
* in-flight challenge, we generate a single challenge to reset lockout for all profiles. This
* hopefully reduces/eliminates issues such as overwritten challenge, incorrectly revoked
* challenge, or other race conditions.
*
* TODO(b/162965646) This logic can be avoided if multiple in-flight challenges are supported.
* Though it will need to continue to exist to support existing HIDLs, each profile that
* requires resetLockout could have its own challenge, and the `mPendingResetLockouts` queue
* can be avoided.
*/
private void processPendingLockoutsForFace(List<UserAuthInfo> pendingResetLockouts) {
if (mFaceManager != null) {
if (mFaceResetLockoutTask != null) {
// This code will need to be updated if this problem ever occurs.
Slog.w(TAG, "mFaceGenerateChallengeCallback not null, previous operation may be"
+ " stuck");
}
final List<FaceSensorProperties> faceSensorProperties =
mFaceManager.getSensorProperties();
final Set<Integer> sensorIds = new ArraySet<>();
for (FaceSensorProperties prop : faceSensorProperties) {
sensorIds.add(prop.sensorId);
}
mFaceResetLockoutTask = new FaceResetLockoutTask(mFaceFinishCallback, mFaceManager,
mSpManager, sensorIds, pendingResetLockouts);
for (final FaceSensorProperties prop : faceSensorProperties) {
// Generate a challenge for each sensor. The challenge does not need to be
// per-user, since the HAT returned by gatekeeper contains userId.
mFaceManager.generateChallenge(prop.sensorId, mFaceResetLockoutTask);
}
}
}
}

View File

@@ -37,7 +37,6 @@ import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_RETURN_GK
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -187,23 +186,6 @@ public class LockSettingsService extends ILockSettings.Stub {
private static final String SYNTHETIC_PASSWORD_UPDATE_TIME_KEY = "sp-handle-ts";
private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
// TODO (b/145978626) LockSettingsService no longer accepts challenges in the verifyCredential
// paths. These are temporarily left around to ensure that resetLockout works. It will be
// removed once resetLockout is compartmentalized.
// No challenge provided
private static final int CHALLENGE_NONE = 0;
// Challenge was provided from the external caller (non-LockSettingsService)
private static final int CHALLENGE_FROM_CALLER = 1;
// Challenge was generated from within LockSettingsService, for resetLockout. When challenge
// type is set to internal, LSS will revokeChallenge after all profiles for that user are
// unlocked.
private static final int CHALLENGE_INTERNAL = 2;
@IntDef({CHALLENGE_NONE,
CHALLENGE_FROM_CALLER,
CHALLENGE_INTERNAL})
@interface ChallengeType {}
// Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
// Do not call into ActivityManager while holding mSpManager lock.
private final Object mSeparateChallengeLock = new Object();
@@ -219,6 +201,7 @@ public class LockSettingsService extends ILockSettings.Stub {
protected final LockSettingsStorage mStorage;
private final LockSettingsStrongAuth mStrongAuth;
private final SynchronizedStrongAuthTracker mStrongAuthTracker;
private final BiometricDeferredQueue mBiometricDeferredQueue;
private final NotificationManager mNotificationManager;
private final UserManager mUserManager;
@@ -321,15 +304,6 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
private class PendingResetLockout {
final int mUserId;
final byte[] mHAT;
PendingResetLockout(int userId, byte[] hat) {
mUserId = userId;
mHAT = hat;
}
}
private LockscreenCredential generateRandomProfilePassword() {
byte[] randomLockSeed = new byte[] {};
try {
@@ -588,6 +562,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mSpManager = injector.getSyntheticPasswordManager(mStorage);
mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache();
mBiometricDeferredQueue = new BiometricDeferredQueue(mContext, mSpManager, mHandler);
mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
mStorage);
@@ -740,8 +715,7 @@ public class LockSettingsService extends ILockSettings.Stub {
// If boot took too long and the password in vold got expired, parent keystore will
// be still locked, we ignore this case since the user will be prompted to unlock
// the device after boot.
unlockChildProfile(userId, true /* ignoreUserNotAuthenticated */,
CHALLENGE_NONE, 0 /* challenge */, null /* resetLockouts */);
unlockChildProfile(userId, true /* ignoreUserNotAuthenticated */);
}
}
@@ -830,6 +804,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
// TODO: maybe skip this for split system user mode.
mStorage.prefetchUser(UserHandle.USER_SYSTEM);
mBiometricDeferredQueue.systemReady();
}
private void getAuthSecretHal() {
@@ -1306,13 +1281,10 @@ public class LockSettingsService extends ILockSettings.Stub {
return credential;
}
private void unlockChildProfile(int profileHandle, boolean ignoreUserNotAuthenticated,
@ChallengeType int challengeType, long challenge,
@Nullable ArrayList<PendingResetLockout> resetLockouts) {
private void unlockChildProfile(int profileHandle, boolean ignoreUserNotAuthenticated) {
try {
doVerifyCredential(getDecryptedPasswordForTiedProfile(profileHandle),
challengeType, challenge, profileHandle, null /* progressCallback */,
resetLockouts, 0 /* flags */);
profileHandle, null /* progressCallback */, 0 /* flags */);
} catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
| NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -1327,10 +1299,6 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
private void unlockUser(int userId, byte[] token, byte[] secret) {
unlockUser(userId, token, secret, CHALLENGE_NONE, 0 /* challenge */, null);
}
/**
* Unlock the user (both storage and user state) and its associated managed profiles
* synchronously.
@@ -1339,9 +1307,7 @@ public class LockSettingsService extends ILockSettings.Stub {
* can end up calling into other system services to process user unlock request (via
* {@link com.android.server.SystemServiceManager#unlockUser} </em>
*/
private void unlockUser(int userId, byte[] token, byte[] secret,
@ChallengeType int challengeType, long challenge,
@Nullable ArrayList<PendingResetLockout> resetLockouts) {
private void unlockUser(int userId, byte[] token, byte[] secret) {
Slog.i(TAG, "Unlocking user " + userId + " with secret only, length "
+ (secret != null ? secret.length : 0));
// TODO: make this method fully async so we can update UI with progress strings
@@ -1378,6 +1344,9 @@ public class LockSettingsService extends ILockSettings.Stub {
}
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
if (!hasUnifiedChallenge(userId)) {
mBiometricDeferredQueue.processPendingLockoutResets();
}
return;
}
@@ -1388,10 +1357,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (hasUnifiedChallenge(profile.id)) {
if (mUserManager.isUserRunning(profile.id)) {
// Unlock managed profile with unified lock
// Must pass the challenge on for resetLockout, so it's not over-written, which
// causes LockSettingsService to revokeChallenge inappropriately.
unlockChildProfile(profile.id, false /* ignoreUserNotAuthenticated */,
challengeType, challenge, resetLockouts);
unlockChildProfile(profile.id, false /* ignoreUserNotAuthenticated */);
} else {
try {
// Profile not ready for unlock yet, but decrypt the unified challenge now
@@ -1412,22 +1378,9 @@ public class LockSettingsService extends ILockSettings.Stub {
restoreCallingIdentity(ident);
}
}
}
if (resetLockouts != null && !resetLockouts.isEmpty()) {
mHandler.post(() -> {
final BiometricManager bm = mContext.getSystemService(BiometricManager.class);
final PackageManager pm = mContext.getPackageManager();
for (int i = 0; i < resetLockouts.size(); i++) {
bm.resetLockout(resetLockouts.get(i).mUserId, resetLockouts.get(i).mHAT);
}
if (challengeType == CHALLENGE_INTERNAL
&& pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
mContext.getSystemService(FaceManager.class).revokeChallenge();
}
});
}
mBiometricDeferredQueue.processPendingLockoutResets();
}
private boolean hasUnifiedChallenge(int userId) {
@@ -1727,8 +1680,7 @@ public class LockSettingsService extends ILockSettings.Stub {
setUserKeyProtection(userId, credential, convertResponse(gkResponse));
fixateNewestUserKeyAuth(userId);
// Refresh the auth token
doVerifyCredential(credential, CHALLENGE_FROM_CALLER, 0, userId,
null /* progressCallback */, 0 /* flags */);
doVerifyCredential(credential, userId, null /* progressCallback */, 0 /* flags */);
synchronizeUnifiedWorkChallengeForProfiles(userId, null);
sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
return true;
@@ -1972,8 +1924,7 @@ public class LockSettingsService extends ILockSettings.Stub {
ICheckCredentialProgressCallback progressCallback) {
checkPasswordReadPermission(userId);
try {
return doVerifyCredential(credential, CHALLENGE_NONE, 0L, userId, progressCallback,
0 /* flags */);
return doVerifyCredential(credential, userId, progressCallback, 0 /* flags */);
} finally {
scheduleGc();
}
@@ -1984,10 +1935,8 @@ public class LockSettingsService extends ILockSettings.Stub {
public VerifyCredentialResponse verifyCredential(LockscreenCredential credential,
int userId, int flags) {
checkPasswordReadPermission(userId);
try {
return doVerifyCredential(credential, CHALLENGE_NONE, 0L, userId,
null /* progressCallback */, flags);
return doVerifyCredential(credential, userId, null /* progressCallback */, flags);
} finally {
scheduleGc();
}
@@ -2006,32 +1955,17 @@ public class LockSettingsService extends ILockSettings.Stub {
return response;
}
/**
/*
* Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
* format.
* @param credential User's lockscreen credential
* @param challengeType Owner of the challenge
* @param challenge Challenge to be wrapped within Gatekeeper's HAT, if the credential is
* verified
* @param userId User to verify the credential for
* @param progressCallback Receive progress callbacks
* @param flags See {@link LockPatternUtils.VerifyFlag}
* @return See {@link VerifyCredentialResponse}
*/
private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
@ChallengeType int challengeType, long challenge, int userId,
ICheckCredentialProgressCallback progressCallback,
@LockPatternUtils.VerifyFlag int flags) {
return doVerifyCredential(credential, challengeType, challenge, userId,
progressCallback, null /* resetLockouts */, flags);
}
/**
* Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
* format.
*/
private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
@ChallengeType int challengeType, long challenge, int userId,
ICheckCredentialProgressCallback progressCallback,
@Nullable ArrayList<PendingResetLockout> resetLockouts,
int userId, ICheckCredentialProgressCallback progressCallback,
@LockPatternUtils.VerifyFlag int flags) {
if (credential == null || credential.isNone()) {
throw new IllegalArgumentException("Credential can't be null or empty");
@@ -2041,9 +1975,10 @@ public class LockSettingsService extends ILockSettings.Stub {
Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
return VerifyCredentialResponse.ERROR;
}
VerifyCredentialResponse response = null;
response = spBasedDoVerifyCredential(credential, challengeType, challenge,
userId, progressCallback, resetLockouts, flags);
VerifyCredentialResponse response = spBasedDoVerifyCredential(credential, userId,
progressCallback, flags);
// The user employs synthetic password based credential.
if (response != null) {
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -2064,8 +1999,7 @@ public class LockSettingsService extends ILockSettings.Stub {
return VerifyCredentialResponse.ERROR;
}
response = verifyCredential(userId, storedHash, credential,
challengeType, challenge, progressCallback);
response = verifyCredential(userId, storedHash, credential, progressCallback);
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
@@ -2085,8 +2019,6 @@ public class LockSettingsService extends ILockSettings.Stub {
// Unlock parent by using parent's challenge
final VerifyCredentialResponse parentResponse = doVerifyCredential(
credential,
CHALLENGE_NONE,
0L,
parentProfileId,
null /* progressCallback */,
flags);
@@ -2098,10 +2030,7 @@ public class LockSettingsService extends ILockSettings.Stub {
try {
// Unlock work profile, and work profile with unified lock must use password only
return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
CHALLENGE_NONE,
0L,
userId, null /* progressCallback */,
flags);
userId, null /* progressCallback */, flags);
} catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
| NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -2119,8 +2048,7 @@ public class LockSettingsService extends ILockSettings.Stub {
* hash to GK.
*/
private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
LockscreenCredential credential, @ChallengeType int challengeType, long challenge,
ICheckCredentialProgressCallback progressCallback) {
LockscreenCredential credential, ICheckCredentialProgressCallback progressCallback) {
if ((storedHash == null || storedHash.hash.length == 0) && credential.isNone()) {
// don't need to pass empty credentials to GateKeeper
return VerifyCredentialResponse.OK;
@@ -2137,7 +2065,7 @@ public class LockSettingsService extends ILockSettings.Stub {
GateKeeperResponse gateKeeperResponse;
try {
gateKeeperResponse = getGateKeeperService().verifyChallenge(
userId, challenge, storedHash.hash, credential.getCredential());
userId, 0L /* challenge */, storedHash.hash, credential.getCredential());
} catch (RemoteException e) {
Slog.e(TAG, "gatekeeper verify failed", e);
gateKeeperResponse = GateKeeperResponse.ERROR;
@@ -2710,28 +2638,13 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
@ChallengeType int challengeType, long challenge,
int userId, ICheckCredentialProgressCallback progressCallback,
@Nullable ArrayList<PendingResetLockout> resetLockouts,
@LockPatternUtils.VerifyFlag int flags) {
final boolean hasEnrolledBiometrics = mInjector.hasEnrolledBiometrics(userId);
Slog.d(TAG, "spBasedDoVerifyCredential: user=" + userId + " challengeType=" + challengeType
Slog.d(TAG, "spBasedDoVerifyCredential: user=" + userId
+ " hasEnrolledBiometrics=" + hasEnrolledBiometrics);
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 (challengeType == CHALLENGE_NONE && pm.hasSystemFeature(PackageManager.FEATURE_FACE)
&& hasEnrolledBiometrics) {
// If there are multiple profiles in the same account, ensure we only generate the
// challenge once.
challengeType = CHALLENGE_INTERNAL;
challenge = mContext.getSystemService(FaceManager.class).generateChallengeBlocking();
}
final AuthenticationResult authResult;
VerifyCredentialResponse response;
final boolean returnGkPw = (flags & VERIFY_FLAG_RETURN_GK_PW) != 0;
@@ -2752,10 +2665,13 @@ public class LockSettingsService extends ILockSettings.Stub {
// credential has matched
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
authResult.authToken.deriveGkPassword());
// perform verifyChallenge with synthetic password which generates the real GK auth
// token and response for the current user
response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
challenge, userId);
0L /* challenge */, userId);
if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
// This shouldn't really happen: the unwrapping of SP succeeds, but SP doesn't
// match the recorded GK password handle.
@@ -2765,15 +2681,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
// Do resetLockout / revokeChallenge when all profiles are unlocked
if (hasEnrolledBiometrics) {
if (resetLockouts == null) {
resetLockouts = new ArrayList<>();
}
resetLockouts.add(new PendingResetLockout(userId, response.getGatekeeperHAT()));
}
onCredentialVerified(authResult.authToken, challengeType, challenge, resetLockouts,
onCredentialVerified(authResult.authToken,
PasswordMetrics.computeForCredential(userCredential), userId);
} else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
if (response.getTimeout() > 0) {
@@ -2789,9 +2697,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
private void onCredentialVerified(AuthenticationToken authToken,
@ChallengeType int challengeType, long challenge,
@Nullable ArrayList<PendingResetLockout> resetLockouts, PasswordMetrics metrics,
private void onCredentialVerified(AuthenticationToken authToken, PasswordMetrics metrics,
int userId) {
if (metrics != null) {
@@ -2806,7 +2712,7 @@ public class LockSettingsService extends ILockSettings.Stub {
{
final byte[] secret = authToken.deriveDiskEncryptionKey();
unlockUser(userId, null, secret, challengeType, challenge, resetLockouts);
unlockUser(userId, null, secret);
Arrays.fill(secret, (byte) 0);
}
activateEscrowTokens(authToken, userId);
@@ -3193,11 +3099,8 @@ public class LockSettingsService extends ILockSettings.Stub {
return false;
}
}
// TODO: Reset biometrics lockout here. Ideally that should be self-contained inside
// onCredentialVerified(), which will require some refactoring on the current lockout
// reset logic.
onCredentialVerified(authResult.authToken, CHALLENGE_NONE, 0, null,
onCredentialVerified(authResult.authToken,
loadPasswordMetrics(authResult.authToken, userId), userId);
return true;
}
@@ -3208,8 +3111,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (cred == null) {
return false;
}
return doVerifyCredential(cred, CHALLENGE_NONE, 0, userId,
null /* progressCallback */, 0 /* flags */)
return doVerifyCredential(cred, userId, null /* progressCallback */, 0 /* flags */)
.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
}
}
@@ -3532,8 +3434,7 @@ public class LockSettingsService extends ILockSettings.Stub {
SyntheticPasswordManager.AuthenticationToken
authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion);
authToken.recreateDirectly(syntheticPassword);
onCredentialVerified(authToken, CHALLENGE_NONE, 0, null,
loadPasswordMetrics(authToken, userId), userId);
onCredentialVerified(authToken, loadPasswordMetrics(authToken, userId), userId);
}
}
}

View File

@@ -269,21 +269,6 @@ public class AuthServiceTest {
eq(callback), eq(UserHandle.getCallingUserId()));
}
@Test
public void testResetLockout_callsBiometricServiceResetLockout() throws
Exception {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
final int userId = 100;
final byte[] token = new byte[0];
mAuthService.mImpl.resetLockout(userId, token);
waitForIdle();
verify(mBiometricService).resetLockout(eq(userId), AdditionalMatchers.aryEq(token));
}
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}