Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5454b851 authored by Grace Cheng's avatar Grace Cheng
Browse files

Updates locked/unlocked state for Secure Lock Device

Adds secure lock device state & DeviceEntryRestrictionReason mappings
for the new auth flags. The device should not be considered unlocked
after bouncer auth when Secure Lock Device is enabled; it is only
unlocked after both stages of secure lock device (LSKF-only bouncer
auth and strong-biometric-only auth) are complete.

These changes are for the post-flexiglass secure lock device
implementation.

Bug: 401645997
Bug: 396641431
Flag: android.security.secure_lock_device
Flag: android.security.secure_lockdown
Test: atest DeviceUnlockedInteractorTest
Change-Id: If95ff8f7af2838345a1503fd1a5f0d420095330c
parent 778440d8
Loading
Loading
Loading
Loading
+122 −3
Original line number Diff line number Diff line
@@ -18,10 +18,13 @@ package com.android.systemui.deviceentry.domain.interactor

import android.content.pm.UserInfo
import android.os.PowerManager
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.security.Flags.FLAG_SECURE_LOCK_DEVICE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -30,6 +33,7 @@ import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryBypassRep
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -43,6 +47,8 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.securelockdevice.data.repository.fakeSecureLockDeviceRepository
import com.android.systemui.securelockdevice.domain.interactor.secureLockDeviceInteractor
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -61,12 +67,11 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceUnlockedInteractorTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val authenticationRepository = kosmos.fakeAuthenticationRepository
    private val authenticationRepository by lazy { kosmos.fakeAuthenticationRepository }

    val underTest = kosmos.deviceUnlockedInteractor
    val underTest: DeviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }

    @Before
    fun setup() {
@@ -376,6 +381,11 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
                    DeviceEntryRestrictionReason.UnattendedUpdate,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
                    DeviceEntryRestrictionReason.PolicyLockdown,
                LockPatternUtils.StrongAuthTracker.PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE to
                    DeviceEntryRestrictionReason.SecureLockDevicePrimaryAuth,
                LockPatternUtils.StrongAuthTracker
                    .STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE to
                    DeviceEntryRestrictionReason.SecureLockDeviceStrongBiometricOnlyAuth,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
                    null,
@@ -410,6 +420,11 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
                    DeviceEntryRestrictionReason.UnattendedUpdate,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
                    DeviceEntryRestrictionReason.PolicyLockdown,
                LockPatternUtils.StrongAuthTracker.PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE to
                    DeviceEntryRestrictionReason.SecureLockDevicePrimaryAuth,
                LockPatternUtils.StrongAuthTracker
                    .STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE to
                    DeviceEntryRestrictionReason.SecureLockDeviceStrongBiometricOnlyAuth,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
                    null,
@@ -445,6 +460,11 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
                    DeviceEntryRestrictionReason.UnattendedUpdate,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
                    DeviceEntryRestrictionReason.PolicyLockdown,
                LockPatternUtils.StrongAuthTracker.PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE to
                    DeviceEntryRestrictionReason.SecureLockDevicePrimaryAuth,
                LockPatternUtils.StrongAuthTracker
                    .STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE to
                    DeviceEntryRestrictionReason.SecureLockDeviceStrongBiometricOnlyAuth,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
                    DeviceEntryRestrictionReason.TrustAgentDisabled,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
@@ -651,6 +671,105 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
        }

    @Test
    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    fun deviceUnlockStatus_updatesAcrossTwoFactorBouncerUnlock_whenSecureLockDeviceEnabled_fp() =
        testScope.runTest {
            val authenticationFlags by
                collectLastValue(kosmos.deviceEntryBiometricSettingsInteractor.authenticationFlags)
            val deviceEntryRestrictionReason by
                collectLastValue(underTest.deviceEntryRestrictionReason)
            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
            val requiresPrimaryAuthForSecureLockDevice by
                collectLastValue(
                    kosmos.secureLockDeviceInteractor.requiresPrimaryAuthForSecureLockDevice
                )
            val requiresStrongBiometricAuthForSecureLockDevice by
                collectLastValue(
                    kosmos.secureLockDeviceInteractor.requiresStrongBiometricAuthForSecureLockDevice
                )
            val isSecureLockDeviceEnabled by collectLastValue(underTest.isSecureLockDeviceEnabled)

            // Enroll fingerprint, configure PIN as primary auth method
            kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
            authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)

            // Mock secure lock device enabled, both StrongAuthFlags set
            kosmos.fakeSecureLockDeviceRepository.onSecureLockDeviceEnabled()
            kosmos.biometricSettingsRepository.setAuthenticationFlags(
                AuthenticationFlags(
                    userId = primaryUserId,
                    flag =
                        LockPatternUtils.StrongAuthTracker
                            .PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE or
                            LockPatternUtils.StrongAuthTracker
                                .STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE,
                )
            )
            runCurrent()

            // Assert device lock state, device entry restriction reason, authentication
            // requirements, and secure lock device state updated
            assertThat(authenticationFlags!!.isPrimaryAuthRequiredForSecureLockDevice).isTrue()
            assertThat(deviceEntryRestrictionReason)
                .isEqualTo(DeviceEntryRestrictionReason.SecureLockDevicePrimaryAuth)
            assertThat(requiresPrimaryAuthForSecureLockDevice).isTrue()
            assertThat(requiresStrongBiometricAuthForSecureLockDevice).isFalse()
            assertThat(isSecureLockDeviceEnabled).isTrue()
            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
            assertThat(deviceUnlockStatus?.deviceUnlockSource).isNull()

            // Mock primary auth on bouncer
            authenticationRepository.reportAuthenticationAttempt(true)

            // Mock primary auth secure lock device flag cleared
            kosmos.fakeSecureLockDeviceRepository.onSuccessfulPrimaryAuth()
            kosmos.biometricSettingsRepository.setAuthenticationFlags(
                AuthenticationFlags(
                    userId = primaryUserId,
                    flag =
                        LockPatternUtils.StrongAuthTracker
                            .STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE,
                )
            )
            runCurrent()

            // Assert device lock state, device entry restriction reason, authentication
            // requirements, and secure lock device state updated to reflect bouncer auth
            assertThat(authenticationFlags!!.isPrimaryAuthRequiredForSecureLockDevice).isFalse()
            assertThat(deviceEntryRestrictionReason)
                .isEqualTo(DeviceEntryRestrictionReason.SecureLockDeviceStrongBiometricOnlyAuth)
            assertThat(requiresPrimaryAuthForSecureLockDevice).isFalse()
            assertThat(requiresStrongBiometricAuthForSecureLockDevice).isTrue()
            assertThat(isSecureLockDeviceEnabled).isTrue()

            // Assert device is still locked, deviceUnlockSource does not update
            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
            assertThat(deviceUnlockStatus?.deviceUnlockSource).isNull()

            // Mock successful strong fingerprint auth
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )

            // Mock secure lock device auth flags cleared, secure lock device disabled
            kosmos.biometricSettingsRepository.setAuthenticationFlags(
                AuthenticationFlags(userId = primaryUserId, flag = STRONG_AUTH_NOT_REQUIRED)
            )
            kosmos.fakeSecureLockDeviceRepository.onSecureLockDeviceDisabled()
            runCurrent()

            // Assert device is now unlocked, deviceUnlockSource updates to fingerprint
            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
            assertThat(deviceUnlockStatus?.deviceUnlockSource)
                .isEqualTo(DeviceUnlockSource.Fingerprint)

            // Assert secure lock device disabled after successful strong biometric auth
            assertThat(requiresPrimaryAuthForSecureLockDevice).isFalse()
            assertThat(requiresStrongBiometricAuthForSecureLockDevice).isFalse()
            assertThat(isSecureLockDeviceEnabled).isFalse()
        }

    private fun TestScope.unlockDevice() {
        val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)

