Remove all biometrics data of a user when password is cleared.

Previously, the biometrics were only cleared if the password was cleared from the Settings.
Moved the logic from the Settings app to the system server side.
Now, the biometrics will be removed no matter how the password is cleared (Settings, adb, TestDPC).

Bug: 130653263
Test: Atest LockSettingsServiceTests
      manual testing from Settings, adb and TestDPC

Change-Id: I840856b2e2db5e595d33583fd79359fde6bccb6f
This commit is contained in:
Alex Johnston
2019-10-03 15:59:03 +01:00
parent 87dfbb36b3
commit 6183cf9952
6 changed files with 184 additions and 9 deletions

View File

@@ -61,7 +61,10 @@ import android.database.ContentObserver;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.authsecret.V1_0.IAuthSecret;
import android.hardware.biometrics.BiometricManager;
import android.hardware.face.Face;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -117,6 +120,7 @@ import com.android.internal.widget.LockPatternUtils.CredentialType;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
@@ -387,8 +391,15 @@ public class LockSettingsService extends ILockSettings.Stub {
return mContext;
}
public Handler getHandler() {
return new Handler();
public ServiceThread getServiceThread() {
ServiceThread handlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
true /*allowIo*/);
handlerThread.start();
return handlerThread;
}
public Handler getHandler(ServiceThread handlerThread) {
return new Handler(handlerThread.getLooper());
}
public LockSettingsStorage getStorage() {
@@ -483,6 +494,23 @@ public class LockSettingsService extends ILockSettings.Stub {
public boolean isGsiRunning() {
return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
}
public FingerprintManager getFingerprintManager() {
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
return (FingerprintManager) mContext.getSystemService(Context.FINGERPRINT_SERVICE);
} else {
return null;
}
}
public FaceManager getFaceManager() {
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
return (FaceManager) mContext.getSystemService(Context.FACE_SERVICE);
} else {
return null;
}
}
}
public LockSettingsService(Context context) {
@@ -495,7 +523,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mContext = injector.getContext();
mKeyStore = injector.getKeyStore();
mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager(mKeyStore);
mHandler = injector.getHandler();
mHandler = injector.getHandler(injector.getServiceThread());
mStrongAuth = injector.getStrongAuth();
mActivityManager = injector.getActivityManager();
@@ -2713,6 +2741,7 @@ public class LockSettingsService extends ILockSettings.Stub {
fixateNewestUserKeyAuth(userId);
unlockKeystore(auth.deriveKeyStorePassword(), userId);
setKeystorePassword(null, userId);
removeBiometricsForUser(userId);
}
setSyntheticPasswordHandleLocked(newHandle, userId);
synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
@@ -2728,6 +2757,85 @@ public class LockSettingsService extends ILockSettings.Stub {
return newHandle;
}
private void removeBiometricsForUser(int userId) {
removeAllFingerprintForUser(userId);
removeAllFaceForUser(userId);
}
private void removeAllFingerprintForUser(final int userId) {
FingerprintManager mFingerprintManager = mInjector.getFingerprintManager();
if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
if (mFingerprintManager.hasEnrolledFingerprints(userId)) {
mFingerprintManager.setActiveUser(userId);
CountDownLatch latch = new CountDownLatch(1);
// For the purposes of M and N, groupId is the same as userId.
Fingerprint finger = new Fingerprint(null, userId, 0, 0);
mFingerprintManager.remove(finger, userId,
fingerprintManagerRemovalCallback(latch));
try {
latch.await(10000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Slog.e(TAG, "Latch interrupted when removing fingerprint", e);
}
}
}
}
private void removeAllFaceForUser(final int userId) {
FaceManager mFaceManager = mInjector.getFaceManager();
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
if (mFaceManager.hasEnrolledTemplates(userId)) {
mFaceManager.setActiveUser(userId);
CountDownLatch latch = new CountDownLatch(1);
Face face = new Face(null, 0, 0);
mFaceManager.remove(face, userId, faceManagerRemovalCallback(latch));
try {
latch.await(10000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Slog.e(TAG, "Latch interrupted when removing face", e);
}
}
}
}
private FingerprintManager.RemovalCallback fingerprintManagerRemovalCallback(
CountDownLatch latch) {
return new FingerprintManager.RemovalCallback() {
@Override
public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence err) {
Slog.e(TAG, String.format(
"Can't remove fingerprint %d in group %d. Reason: %s",
fp.getBiometricId(), fp.getGroupId(), err));
latch.countDown();
}
@Override
public void onRemovalSucceeded(Fingerprint fp, int remaining) {
if (remaining == 0) {
latch.countDown();
}
}
};
}
private FaceManager.RemovalCallback faceManagerRemovalCallback(CountDownLatch latch) {
return new FaceManager.RemovalCallback() {
@Override
public void onRemovalError(Face face, int errMsgId, CharSequence err) {
Slog.e(TAG, String.format("Can't remove face %d. Reason: %s",
face.getBiometricId(), err));
latch.countDown();
}
@Override
public void onRemovalSucceeded(Face face, int remaining) {
if (remaining == 0) {
latch.countDown();
}
}
};
}
@GuardedBy("mSpManager")
private boolean spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType,
byte[] savedCredential, int requestedQuality, int userId,

View File

