diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java index 4ec5e1b67c5ddee191f4db5cd4763658c4ca83b0..6404c4bc33d65c90d8eb32754861355cffa839fb 100644 --- a/keystore/java/android/security/Authorization.java +++ b/keystore/java/android/security/Authorization.java @@ -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); diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index ed9445c267404803513ea9ba80f9ff539b3e7056..ba8a76c9dddea842a7e4045cd2c2a79ee42e5edd 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -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 diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java index 37ca09d9fa2707cb120433317b829ed643c5b054..b41568298dbc1c32ff11d1cfc4d1364f07f767ca 100644 --- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java @@ -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) {