diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index ebc18bcd2369a..e33041273fe4f 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -29,6 +29,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRY import static android.content.Context.KEYGUARD_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_ALL; +import static android.os.UserHandle.USER_SYSTEM; import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; @@ -70,6 +71,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.database.ContentObserver; import android.database.sqlite.SQLiteDatabase; import android.hardware.authsecret.IAuthSecret; @@ -218,6 +220,8 @@ public class LockSettingsService extends ILockSettings.Stub { private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_"; private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_"; + private static final int HEADLESS_VENDOR_AUTH_SECRET_LENGTH = 32; + // Order of holding lock: mSeparateChallengeLock -> mSpManager -> this // Do not call into ActivityManager while holding mSpManager lock. private final Object mSeparateChallengeLock = new Object(); @@ -266,6 +270,13 @@ public class LockSettingsService extends ILockSettings.Stub { @VisibleForTesting protected boolean mHasSecureLockScreen; + @VisibleForTesting + protected final Object mHeadlessAuthSecretLock = new Object(); + + @VisibleForTesting + @GuardedBy("mHeadlessAuthSecretLock") + protected byte[] mAuthSecret; + protected IGateKeeperService mGateKeeperService; protected IAuthSecret mAuthSecretService; @@ -562,6 +573,15 @@ public class LockSettingsService extends ILockSettings.Stub { java.security.KeyStore ks) { return new ManagedProfilePasswordCache(ks, getUserManager()); } + + public boolean isHeadlessSystemUserMode() { + return UserManager.isHeadlessSystemUserMode(); + } + + public boolean isMainUserPermanentAdmin() { + return Resources.getSystem() + .getBoolean(com.android.internal.R.bool.config_isMainUserPermanentAdmin); + } } public LockSettingsService(Context context) { @@ -1679,7 +1699,7 @@ public class LockSettingsService extends ILockSettings.Stub { throw new IllegalStateException("password change failed"); } - onSyntheticPasswordKnown(userId, sp); + onSyntheticPasswordUnlocked(userId, sp); setLockCredentialWithSpLocked(credential, sp, userId); sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent); return true; @@ -1991,7 +2011,7 @@ public class LockSettingsService extends ILockSettings.Stub { Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId); return; } - onSyntheticPasswordKnown(userId, result.syntheticPassword); + onSyntheticPasswordUnlocked(userId, result.syntheticPassword); unlockUserKey(userId, result.syntheticPassword); } } @@ -2584,43 +2604,112 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private void onSyntheticPasswordKnown(@UserIdInt int userId, SyntheticPassword sp) { + private void onSyntheticPasswordCreated(@UserIdInt int userId, SyntheticPassword sp) { + onSyntheticPasswordKnown(userId, sp, true); + } + + private void onSyntheticPasswordUnlocked(@UserIdInt int userId, SyntheticPassword sp) { + onSyntheticPasswordKnown(userId, sp, false); + } + + private void onSyntheticPasswordKnown( + @UserIdInt int userId, SyntheticPassword sp, boolean justCreated) { if (mInjector.isGsiRunning()) { Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow"); return; } - mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, sp.getVersion(), - sp.getSyntheticPassword()); - - callToAuthSecretIfNeeded(userId, sp); + mRebootEscrowManager.callToRebootEscrowIfNeeded( + userId, sp.getVersion(), sp.getSyntheticPassword()); + callToAuthSecretIfNeeded(userId, sp, justCreated); } - private void callToAuthSecretIfNeeded(@UserIdInt int userId, SyntheticPassword sp) { - // If the given user is the primary user, pass the auth secret to the HAL. Only the system - // user can be primary. Check for the system user ID before calling getUserInfo(), as other - // users may still be under construction. + /** + * Handles generation, storage, and sending of the vendor auth secret. Here we try to retrieve + * the auth secret to send it to the auth secret HAL, generate a fresh secret if need be, store + * it encrypted on disk so that the given user can unlock it in future, and stash it in memory + * so that when future users are created they can also unlock it. + * + *

Called whenever the SP of a user is available, except in GSI. + */ + private void callToAuthSecretIfNeeded( + @UserIdInt int userId, SyntheticPassword sp, boolean justCreated) { if (mAuthSecretService == null) { + // If there's no IAuthSecret service, we don't need to maintain a auth secret return; } - if (userId == UserHandle.USER_SYSTEM && - mUserManager.getUserInfo(userId).isPrimary()) { - final byte[] secret = sp.deriveVendorAuthSecret(); - try { - mAuthSecretService.setPrimaryUserCredential(secret); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL", e); + // User may be partially created, so use the internal user manager interface + final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal(); + final UserInfo userInfo = userManagerInternal.getUserInfo(userId); + if (userInfo == null) { + // User may be partially deleted, skip this. + return; + } + final byte[] authSecret; + if (!mInjector.isHeadlessSystemUserMode()) { + // On non-headless systems, the auth secret is derived from user 0's + // SP, and only user 0 passes it to the HAL. + if (userId != USER_SYSTEM) { + return; } + authSecret = sp.deriveVendorAuthSecret(); + } else if (!mInjector.isMainUserPermanentAdmin() || !userInfo.isFull()) { + // Only full users can receive or pass on the auth secret. + // If there is no main permanent admin user, we don't try to create or send + // an auth secret, since there may sometimes be no full users. + return; + } else if (justCreated) { + if (userInfo.isMain()) { + // The first user is just being created, so we create a new auth secret + // at the same time. + Slog.i(TAG, "Generating new vendor auth secret and storing for user: " + userId); + authSecret = SecureRandomUtils.randomBytes(HEADLESS_VENDOR_AUTH_SECRET_LENGTH); + // Store it in memory, for when new users are created. + synchronized (mHeadlessAuthSecretLock) { + mAuthSecret = authSecret; + } + } else { + // A new user is being created. Another user should already have logged in at + // this point, and therefore the auth secret should be stored in memory. + synchronized (mHeadlessAuthSecretLock) { + authSecret = mAuthSecret; + } + if (authSecret == null) { + Slog.e(TAG, "Creating non-main user " + userId + + " but vendor auth secret is not in memory"); + return; + } + } + // Store the auth secret encrypted using the user's SP (which was just created). + mSpManager.writeVendorAuthSecret(authSecret, sp, userId); + } else { + // The user already exists, so the auth secret should be stored encrypted + // with that user's SP. + authSecret = mSpManager.readVendorAuthSecret(sp, userId); + if (authSecret == null) { + Slog.e(TAG, "Unable to read vendor auth secret for user: " + userId); + return; + } + // Store it in memory, for when new users are created. + synchronized (mHeadlessAuthSecretLock) { + mAuthSecret = authSecret; + } + } + Slog.i(TAG, "Sending vendor auth secret to IAuthSecret HAL as user: " + userId); + try { + mAuthSecretService.setPrimaryUserCredential(authSecret); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to send vendor auth secret to IAuthSecret HAL", e); } } /** * Creates the synthetic password (SP) for the given user, protects it with an empty LSKF, and * protects the user's CE key with a key derived from the SP. - *

- * This is called just once in the lifetime of the user: at user creation time (possibly delayed - * until the time when Weaver is guaranteed to be available), or when upgrading from Android 13 - * or earlier where users with no LSKF didn't necessarily have an SP. + * + *

This is called just once in the lifetime of the user: at user creation time (possibly + * delayed until the time when Weaver is guaranteed to be available), or when upgrading from + * Android 13 or earlier where users with no LSKF didn't necessarily have an SP. */ @VisibleForTesting SyntheticPassword initializeSyntheticPassword(int userId) { @@ -2635,7 +2724,7 @@ public class LockSettingsService extends ILockSettings.Stub { LockscreenCredential.createNone(), sp, userId); setCurrentLskfBasedProtectorId(protectorId, userId); setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey()); - onSyntheticPasswordKnown(userId, sp); + onSyntheticPasswordCreated(userId, sp); return sp; } } @@ -2702,7 +2791,7 @@ public class LockSettingsService extends ILockSettings.Stub { } mStrongAuth.reportSuccessfulStrongAuthUnlock(userId); - onSyntheticPasswordKnown(userId, sp); + onSyntheticPasswordUnlocked(userId, sp); } private void setDeviceUnlockedForUser(int userId) { @@ -2990,7 +3079,7 @@ public class LockSettingsService extends ILockSettings.Stub { + "verification."); return false; } - onSyntheticPasswordKnown(userId, result.syntheticPassword); + onSyntheticPasswordUnlocked(userId, result.syntheticPassword); setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId); return true; } diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index cd972dcd461cb..7f8b2678395b9 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -21,6 +21,7 @@ import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChang import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.admin.PasswordMetrics; import android.content.Context; import android.content.pm.UserInfo; @@ -93,6 +94,9 @@ import java.util.Set; * while the LSKF is nonempty. * SP_E0_NAME, SP_P1_NAME: Information needed to create and use escrow token-based protectors. * Deleted when escrow token support is disabled for the user. + * VENDOR_AUTH_SECRET_NAME: A copy of the secret passed using the IAuthSecret interface, + * encrypted using a secret derived from the SP using + * PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY. * * For each protector, stored under the corresponding protector ID: * SP_BLOB_NAME: The encrypted SP secret (the SP itself or the P0 value). Always exists. @@ -120,6 +124,7 @@ class SyntheticPasswordManager { private static final String PASSWORD_DATA_NAME = "pwd"; private static final String WEAVER_SLOT_NAME = "weaver"; private static final String PASSWORD_METRICS_NAME = "metrics"; + private static final String VENDOR_AUTH_SECRET_NAME = "vendor_auth_secret"; // used for files associated with the SP itself, not with a particular protector public static final long NULL_PROTECTOR_ID = 0L; @@ -158,6 +163,8 @@ class SyntheticPasswordManager { private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes(); private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes(); private static final byte[] PERSONALIZATION_AUTHSECRET_KEY = "authsecret-hal".getBytes(); + private static final byte[] PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY = + "vendor-authsecret-encryption-key".getBytes(); private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes(); private static final byte[] PERSONALIZATION_PASSWORD_HASH = "pw-hash".getBytes(); private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes(); @@ -249,6 +256,10 @@ class SyntheticPasswordManager { return deriveSubkey(PERSONALIZATION_PASSWORD_METRICS); } + public byte[] deriveVendorAuthSecretEncryptionKey() { + return deriveSubkey(PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY); + } + /** * Assigns escrow data to this synthetic password. This is a prerequisite to call * {@link SyntheticPassword#recreateFromEscrow}. @@ -1737,4 +1748,25 @@ class SyntheticPasswordManager { mListeners.finishBroadcast(); } } + + public void writeVendorAuthSecret( + @NonNull final byte[] vendorAuthSecret, + @NonNull final SyntheticPassword sp, + @UserIdInt final int userId) { + final byte[] encrypted = + SyntheticPasswordCrypto.encrypt( + sp.deriveVendorAuthSecretEncryptionKey(), new byte[0], vendorAuthSecret); + saveState(VENDOR_AUTH_SECRET_NAME, encrypted, NULL_PROTECTOR_ID, userId); + syncState(userId); + } + + public @Nullable byte[] readVendorAuthSecret( + @NonNull final SyntheticPassword sp, @UserIdInt final int userId) { + final byte[] encrypted = loadState(VENDOR_AUTH_SECRET_NAME, NULL_PROTECTOR_ID, userId); + if (encrypted == null) { + return null; + } + return SyntheticPasswordCrypto.decrypt( + sp.deriveVendorAuthSecretEncryptionKey(), new byte[0], encrypted); + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index 5ca695bcb65a0..c9612cd80ac62 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -49,8 +49,8 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; -import android.provider.Settings; import android.provider.DeviceConfig; +import android.provider.Settings; import android.security.KeyStore; import androidx.test.InstrumentationRegistry; @@ -83,16 +83,15 @@ public abstract class BaseLockSettingsServiceTests { protected static final int MANAGED_PROFILE_USER_ID = 12; protected static final int TURNED_OFF_PROFILE_USER_ID = 17; protected static final int SECONDARY_USER_ID = 20; + protected static final int TERTIARY_USER_ID = 21; - private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null, - UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY - | UserInfo.FLAG_MAIN); - private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null, - UserInfo.FLAG_INITIALIZED); + protected UserInfo mPrimaryUserInfo; + protected UserInfo mSecondaryUserInfo; + protected UserInfo mTertiaryUserInfo; private ArrayList mPrimaryUserProfiles = new ArrayList<>(); - LockSettingsService mService; + LockSettingsServiceTestable mService; LockSettingsInternal mLocalService; MockLockSettingsContext mContext; @@ -117,6 +116,7 @@ public abstract class BaseLockSettingsServiceTests { FingerprintManager mFingerprintManager; FaceManager mFaceManager; PackageManager mPackageManager; + LockSettingsServiceTestable.MockInjector mInjector; @Rule public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule(); @@ -162,22 +162,61 @@ public abstract class BaseLockSettingsServiceTests { mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService, mUserManager, mPasswordSlotManager); mAuthSecretService = mock(IAuthSecret.class); - mService = new LockSettingsServiceTestable(mContext, mStorage, - mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager, - mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager, - mUserManagerInternal, mDeviceStateCache); + mInjector = + new LockSettingsServiceTestable.MockInjector( + mContext, + mStorage, + mKeyStore, + mActivityManager, + setUpStorageManagerMock(), + mSpManager, + mGsiService, + mRecoverableKeyStoreManager, + mUserManagerInternal, + mDeviceStateCache); + mService = + new LockSettingsServiceTestable(mInjector, mGateKeeperService, mAuthSecretService); mService.mHasSecureLockScreen = true; - when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO); - mPrimaryUserProfiles.add(PRIMARY_USER_INFO); + mPrimaryUserInfo = + new UserInfo( + PRIMARY_USER_ID, + null, + null, + UserInfo.FLAG_INITIALIZED + | UserInfo.FLAG_ADMIN + | UserInfo.FLAG_PRIMARY + | UserInfo.FLAG_MAIN + | UserInfo.FLAG_FULL); + mSecondaryUserInfo = + new UserInfo( + SECONDARY_USER_ID, + null, + null, + UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_FULL); + mTertiaryUserInfo = + new UserInfo( + TERTIARY_USER_ID, + null, + null, + UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_FULL); + + when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserInfo); + when(mUserManagerInternal.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserInfo); + mPrimaryUserProfiles.add(mPrimaryUserInfo); installChildProfile(MANAGED_PROFILE_USER_ID); installAndTurnOffChildProfile(TURNED_OFF_PROFILE_USER_ID); for (UserInfo profile : mPrimaryUserProfiles) { when(mUserManager.getProfiles(eq(profile.id))).thenReturn(mPrimaryUserProfiles); } - when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO); + when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(mSecondaryUserInfo); + when(mUserManagerInternal.getUserInfo(eq(SECONDARY_USER_ID))) + .thenReturn(mSecondaryUserInfo); + when(mUserManager.getUserInfo(eq(TERTIARY_USER_ID))).thenReturn(mTertiaryUserInfo); + when(mUserManagerInternal.getUserInfo(eq(TERTIARY_USER_ID))).thenReturn(mTertiaryUserInfo); final ArrayList allUsers = new ArrayList<>(mPrimaryUserProfiles); - allUsers.add(SECONDARY_USER_INFO); + allUsers.add(mSecondaryUserInfo); + allUsers.add(mTertiaryUserInfo); when(mUserManager.getUsers()).thenReturn(allUsers); when(mActivityManager.unlockUser2(anyInt(), any())).thenAnswer( @@ -227,9 +266,10 @@ public abstract class BaseLockSettingsServiceTests { userInfo.profileGroupId = PRIMARY_USER_ID; mPrimaryUserProfiles.add(userInfo); when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo); - when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO); + when(mUserManager.getProfileParent(eq(profileId))).thenReturn(mPrimaryUserInfo); when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true); when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true); + when(mUserManagerInternal.getUserInfo(eq(profileId))).thenReturn(userInfo); // TODO(b/258213147): Remove when(mUserManagerInternal.isUserManaged(eq(profileId))).thenReturn(true); when(mDeviceStateCache.isUserOrganizationManaged(eq(profileId))) diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java index f0f0632d63327..9686c385e91e4 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java @@ -30,6 +30,7 @@ import android.os.RemoteException; import android.os.storage.IStorageManager; import android.security.KeyStore; import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.service.gatekeeper.IGateKeeperService; import com.android.internal.widget.LockscreenCredential; import com.android.server.ServiceThread; @@ -40,7 +41,7 @@ import java.io.FileNotFoundException; public class LockSettingsServiceTestable extends LockSettingsService { - private static class MockInjector extends LockSettingsService.Injector { + public static class MockInjector extends LockSettingsService.Injector { private LockSettingsStorage mLockSettingsStorage; private KeyStore mKeyStore; @@ -52,6 +53,9 @@ public class LockSettingsServiceTestable extends LockSettingsService { private UserManagerInternal mUserManagerInternal; private DeviceStateCache mDeviceStateCache; + public boolean mIsHeadlessSystemUserMode = false; + public boolean mIsMainUserPermanentAdmin = false; + public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore, IActivityManager activityManager, IStorageManager storageManager, SyntheticPasswordManager spManager, @@ -140,19 +144,22 @@ public class LockSettingsServiceTestable extends LockSettingsService { return mock(ManagedProfilePasswordCache.class); } + @Override + public boolean isHeadlessSystemUserMode() { + return mIsHeadlessSystemUserMode; + } + + @Override + public boolean isMainUserPermanentAdmin() { + return mIsMainUserPermanentAdmin; + } } - public MockInjector mInjector; - - protected LockSettingsServiceTestable(Context context, - LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore, - IStorageManager storageManager, IActivityManager mActivityManager, - SyntheticPasswordManager spManager, IAuthSecret authSecretService, - FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager, - UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) { - super(new MockInjector(context, storage, keystore, mActivityManager, - storageManager, spManager, gsiService, recoverableKeyStoreManager, - userManagerInternal, deviceStateCache)); + protected LockSettingsServiceTestable( + LockSettingsService.Injector injector, + IGateKeeperService gatekeeper, + IAuthSecret authSecretService) { + super(injector); mGateKeeperService = gatekeeper; mAuthSecretService = authSecretService; } @@ -199,4 +206,10 @@ public class LockSettingsServiceTestable extends LockSettingsService { UserInfo userInfo = mUserManager.getUserInfo(userId); return userInfo.isCloneProfile() || userInfo.isManagedProfile(); } + + void clearAuthSecret() { + synchronized (mHeadlessAuthSecretLock) { + mAuthSecret = null; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index 57593cf660f2b..62d8a76452366 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -16,6 +16,10 @@ package com.android.server.locksettings; +import static android.content.pm.UserInfo.FLAG_FULL; +import static android.content.pm.UserInfo.FLAG_MAIN; +import static android.content.pm.UserInfo.FLAG_PRIMARY; + import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN; @@ -27,9 +31,9 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -247,6 +251,15 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { @Test public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException { + initSpAndSetCredential(PRIMARY_USER_ID, newPassword(null)); + reset(mAuthSecretService); + mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID); + verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class)); + } + + @Test + public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecretIfPasswordIsCleared() + throws RemoteException { LockscreenCredential password = newPassword("password"); initSpAndSetCredential(PRIMARY_USER_ID, password); mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID); @@ -256,6 +269,56 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class)); } + private void setupHeadlessTest() { + mInjector.mIsHeadlessSystemUserMode = true; + mInjector.mIsMainUserPermanentAdmin = true; + mPrimaryUserInfo.flags &= ~(FLAG_FULL | FLAG_PRIMARY); + mSecondaryUserInfo.flags |= FLAG_MAIN; + mService.initializeSyntheticPassword(PRIMARY_USER_ID); + mService.initializeSyntheticPassword(SECONDARY_USER_ID); + mService.initializeSyntheticPassword(TERTIARY_USER_ID); + reset(mAuthSecretService); + } + + @Test + public void testHeadlessSystemUserDoesNotPassAuthSecret() throws RemoteException { + setupHeadlessTest(); + mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID); + verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class)); + } + + @Test + public void testHeadlessSecondaryUserPassesAuthSecret() throws RemoteException { + setupHeadlessTest(); + mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID); + verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class)); + } + + @Test + public void testHeadlessTertiaryUserPassesSameAuthSecret() throws RemoteException { + setupHeadlessTest(); + mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID); + var captor = ArgumentCaptor.forClass(byte[].class); + verify(mAuthSecretService).setPrimaryUserCredential(captor.capture()); + var value = captor.getValue(); + reset(mAuthSecretService); + mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID); + verify(mAuthSecretService).setPrimaryUserCredential(eq(value)); + } + + @Test + public void testHeadlessTertiaryUserPassesSameAuthSecretAfterReset() throws RemoteException { + setupHeadlessTest(); + mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID); + var captor = ArgumentCaptor.forClass(byte[].class); + verify(mAuthSecretService).setPrimaryUserCredential(captor.capture()); + var value = captor.getValue(); + mService.clearAuthSecret(); + reset(mAuthSecretService); + mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID); + verify(mAuthSecretService).setPrimaryUserCredential(eq(value)); + } + @Test public void testTokenBasedResetPassword() throws RemoteException { LockscreenCredential password = newPassword("password"); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java index 966c0479d69d6..50f3a88cc3a21 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java @@ -8,6 +8,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.locksettings.LockSettingsStorage.PersistentData; + import com.google.android.collect.Sets; import org.junit.Before; @@ -29,7 +30,7 @@ public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests { // sequentially, starting at slot 0. @Test public void testFrpWeaverSlotNotReused() { - final int userId = 10; + final int userId = SECONDARY_USER_ID; final int frpWeaverSlot = 0; setDeviceProvisioned(false); @@ -45,7 +46,7 @@ public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests { // it's here as a control for testFrpWeaverSlotNotReused(). @Test public void testFrpWeaverSlotReused() { - final int userId = 10; + final int userId = SECONDARY_USER_ID; final int frpWeaverSlot = 0; setDeviceProvisioned(true);