+56 −21
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.deviceentry.domain.interactor

import android.provider.Settings
import android.security.Flags.secureLockDevice
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.CoreStartable
@@ -39,7 +40,9 @@ import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.scene.domain.SceneFrameworkTableLog
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.securelockdevice.domain.interactor.SecureLockDeviceInteractor
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CancellationException
@@ -76,31 +79,13 @@ constructor(
    fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
    private val powerInteractor: PowerInteractor,
    private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
    secureLockDeviceInteractor: SecureLockDeviceInteractor,
    private val systemPropertiesHelper: SystemPropertiesHelper,
    private val secureSettingsRepository: SecureSettingsRepository,
    private val keyguardInteractor: KeyguardInteractor,
    @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
    deviceEntryBypassInteractor: DeviceEntryBypassInteractor,
) : ExclusiveActivatable() {

    private val deviceUnlockSource =
        merge(
            fingerprintAuthInteractor.fingerprintSuccess.map { DeviceUnlockSource.Fingerprint },
            faceAuthInteractor.isAuthenticated
                .filter { it }
                .map {
                    if (deviceEntryBypassInteractor.isBypassEnabled.value) {
                        DeviceUnlockSource.FaceWithBypass
                    } else {
                        DeviceUnlockSource.FaceWithoutBypass
                    }
                },
            trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent },
            authenticationInteractor.onAuthenticationResult
                .filter { it }
                .map { DeviceUnlockSource.BouncerInput },
        )

    private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
    private val fingerprintEnrolledAndEnabled =
        biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
