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

This commit is contained in:
Kevin Chyn
2020-08-16 23:21:21 +00:00
committed by Android (Google) Code Review
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();
}