2/n: Keep track of IBiometricsFace@1.0 challenge interruptions

IBiometricsFace@1.0 only supports a single in-flight challenge.
Currently, challenge owners paths are designed to never conflict
lifecycle-wise. However, to decouple resetLockout (which relies
on challenge) from the critical user unlock path, we need to
keep track of challenge interruptions, and notify clients when
an interruption starts/finishes.

This change only adds the infrastructure for tracking challenge
interruptions. A following change will make use of this.

Bug: 145978626
Test: Builds
Change-Id: I708312cafad29614e3708b5c606ae8a90704ac66
This commit is contained in:
Kevin Chyn
2020-08-05 21:56:31 -07:00
parent 86943d140f
commit e44a86b2ed
5 changed files with 137 additions and 6 deletions

View File

@@ -72,6 +72,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
private static final int MSG_SET_FEATURE_COMPLETED = 107;
private static final int MSG_CHALLENGE_GENERATED = 108;
private static final int MSG_FACE_DETECTED = 109;
private static final int MSG_CHALLENGE_INTERRUPTED = 110;
private static final int MSG_CHALLENGE_INTERRUPT_FINISHED = 111;
private final IFaceService mService;
private final Context mContext;
@@ -150,6 +152,16 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, challenge).sendToTarget();
}
}
@Override
public void onChallengeInterrupted(int sensorId) {
mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPTED, sensorId).sendToTarget();
}
@Override
public void onChallengeInterruptFinished(int sensorId) {
mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPT_FINISHED, sensorId).sendToTarget();
}
};
/**
@@ -1071,10 +1083,25 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
* Callback structure provided to {@link #generateChallenge(GenerateChallengeCallback)}.
* @hide
*/
public interface GenerateChallengeCallback {
/**
* Invoked when a challenge has been generated.
*/
void onGenerateChallengeResult(long challenge);
/**
* Invoked if the challenge has not been revoked and a subsequent caller/owner invokes
* {@link #generateChallenge(GenerateChallengeCallback)}, but
*/
default void onChallengeInterrupted(int sensorId) {}
/**
* Invoked when the interrupting client has finished (e.g. revoked its challenge).
*/
default void onChallengeInterruptFinished(int sensorId) {}
}
private abstract static class InternalGenerateChallengeCallback
@@ -1157,6 +1184,12 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
(boolean) msg.obj /* isStrongBiometric */);
break;
case MSG_CHALLENGE_INTERRUPTED:
sendChallengeInterrupted((int) msg.obj /* sensorId */);
break;
case MSG_CHALLENGE_INTERRUPT_FINISHED:
sendChallengeInterruptFinished((int) msg.obj /* sensorId */);
break;
default:
Slog.w(TAG, "Unknown message: " + msg.what);
}
@@ -1193,6 +1226,22 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
}
private void sendChallengeInterrupted(int sensorId) {
if (mGenerateChallengeCallback == null) {
Slog.e(TAG, "sendChallengeInterrupted, callback null");
return;
}
mGenerateChallengeCallback.onChallengeInterrupted(sensorId);
}
private void sendChallengeInterruptFinished(int sensorId) {
if (mGenerateChallengeCallback == null) {
Slog.e(TAG, "sendChallengeInterruptFinished, callback null");
return;
}
mGenerateChallengeCallback.onChallengeInterruptFinished(sensorId);
}
private void sendRemovedResult(Face face, int remaining) {
if (mRemovalCallback == null) {
return;

View File

@@ -32,4 +32,6 @@ oneway interface IFaceServiceReceiver {
void onFeatureSet(boolean success, int feature);
void onFeatureGet(boolean success, int feature, boolean value);
void onChallengeGenerated(long challenge);
void onChallengeInterrupted(int sensorId);
void onChallengeInterruptFinished(int sensorId);
}

View File

@@ -142,10 +142,21 @@ public final class ClientMonitorCallbackConverter {
}
}
public void onFeatureGet(boolean success, int feature, boolean value)
throws RemoteException {
public void onFeatureGet(boolean success, int feature, boolean value) throws RemoteException {
if (mFaceServiceReceiver != null) {
mFaceServiceReceiver.onFeatureGet(success, feature, value);
}
}
public void onChallengeInterrupted(int sensorId) throws RemoteException {
if (mFaceServiceReceiver != null) {
mFaceServiceReceiver.onChallengeInterrupted(sensorId);
}
}
public void onChallengeInterruptFinished(int sensorId) throws RemoteException {
if (mFaceServiceReceiver != null) {
mFaceServiceReceiver.onChallengeInterruptFinished(sensorId);
}
}
}

View File

@@ -98,6 +98,11 @@ class Face10 implements IHwBinder.DeathRecipient {
@Nullable private IBiometricsFace mDaemon;
private int mCurrentUserId = UserHandle.USER_NULL;
// If a challenge is generated, keep track of its owner. Since IBiometricsFace@1.0 only
// supports a single in-flight challenge, we must notify the interrupted owner that its
// challenge is no longer valid. The interrupted owner will be notified when the interrupter
// has finished.
@Nullable private FaceGenerateChallengeClient mCurrentChallengeOwner;
private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
@Override
@@ -462,18 +467,68 @@ class Face10 implements IHwBinder.DeathRecipient {
void scheduleGenerateChallenge(@NonNull IBinder token, @NonNull IFaceServiceReceiver receiver,
@NonNull String opPackageName) {
mHandler.post(() -> {
if (mCurrentChallengeOwner != null) {
Slog.w(TAG, "Current challenge owner: " + mCurrentChallengeOwner
+ ", interrupted by: " + opPackageName);
try {
mCurrentChallengeOwner.getListener().onChallengeInterrupted(mSensorId);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify challenge interrupted", e);
}
}
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
mSensorId);
mScheduler.scheduleClientMonitor(client);
mSensorId, mCurrentChallengeOwner);
mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
@Override
public void onClientStarted(@NonNull ClientMonitor<?> clientMonitor) {
if (client != clientMonitor) {
Slog.e(TAG, "scheduleGenerateChallenge, mismatched client."
+ " Expecting: " + client + ", received: " + clientMonitor);
return;
}
Slog.d(TAG, "Current challenge owner: " + client);
mCurrentChallengeOwner = client;
}
});
});
}
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: "
+ mCurrentChallengeOwner.getOwnerString());
return;
}
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
mLazyDaemon, token, owner, mSensorId);
mScheduler.scheduleClientMonitor(client);
mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
@Override
public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
boolean success) {
if (client != clientMonitor) {
Slog.e(TAG, "scheduleRevokeChallenge, mismatched client."
+ "Expecting: " + client + ", received: " + clientMonitor);
return;
}
final FaceGenerateChallengeClient previousChallengeOwner =
mCurrentChallengeOwner.getInterruptedClient();
mCurrentChallengeOwner = null;
Slog.d(TAG, "Previous challenge owner: " + previousChallengeOwner);
if (previousChallengeOwner != null) {
try {
previousChallengeOwner.getListener()
.onChallengeInterruptFinished(mSensorId);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify interrupt finished", e);
}
}
}
});
});
}

View File

@@ -17,12 +17,14 @@
package com.android.server.biometrics.sensors.face;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.sensors.ClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;
@@ -36,10 +38,22 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiomet
private static final String TAG = "FaceGenerateChallengeClient";
private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
// If `this` FaceGenerateChallengeClient was invoked while an existing in-flight challenge
// was not revoked yet, store a reference to the interrupted client here. Notify the interrupted
// client when `this` challenge is revoked.
@Nullable private final FaceGenerateChallengeClient mInterruptedClient;
FaceGenerateChallengeClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, @NonNull String owner, int sensorId) {
@NonNull ClientMonitorCallbackConverter listener, @NonNull String owner, int sensorId,
@Nullable FaceGenerateChallengeClient interruptedClient) {
super(context, lazyDaemon, token, listener, owner, sensorId);
mInterruptedClient = interruptedClient;
}
@Nullable
public FaceGenerateChallengeClient getInterruptedClient() {
return mInterruptedClient;
}
@Override