Skip to content
Snippets Groups Projects
Commit cd7f4fad authored by Eric Biggers's avatar Eric Biggers
Browse files

Fix UnlockedDeviceRequired with weak unlock methods

Starting in Android 12, unlocking the device with a class 1
("convenience") biometric, class 2 ("weak") biometric, or a trust agent
unexpectedly doesn't allow the use of UnlockedDeviceRequired keys.  The
cause of this bug is that the cryptographic protection that Keystore now
applies to UnlockedDeviceRequired keys incorrectly assumes that the
device can only be unlocked using LSKF or via a biometric that
participates in Keystore (has a SID and uses HardwareAuthTokens).
Actually, Keyguard also allows the device to be unlocked using weaker
biometrics that do not particiate in Keystore, if they are enrolled.
Similarly, there are also cases where a trust agent can actively unlock
the device, e.g. unlocking a phone using a paired watch.

In combination with the Keystore changes in
I1b0d9ec4f9e31dc91642e865045766bd17e34cad, this CL fixes the bug by
making Keystore retain the UnlockedDeviceRequired super keys in memory
if a weak unlock method is enabled at device lock time.  This does mean
that UnlockedDeviceRequired is enforced only logically when a weak
unlock method is enabled, but this is the best we can do in this case.

Note: a future CL will take into account the progressive expiration of
unlock methods while the device is locked and upgrade the security of
UnlockedDeviceRequired accordingly.  The present CL focuses just on
choosing the correct protection at lock time, fixing a user-visible bug.

Test: Ran the following automated tests with and without the
      fix_unlocked_device_required_keys_v2 flag enabled:
          atest com.android.server.locksettings \
          && atest TrustManagerServiceTest \
          && atest TrustTests \
          && atest -p --include-subdirs system/security/keystore2 \
          && atest CtsKeystoreTestCases

Test: Manually tested each combination of biometric setup: none,
      fingerprint, face, and fingerprint+face.  Locked the device, then
      verified via logcat that Keystore protected the
      UnlockedDeviceRequired keys in the expected way, then verified
      that UnlockedDeviceRequired keys cannot be used (even in the case
      where the super keys were not protected).  Unlocked device using
      weakest method available, then verified that
      UnlockedDeviceRequired keys can be used.  To check whether
      UnlockedDeviceRequired keys can be used or not, used the CTS
      method mentioned in the Test of https://r.android.com/2878769.

      Also, enabled Extend Unlock with a bluetooth device, and verified
      that it's not counted as an unlock method.

      Also, verified that if Lockdown mode is triggered, the
      UnlockedDeviceRequired keys are fully protected.