@@ -125,6 +110,10 @@ constructor(
                    trustInteractor.isTrustAgentCurrentlyAllowed,
                ) { authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged ->
                    when {
                        authFlags.isPrimaryAuthRequiredForSecureLockDevice ->
                            DeviceEntryRestrictionReason.SecureLockDevicePrimaryAuth
                        authFlags.isStrongBiometricAuthRequiredForSecureLockDevice ->
                            DeviceEntryRestrictionReason.SecureLockDeviceStrongBiometricOnlyAuth
                        authFlags.isPrimaryAuthRequiredAfterReboot &&
                            wasRebootedForMainlineUpdate() ->
                            DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
@@ -168,6 +157,50 @@ constructor(
    /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
    val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }

    /**
     * Whether secure lock device mode is enabled, meaning device entry requires two-factor
     * authentication: primary auth on the bouncer, followed by strong biometric-only auth on the
     * bouncer.
     *
     * Returns false when FLAG_SECURE_LOCK_DEVICE is disabled
     */
    val isSecureLockDeviceEnabled: Flow<Boolean> =
        if (secureLockDevice()) {
            secureLockDeviceInteractor.isSecureLockDeviceEnabled
        } else {
            flowOf(false)
        }

    /** Indicates when a device has been unlocked from successful authentication on the bouncer. */
    private val onUnlockFromBouncer =
        authenticationInteractor.onAuthenticationResult
            .filter { it }
            .sampleFilter(isSecureLockDeviceEnabled) {
                /**
                 * When secure lock device is active, the device is not considered unlocked after
                 * successful bouncer auth. Secure Lock Device requires two-factor authentication:
                 * primary auth on the bouncer, followed by strong biometric authentication on the
                 * bouncer, in order to unlock and enter the device.
                 */
                !it
            }

    private val deviceUnlockSource =
        merge(
            fingerprintAuthInteractor.fingerprintSuccess.map { DeviceUnlockSource.Fingerprint },
            faceAuthInteractor.isAuthenticated
                .filter { it }
                .map {
                    if (deviceEntryBypassInteractor.isBypassEnabled.value) {
                        DeviceUnlockSource.FaceWithBypass
                    } else {
                        DeviceUnlockSource.FaceWithoutBypass
                    }
                },
            trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent },
            onUnlockFromBouncer.map { DeviceUnlockSource.BouncerInput },
        )

    /**
     * Whether the device is unlocked or not, along with the information about the authentication
     * method that was used to unlock the device.
@@ -262,8 +295,7 @@ constructor(
                        // as normal.
                        Log.d(
                            TAG,
                            "Not in trusted environment, power-related lock events treated as" +
                                " normal",
                            "Not in trusted environment, power-related lock events treated as normal",
                        )
                        merge(
                            // Device wakefulness events.
@@ -394,6 +426,9 @@ constructor(
        return when (this) {
            DeviceEntryRestrictionReason.UserLockdown -> true
            DeviceEntryRestrictionReason.PolicyLockdown -> true
            // Device locking is handled via the lockNow request from SecureLockDeviceService
            DeviceEntryRestrictionReason.SecureLockDevicePrimaryAuth -> false
            DeviceEntryRestrictionReason.SecureLockDeviceStrongBiometricOnlyAuth -> false

            // Add individual enum value instead of using "else" so new reasons are guaranteed
            // to be added here at compile-time.
+15 −0
Original line number Diff line number Diff line
@@ -116,4 +116,19 @@ enum class DeviceEntryRestrictionReason {
     * Restriction: Only bouncer based device entry is allowed.
     */
    BouncerLockedOut,
    /**
     * Reason: Secure lock device has been enabled.
     *
     * Restriction: Only primary auth on the bouncer is allowed, biometric authentication is
     * disabled.
     */
    SecureLockDevicePrimaryAuth,

    /**
     * Reason: Secure lock device has been enabled, and the user has completed the first-factor
     * bouncer authentication.
     *
     * Restriction: Only strong biometric authentication is allowed.
     */
    SecureLockDeviceStrongBiometricOnlyAuth,
}
+2 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.securelockdevice.domain.interactor.secureLockDeviceInteractor
import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository

val Kosmos.deviceUnlockedInteractor by Fixture {
@@ -38,6 +39,7 @@ val Kosmos.deviceUnlockedInteractor by Fixture {
            fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
            powerInteractor = powerInteractor,
            biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
            secureLockDeviceInteractor = secureLockDeviceInteractor,
            systemPropertiesHelper = fakeSystemPropertiesHelper,
            secureSettingsRepository = userAwareSecureSettingsRepository,
            keyguardInteractor = keyguardInteractor,