@@ -32,8 +32,11 @@ import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DeviceStateCache;
import android.app.trust.TrustManager;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.authsecret.V1_0.IAuthSecret;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.FileUtils;
import android.os.IProgressListener;
import android.os.RemoteException;
@@ -95,6 +98,9 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
RecoverableKeyStoreManager mRecoverableKeyStoreManager;
UserManagerInternal mUserManagerInternal;
DeviceStateCache mDeviceStateCache;
FingerprintManager mFingerprintManager;
FaceManager mFaceManager;
PackageManager mPackageManager;
protected boolean mHasSecureLockScreen;
@Override
@@ -114,6 +120,9 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
mRecoverableKeyStoreManager = mock(RecoverableKeyStoreManager.class);
mUserManagerInternal = mock(UserManagerInternal.class);
mDeviceStateCache = mock(DeviceStateCache.class);
mFingerprintManager = mock(FingerprintManager.class);
mFaceManager = mock(FaceManager.class);
mPackageManager = mock(PackageManager.class);
LocalServices.removeServiceForTest(LockSettingsInternal.class);
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -123,7 +132,7 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
mock(KeyguardManager.class));
mock(KeyguardManager.class), mFingerprintManager, mFaceManager, mPackageManager);
mStorage = new LockSettingsStorageTestable(mContext,
new File(getContext().getFilesDir(), "locksettings"));
File storageDir = mStorage.mStorageDir;
@@ -181,6 +190,8 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
when(mUserManagerInternal.isDeviceManaged()).thenReturn(true);
when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
mockBiometricsHardwareFingerprintsAndTemplates(PRIMARY_USER_ID);
mockBiometricsHardwareFingerprintsAndTemplates(MANAGED_PROFILE_USER_ID);
mLocalService = LocalServices.getService(LockSettingsInternal.class);
}
@@ -233,6 +244,18 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
return sm;
}
private void mockBiometricsHardwareFingerprintsAndTemplates(int userId) {
// Hardware must be detected and fingerprints must be enrolled
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledFingerprints(userId)).thenReturn(true);
// Hardware must be detected and templates must be enrolled
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(userId)).thenReturn(true);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();

View File

@@ -23,7 +23,6 @@ import android.app.admin.DeviceStateCache;
import android.content.Context;
import android.hardware.authsecret.V1_0.IAuthSecret;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserManagerInternal;
@@ -32,6 +31,7 @@ import android.security.KeyStore;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.ServiceThread;
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
import java.io.FileNotFoundException;
@@ -70,8 +70,8 @@ public class LockSettingsServiceTestable extends LockSettingsService {
}
@Override
public Handler getHandler() {
return new Handler(Looper.getMainLooper());
public Handler getHandler(ServiceThread handlerThread) {
return new Handler(handlerThread.getLooper());
}
@Override

View File

@@ -330,6 +330,27 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
.lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, MANAGED_PROFILE_USER_ID);
}
public void testSetLockCredential_nullCredential_removeBiometrics() throws RemoteException {
final String oldCredential = "oldPassword";
initializeStorageWithCredential(
PRIMARY_USER_ID,
oldCredential,
CREDENTIAL_TYPE_PATTERN,
PASSWORD_QUALITY_SOMETHING);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, oldCredential.getBytes(),
PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
// Verify fingerprint is removed
verify(mFingerprintManager).remove(any(), eq(PRIMARY_USER_ID), any());
verify(mFaceManager).remove(any(), eq(PRIMARY_USER_ID), any());
verify(mFingerprintManager).remove(any(), eq(MANAGED_PROFILE_USER_ID), any());
verify(mFaceManager).remove(any(), eq(MANAGED_PROFILE_USER_ID), any());
}
public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials()
throws Exception {
final String parentPassword = "parentPassword";

View File

@@ -24,8 +24,11 @@ import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.FileUtils;
import android.os.SystemClock;
import android.os.UserManager;
@@ -86,7 +89,9 @@ public class LockSettingsStorageTests extends AndroidTestCase {
MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager,
mock(NotificationManager.class), mock(DevicePolicyManager.class),
mock(StorageManager.class), mock(TrustManager.class), mock(KeyguardManager.class));
mock(StorageManager.class), mock(TrustManager.class), mock(KeyguardManager.class),
mock(FingerprintManager.class), mock(FaceManager.class),
mock(PackageManager.class));
mStorage = new LockSettingsStorageTestable(context,
new File(getContext().getFilesDir(), "locksettings"));
mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {

View File

@@ -23,6 +23,8 @@ import android.app.trust.TrustManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.UserManager;
import android.os.storage.StorageManager;
@@ -34,11 +36,15 @@ public class MockLockSettingsContext extends ContextWrapper {
private StorageManager mStorageManager;
private TrustManager mTrustManager;
private KeyguardManager mKeyguardManager;
private FingerprintManager mFingerprintManager;
private FaceManager mFaceManager;
private PackageManager mPackageManager;
public MockLockSettingsContext(Context base, UserManager userManager,
NotificationManager notificationManager, DevicePolicyManager devicePolicyManager,
StorageManager storageManager, TrustManager trustManager,
KeyguardManager keyguardManager) {
KeyguardManager keyguardManager, FingerprintManager fingerprintManager,
FaceManager faceManager, PackageManager packageManager) {
super(base);
mUserManager = userManager;
mNotificationManager = notificationManager;
@@ -46,6 +52,9 @@ public class MockLockSettingsContext extends ContextWrapper {
mStorageManager = storageManager;
mTrustManager = trustManager;
mKeyguardManager = keyguardManager;
mFingerprintManager = fingerprintManager;
mFaceManager = faceManager;
mPackageManager = packageManager;
}
@Override
@@ -62,11 +71,20 @@ public class MockLockSettingsContext extends ContextWrapper {
return mTrustManager;
} else if (KEYGUARD_SERVICE.equals(name)) {
return mKeyguardManager;
} else if (FINGERPRINT_SERVICE.equals(name)) {
return mFingerprintManager;
} else if (FACE_SERVICE.equals(name)) {
return mFaceManager;
} else {
throw new RuntimeException("System service not mocked: " + name);
}
}
@Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@Override
public void enforceCallingOrSelfPermission(String permission, String message) {
// Skip permission checks for unit tests.