Bug: 296464083
Change-Id: I34dc49f1338e94755e96c1cf84de0638dc70d311
parent 31fd707a
No related branches found
No related tags found
No related merge requests found
......@@ -100,12 +100,14 @@ public class Authorization {
*
* @param userId - the user's Android user ID
* @param unlockingSids - list of biometric SIDs with which the device may be unlocked again
* @param weakUnlockEnabled - true if non-strong biometric or trust agent unlock is enabled
* @return 0 if successful or a {@code ResponseCode}.
*/
public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids) {
public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
boolean weakUnlockEnabled) {
StrictMode.noteDiskWrite();
try {
getService().onDeviceLocked(userId, unlockingSids);
getService().onDeviceLocked(userId, unlockingSids, weakUnlockEnabled);
return 0;
} catch (RemoteException | NullPointerException e) {
Log.w(TAG, "Can not connect to keystore", e);
......
......@@ -44,6 +44,9 @@ import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
......@@ -157,6 +160,8 @@ public class TrustManagerService extends SystemService {
private final LockPatternUtils mLockPatternUtils;
private final UserManager mUserManager;
private final ActivityManager mActivityManager;
private FingerprintManager mFingerprintManager;
private FaceManager mFaceManager;
private VirtualDeviceManagerInternal mVirtualDeviceManager;
private enum TrustState {
......@@ -294,6 +299,8 @@ public class TrustManagerService extends SystemService {
mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
mReceiver.register(mContext);
mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
mFaceManager = mContext.getSystemService(FaceManager.class);
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
mTrustAgentsCanRun = true;
refreshAgentList(UserHandle.USER_ALL);
......@@ -895,7 +902,19 @@ public class TrustManagerService extends SystemService {
private void notifyKeystoreOfDeviceLockState(int userId, boolean isLocked) {
if (isLocked) {
Authorization.onDeviceLocked(userId, getBiometricSids(userId));
if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()) {
// A profile with unified challenge is unlockable not by its own biometrics and
// trust agents, but rather by those of the parent user. Therefore, when protecting
// the profile's UnlockedDeviceRequired keys, we must use the parent's list of
// biometric SIDs and weak unlock methods, not the profile's.
int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
? resolveProfileParent(userId) : userId;
Authorization.onDeviceLocked(userId, getBiometricSids(authUserId),
isWeakUnlockMethodEnabled(authUserId));
} else {
Authorization.onDeviceLocked(userId, getBiometricSids(userId), false);
}
} else {
// Notify Keystore that the device is now unlocked for the user. Note that for unlocks
// with LSKF, this is redundant with the call from LockSettingsService which provides
......@@ -1442,16 +1461,59 @@ public class TrustManagerService extends SystemService {
if (biometricManager == null) {
return new long[0];
}
if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()
&& mLockPatternUtils.isProfileWithUnifiedChallenge(userId)) {
// Profiles with unified challenge have their own set of biometrics, but the device
// unlock happens via the parent user. In this case Keystore needs to be given the list
// of biometric SIDs from the parent user, not the profile.
userId = resolveProfileParent(userId);
}
return biometricManager.getAuthenticatorIds(userId);
}
// Returns whether the device can become unlocked for the specified user via one of that user's
// non-strong biometrics or trust agents. This assumes that the device is currently locked, or
// is becoming locked, for the user.
private boolean isWeakUnlockMethodEnabled(int userId) {
// Check whether the system currently allows the use of non-strong biometrics for the user,
// *and* the user actually has a non-strong biometric enrolled.
//
// The biometrics framework ostensibly supports multiple sensors per modality. However,
// that feature is unused and untested. So, we simply consider one sensor per modality.
//
// Also, currently we just consider fingerprint and face, matching Keyguard. If Keyguard
// starts supporting other biometric modalities, this will need to be updated.
if (mStrongAuthTracker.isBiometricAllowedForUser(/* isStrongBiometric= */ false, userId)) {
DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userId);
if (mFingerprintManager != null
&& (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) == 0
&& mFingerprintManager.hasEnrolledTemplates(userId)
&& isWeakOrConvenienceSensor(
mFingerprintManager.getSensorProperties().get(0))) {
Slog.i(TAG, "User is unlockable by non-strong fingerprint auth");
return true;
}
if (mFaceManager != null
&& (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FACE) == 0
&& mFaceManager.hasEnrolledTemplates(userId)
&& isWeakOrConvenienceSensor(mFaceManager.getSensorProperties().get(0))) {
Slog.i(TAG, "User is unlockable by non-strong face auth");
return true;
}
}
// Check whether it's possible for the device to be actively unlocked by a trust agent.
if (getUserTrustStateInner(userId) == TrustState.TRUSTABLE
|| (isAutomotive() && isTrustUsuallyManagedInternal(userId))) {
Slog.i(TAG, "User is unlockable by trust agent");
return true;
}
return false;
}
private static boolean isWeakOrConvenienceSensor(SensorProperties sensor) {
return sensor.getSensorStrength() == SensorProperties.STRENGTH_WEAK
|| sensor.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE;
}
// User lifecycle
@Override
......
......@@ -47,6 +47,10 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
......@@ -69,6 +73,7 @@ import android.view.WindowManagerGlobal;
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
......@@ -105,6 +110,8 @@ public class TrustManagerServiceTest {
private static final String URI_SCHEME_PACKAGE = "package";
private static final int TEST_USER_ID = 50;
private static final UserInfo TEST_USER =
new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL);
private static final int PARENT_USER_ID = 60;
private static final int PROFILE_USER_ID = 70;
private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L };
......@@ -117,6 +124,8 @@ public class TrustManagerServiceTest {
private @Mock ActivityManager mActivityManager;
private @Mock BiometricManager mBiometricManager;
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock FaceManager mFaceManager;
private @Mock FingerprintManager mFingerprintManager;
private @Mock IKeystoreAuthorization mKeystoreAuthorization;
private @Mock LockPatternUtils mLockPatternUtils;
private @Mock PackageManager mPackageManager;
......@@ -133,6 +142,9 @@ public class TrustManagerServiceTest {
when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
doReturn(mock(IActivityManager.class)).when(() -> ActivityManager.getService());
when(mFaceManager.getSensorProperties()).thenReturn(List.of());
when(mFingerprintManager.getSensorProperties()).thenReturn(List.of());
doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
......@@ -161,13 +173,16 @@ public class TrustManagerServiceTest {
when(mPackageManager.checkPermission(any(), any())).thenReturn(
PackageManager.PERMISSION_GRANTED);
when(mUserManager.getAliveUsers()).thenReturn(
List.of(new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL)));
when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER));
when(mUserManager.getEnabledProfileIds(TEST_USER_ID)).thenReturn(new int[0]);
when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(TEST_USER);
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager);
mMockContext.addMockSystemService(FaceManager.class, mFaceManager);
mMockContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
mMockContext.setMockPackageManager(mPackageManager);
mMockContext.addMockSystemService(UserManager.class, mUserManager);
doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
......@@ -362,9 +377,9 @@ public class TrustManagerServiceTest {
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mTrustManager.reportKeyguardShowingChanged();
verify(mKeystoreAuthorization)
.onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
.onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
verify(mKeystoreAuthorization)
.onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
.onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
}
// Tests that when the device is locked for a managed profile with a *separate* challenge, the
......@@ -381,7 +396,188 @@ public class TrustManagerServiceTest {
mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
verify(mKeystoreAuthorization)
.onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS));
.onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS), eq(false));
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockEnabled_whenWeakFingerprintIsSetupAndAllowed()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFingerprint(SensorProperties.STRENGTH_WEAK);
verifyWeakUnlockEnabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockEnabled_whenWeakFaceIsSetupAndAllowed() throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFace(SensorProperties.STRENGTH_WEAK);
verifyWeakUnlockEnabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockEnabled_whenConvenienceFingerprintIsSetupAndAllowed()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFingerprint(SensorProperties.STRENGTH_CONVENIENCE);
verifyWeakUnlockEnabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockEnabled_whenConvenienceFaceIsSetupAndAllowed()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFace(SensorProperties.STRENGTH_CONVENIENCE);
verifyWeakUnlockEnabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenStrongAuthRequired() throws Exception {
setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, true);
setupFace(SensorProperties.STRENGTH_WEAK);
verifyWeakUnlockDisabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenNonStrongBiometricNotAllowed() throws Exception {
setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED,
/* isNonStrongBiometricAllowed= */ false);
setupFace(SensorProperties.STRENGTH_WEAK);
verifyWeakUnlockDisabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenWeakFingerprintSensorIsPresentButNotEnrolled()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFingerprint(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
verifyWeakUnlockDisabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenWeakFaceSensorIsPresentButNotEnrolled()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFace(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
verifyWeakUnlockDisabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void
testKeystoreWeakUnlockDisabled_whenWeakFingerprintIsSetupButForbiddenByDevicePolicy()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFingerprint(SensorProperties.STRENGTH_WEAK);
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
verifyWeakUnlockDisabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenWeakFaceIsSetupButForbiddenByDevicePolicy()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFace(SensorProperties.STRENGTH_WEAK);
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
verifyWeakUnlockDisabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFingerprintIsSetup() throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFingerprint(SensorProperties.STRENGTH_STRONG);
verifyWeakUnlockDisabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFaceIsSetup() throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFace(SensorProperties.STRENGTH_STRONG);
verifyWeakUnlockDisabled();
}
@Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenNoBiometricsAreSetup() throws Exception {
setupStrongAuthTrackerToAllowEverything();
verifyWeakUnlockDisabled();
}
private void setupStrongAuthTrackerToAllowEverything() throws Exception {
setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED, true);
}
private void setupStrongAuthTracker(int strongAuthFlags, boolean isNonStrongBiometricAllowed)
throws Exception {
bootService();
mService.onUserSwitching(null, new SystemService.TargetUser(TEST_USER));
ArgumentCaptor<StrongAuthTracker> strongAuthTracker =
ArgumentCaptor.forClass(StrongAuthTracker.class);
verify(mLockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture());
strongAuthTracker.getValue().getStub().onStrongAuthRequiredChanged(
strongAuthFlags, TEST_USER_ID);
strongAuthTracker.getValue().getStub().onIsNonStrongBiometricAllowedChanged(
isNonStrongBiometricAllowed, TEST_USER_ID);
mService.waitForIdle();
}
private void setupFingerprint(int strength) {
setupFingerprint(strength, /* enrolled= */ true);
}
private void setupFingerprint(int strength, boolean enrolled) {
int sensorId = 100;
List<SensorProperties.ComponentInfo> componentInfo = List.of();
SensorProperties sensor = new SensorProperties(sensorId, strength, componentInfo);
when(mFingerprintManager.getSensorProperties()).thenReturn(List.of(sensor));
when(mFingerprintManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
}
private void setupFace(int strength) {
setupFace(strength, /* enrolled= */ true);
}
private void setupFace(int strength, boolean enrolled) {
int sensorId = 100;
List<SensorProperties.ComponentInfo> componentInfo = List.of();
FaceSensorProperties sensor = new FaceSensorProperties(
sensorId, strength, componentInfo, FaceSensorProperties.TYPE_RGB);
when(mFaceManager.getSensorProperties()).thenReturn(List.of(sensor));
when(mFaceManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
}
private void verifyWeakUnlockEnabled() throws Exception {
verifyWeakUnlockValue(true);
}
private void verifyWeakUnlockDisabled() throws Exception {
verifyWeakUnlockValue(false);
}
// Simulates a device unlock and a device lock, then verifies that the expected
// weakUnlockEnabled flag was passed to Keystore's onDeviceLocked method.
private void verifyWeakUnlockValue(boolean expectedWeakUnlockEnabled) throws Exception {
when(mWindowManager.isKeyguardLocked()).thenReturn(false);
mTrustManager.reportKeyguardShowingChanged();
verify(mKeystoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mTrustManager.reportKeyguardShowingChanged();
verify(mKeystoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
eq(expectedWeakUnlockEnabled));
}
private void setupMocksForProfile(boolean unifiedChallenge) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment