diff options
5 files changed, 355 insertions, 29 deletions
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 3e134b266a88..1fc7a02bb01b 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -94,6 +94,7 @@ import android.service.gatekeeper.GateKeeperResponse; import android.service.gatekeeper.IGateKeeperService; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -141,6 +142,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -324,7 +326,7 @@ public class LockSettingsService extends ILockSettings.Stub { Arrays.fill(newPasswordChars, '\u0000'); final int quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; setLockCredentialInternal(newPassword, CREDENTIAL_TYPE_PASSWORD, managedUserPassword, - quality, managedUserId, false); + quality, managedUserId, false, /* isLockTiedToParent= */ true); // We store a private credential for the managed user that's unlocked by the primary // account holder's credential. As such, the user will never be prompted to enter this // password directly, so we always store a password. @@ -1303,13 +1305,13 @@ public class LockSettingsService extends ILockSettings.Stub { setLockCredentialInternal(null, CREDENTIAL_TYPE_NONE, profilePasswordMap.get(managedUserId), DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId, - false); + false, /* isLockTiedToParent= */ true); } else { Slog.wtf(TAG, "clear tied profile challenges, but no password supplied."); // Supplying null here would lead to untrusted credential change setLockCredentialInternal(null, CREDENTIAL_TYPE_NONE, null, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId, - true); + true, /* isLockTiedToParent= */ true); } mStorage.removeChildProfileLock(managedUserId); removeKeystoreProfileKey(managedUserId); @@ -1328,6 +1330,67 @@ public class LockSettingsService extends ILockSettings.Stub { && mLockPatternUtils.isSeparateProfileChallengeEnabled(userId); } + /** + * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} during an + * unlock operation. + */ + private void sendCredentialsOnUnlockIfRequired( + int credentialType, @NonNull byte[] credential, int userId) { + // Don't send credentials during the factory reset protection flow. + if (userId == USER_FRP) { + return; + } + + // A profile with a unified lock screen stores a randomly generated credential, so skip it. + // Its parent will send credentials for the profile, as it stores the unified lock + // credential. + if (isManagedProfileWithUnifiedLock(userId)) { + return; + } + + // Send credentials for the user and any child profiles that share its lock screen. + for (int profileId : getProfilesWithSameLockScreen(userId)) { + mRecoverableKeyStoreManager.lockScreenSecretAvailable( + credentialType, credential, profileId); + } + } + + /** + * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} when its + * credentials are set/changed. + */ + private void sendCredentialsOnChangeIfRequired( + int credentialType, byte[] credential, int userId, boolean isLockTiedToParent) { + // A profile whose lock screen is being tied to its parent's will either have a randomly + // generated credential (creation) or null (removal). We rely on the parent to send its + // credentials for the profile in both cases as it stores the unified lock credential. + if (isLockTiedToParent) { + return; + } + + // Send credentials for the user and any child profiles that share its lock screen. + for (int profileId : getProfilesWithSameLockScreen(userId)) { + mRecoverableKeyStoreManager.lockScreenSecretChanged( + credentialType, credential, profileId); + } + } + + /** + * Returns all profiles of {@code userId}, including itself, that have the same lock screen + * challenge. + */ + private Set<Integer> getProfilesWithSameLockScreen(int userId) { + Set<Integer> profiles = new ArraySet<>(); + for (UserInfo profile : mUserManager.getProfiles(userId)) { + if (profile.id == userId + || (profile.profileGroupId == userId + && isManagedProfileWithUnifiedLock(profile.id))) { + profiles.add(profile.id); + } + } + return profiles; + } + // This method should be called by LockPatternUtil only, all internal methods in this class // should call setLockCredentialInternal. @Override @@ -1342,16 +1405,20 @@ public class LockSettingsService extends ILockSettings.Stub { checkWritePermission(userId); synchronized (mSeparateChallengeLock) { setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId, - allowUntrustedChange); + allowUntrustedChange, /* isLockTiedToParent= */ false); setSeparateProfileChallengeEnabledLocked(userId, true, null); notifyPasswordChanged(userId); } notifySeparateProfileChallengeChanged(userId); } + /** + * @param isLockTiedToParent is {@code true} if {@code userId} is a profile and its new + * credentials are being tied to its parent's credentials. + */ private void setLockCredentialInternal(byte[] credential, @CredentialType int credentialType, - byte[] savedCredential, int requestedQuality, int userId, - boolean allowUntrustedChange) throws RemoteException { + byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange, + boolean isLockTiedToParent) throws RemoteException { // Normalize savedCredential and credential such that empty string is always represented // as null. if (savedCredential == null || savedCredential.length == 0) { @@ -1363,7 +1430,7 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSpManager) { if (isSyntheticPasswordBasedCredentialLocked(userId)) { spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential, - requestedQuality, userId, allowUntrustedChange); + requestedQuality, userId, allowUntrustedChange, isLockTiedToParent); return; } } @@ -1379,7 +1446,8 @@ public class LockSettingsService extends ILockSettings.Stub { fixateNewestUserKeyAuth(userId); synchronizeUnifiedWorkChallengeForProfiles(userId, null); notifyActivePasswordMetricsAvailable(CREDENTIAL_TYPE_NONE, null, userId); - mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId); + sendCredentialsOnChangeIfRequired( + credentialType, credential, userId, isLockTiedToParent); return; } if (credential == null) { @@ -1414,7 +1482,7 @@ public class LockSettingsService extends ILockSettings.Stub { initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential, currentHandle.type, requestedQuality, userId); spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential, - requestedQuality, userId, allowUntrustedChange); + requestedQuality, userId, allowUntrustedChange, isLockTiedToParent); return; } } @@ -1432,8 +1500,8 @@ public class LockSettingsService extends ILockSettings.Stub { // Refresh the auth token doVerifyCredential(credential, credentialType, true, 0, userId, null /* progressCallback */); synchronizeUnifiedWorkChallengeForProfiles(userId, null); - mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, - userId); + sendCredentialsOnChangeIfRequired( + credentialType, credential, userId, isLockTiedToParent); } else { throw new RemoteException("Failed to enroll " + (credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern")); @@ -1674,8 +1742,7 @@ public class LockSettingsService extends ILockSettings.Stub { // The user employs synthetic password based credential. if (response != null) { if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { - mRecoverableKeyStoreManager.lockScreenSecretAvailable(credentialType, credential, - userId); + sendCredentialsOnUnlockIfRequired(credentialType, credential, userId); } return response; } @@ -1709,7 +1776,8 @@ public class LockSettingsService extends ILockSettings.Stub { mStrongAuth.reportSuccessfulStrongAuthUnlock(userId); if (shouldReEnrollBaseZero) { setLockCredentialInternal(credential, storedHash.type, credentialToVerify, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId, false); + DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId, false, + /* isLockTiedToParent= */ false); } } @@ -1800,12 +1868,12 @@ public class LockSettingsService extends ILockSettings.Stub { storedHash.type == CREDENTIAL_TYPE_PATTERN ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC - /* TODO(roosa): keep the same password quality */, userId, false); + /* TODO(roosa): keep the same password quality */, + userId, false, /* isLockTiedToParent= */ false); if (!hasChallenge) { notifyActivePasswordMetricsAvailable(storedHash.type, credential, userId); // Use credentials to create recoverable keystore snapshot. - mRecoverableKeyStoreManager.lockScreenSecretAvailable( - storedHash.type, credential, userId); + sendCredentialsOnUnlockIfRequired(storedHash.type, credential, userId); return VerifyCredentialResponse.OK; } // Fall through to get the auth token. Technically this should never happen, @@ -1845,7 +1913,7 @@ public class LockSettingsService extends ILockSettings.Stub { /* TODO(roosa): keep the same password quality */; if (shouldReEnroll) { setLockCredentialInternal(credential, storedHash.type, credential, - reEnrollQuality, userId, false); + reEnrollQuality, userId, false, /* isLockTiedToParent= */ false); } else { // Now that we've cleared of all required GK migration, let's do the final // migration to synthetic password. @@ -1859,8 +1927,7 @@ public class LockSettingsService extends ILockSettings.Stub { } } // Use credentials to create recoverable keystore snapshot. - mRecoverableKeyStoreManager.lockScreenSecretAvailable(storedHash.type, credential, - userId); + sendCredentialsOnUnlockIfRequired(storedHash.type, credential, userId); } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { if (response.getTimeout() > 0) { @@ -2549,7 +2616,7 @@ public class LockSettingsService extends ILockSettings.Stub { @GuardedBy("mSpManager") private void spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType, byte[] savedCredential, int requestedQuality, int userId, - boolean allowUntrustedChange) throws RemoteException { + boolean allowUntrustedChange, boolean isLockTiedToParent) throws RemoteException { if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId); if (isManagedProfileWithUnifiedLock(userId)) { // get credential from keystore when managed profile has unified lock @@ -2615,7 +2682,7 @@ public class LockSettingsService extends ILockSettings.Stub { // requestedQuality, userId) instead if we still allow untrusted reset that changes // synthetic password. That would invalidate existing escrow tokens though. } - mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId); + sendCredentialsOnChangeIfRequired(credentialType, credential, userId, isLockTiedToParent); } /** 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 2fbeebdb4937..09e20e04835b 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -46,6 +46,7 @@ import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; import com.android.server.LocalServices; +import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; import com.android.server.wm.WindowManagerInternal; import org.mockito.invocation.InvocationOnMock; @@ -89,6 +90,7 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase { WindowManagerInternal mMockWindowManager; FakeGsiService mGsiService; PasswordSlotManagerTestable mPasswordSlotManager; + RecoverableKeyStoreManager mRecoverableKeyStoreManager; protected boolean mHasSecureLockScreen; @Override @@ -105,6 +107,7 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase { mMockWindowManager = mock(WindowManagerInternal.class); mGsiService = new FakeGsiService(); mPasswordSlotManager = new PasswordSlotManagerTestable(); + mRecoverableKeyStoreManager = mock(RecoverableKeyStoreManager.class); LocalServices.removeServiceForTest(LockSettingsInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); @@ -141,12 +144,14 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase { mAuthSecretService = mock(IAuthSecret.class); mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage, mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager, - mSpManager, mAuthSecretService, mGsiService); + mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager); when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO); mPrimaryUserProfiles.add(PRIMARY_USER_INFO); installChildProfile(MANAGED_PROFILE_USER_ID); installAndTurnOffChildProfile(TURNED_OFF_PROFILE_USER_ID); - when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserProfiles); + for (UserInfo profile : mPrimaryUserProfiles) { + when(mUserManager.getProfiles(eq(profile.id))).thenReturn(mPrimaryUserProfiles); + } when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO); final ArrayList<UserInfo> allUsers = new ArrayList<>(mPrimaryUserProfiles); @@ -173,6 +178,7 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase { private UserInfo installChildProfile(int profileId) { final UserInfo userInfo = new UserInfo( profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE); + userInfo.profileGroupId = PRIMARY_USER_ID; mPrimaryUserProfiles.add(userInfo); when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo); when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO); 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 f4632db3cb6d..10fb3ba938d4 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.security.KeyStore; import android.security.keystore.KeyPermanentlyInvalidatedException; import com.android.internal.widget.LockPatternUtils; +import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; import java.io.FileNotFoundException; @@ -45,11 +46,13 @@ public class LockSettingsServiceTestable extends LockSettingsService { private SyntheticPasswordManager mSpManager; private IAuthSecret mAuthSecretService; private FakeGsiService mGsiService; + private RecoverableKeyStoreManager mRecoverableKeyStoreManager; public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore, IActivityManager activityManager, LockPatternUtils lockPatternUtils, IStorageManager storageManager, SyntheticPasswordManager spManager, - IAuthSecret authSecretService, FakeGsiService gsiService) { + IAuthSecret authSecretService, FakeGsiService gsiService, + RecoverableKeyStoreManager recoverableKeyStoreManager) { super(context); mLockSettingsStorage = storage; mKeyStore = keyStore; @@ -58,6 +61,7 @@ public class LockSettingsServiceTestable extends LockSettingsService { mStorageManager = storageManager; mSpManager = spManager; mGsiService = gsiService; + mRecoverableKeyStoreManager = recoverableKeyStoreManager; } @Override @@ -119,15 +123,21 @@ public class LockSettingsServiceTestable extends LockSettingsService { public boolean isGsiRunning() { return mGsiService.isGsiRunning(); } + + @Override + public RecoverableKeyStoreManager getRecoverableKeyStoreManager(KeyStore keyStore) { + return mRecoverableKeyStoreManager; + } } protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils, LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore, IStorageManager storageManager, IActivityManager mActivityManager, SyntheticPasswordManager spManager, IAuthSecret authSecretService, - FakeGsiService gsiService) { + FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager) { super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils, - storageManager, spManager, authSecretService, gsiService)); + storageManager, spManager, authSecretService, gsiService, + recoverableKeyStoreManager)); mGateKeeperService = gatekeeper; mAuthSecretService = authSecretService; } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 7ebc7454d995..67d6eda1b3ee 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -25,6 +25,13 @@ 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_PATTERN; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.service.gatekeeper.GateKeeperResponse; @@ -211,6 +218,222 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); } + public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception { + final byte[] password = "password".getBytes(); + + mService.setLockCredential( + password, + CREDENTIAL_TYPE_PASSWORD, + null, + PASSWORD_QUALITY_ALPHABETIC, + PRIMARY_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, password, PRIMARY_USER_ID); + } + + public void testSetLockCredential_forProfileWithSeparateChallenge_sendsCredentials() + throws Exception { + final byte[] pattern = "12345".getBytes(); + + mService.setLockCredential( + pattern, + CREDENTIAL_TYPE_PATTERN, + null, + PASSWORD_QUALITY_SOMETHING, + MANAGED_PROFILE_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID); + } + + public void testSetLockCredential_forProfileWithSeparateChallenge_updatesCredentials() + throws Exception { + final String oldCredential = "12345"; + final byte[] newCredential = "newPassword".getBytes(); + initializeStorageWithCredential( + MANAGED_PROFILE_USER_ID, + oldCredential, + CREDENTIAL_TYPE_PATTERN, + PASSWORD_QUALITY_SOMETHING); + + mService.setLockCredential( + newCredential, + CREDENTIAL_TYPE_PASSWORD, + oldCredential.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, + MANAGED_PROFILE_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged( + CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID); + } + + public void testSetLockCredential_forProfileWithUnifiedChallenge_doesNotSendRandomCredential() + throws Exception { + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + + mService.setLockCredential( + "12345".getBytes(), + CREDENTIAL_TYPE_PATTERN, + null, + PASSWORD_QUALITY_SOMETHING, + PRIMARY_USER_ID, + false); + + verify(mRecoverableKeyStoreManager, never()) + .lockScreenSecretChanged( + eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID)); + } + + public void + testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_updatesBothCredentials() + throws Exception { + final String oldCredential = "oldPassword"; + final byte[] newCredential = "newPassword".getBytes(); + initializeStorageWithCredential( + PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + + mService.setLockCredential( + newCredential, + CREDENTIAL_TYPE_PASSWORD, + oldCredential.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, + PRIMARY_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential, PRIMARY_USER_ID); + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged( + CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID); + } + + public void + testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials() + throws Exception { + final String oldCredential = "oldPassword"; + initializeStorageWithCredential( + PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + + mService.setLockCredential( + null, + CREDENTIAL_TYPE_NONE, + oldCredential.getBytes(), + PASSWORD_QUALITY_UNSPECIFIED, + PRIMARY_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, PRIMARY_USER_ID); + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, MANAGED_PROFILE_USER_ID); + } + + public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials() + throws Exception { + final String parentPassword = "parentPassword"; + final byte[] profilePassword = "profilePassword".getBytes(); + initializeStorageWithCredential( + PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + + mService.setLockCredential( + profilePassword, + CREDENTIAL_TYPE_PASSWORD, + null, + PASSWORD_QUALITY_ALPHABETIC, + MANAGED_PROFILE_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged( + CREDENTIAL_TYPE_PASSWORD, profilePassword, MANAGED_PROFILE_USER_ID); + } + + public void + testSetLockCredential_forSeparateToUnifiedChallengeProfile_doesNotSendRandomCredential() + throws Exception { + final String parentPassword = "parentPassword"; + final String profilePassword = "12345"; + initializeStorageWithCredential( + PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234); + // Create and verify separate profile credentials. + testCreateCredential( + MANAGED_PROFILE_USER_ID, + profilePassword, + CREDENTIAL_TYPE_PATTERN, + PASSWORD_QUALITY_SOMETHING); + + mService.setSeparateProfileChallengeEnabled( + MANAGED_PROFILE_USER_ID, false, profilePassword.getBytes()); + + // Called once for setting the initial separate profile credentials and not again during + // unification. + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(anyInt(), any(), eq(MANAGED_PROFILE_USER_ID)); + } + + public void testVerifyCredential_forPrimaryUser_sendsCredentials() throws Exception { + final String password = "password"; + initializeStorageWithCredential(PRIMARY_USER_ID, password, CREDENTIAL_TYPE_PASSWORD, 1234); + reset(mRecoverableKeyStoreManager); + + mService.verifyCredential( + password.getBytes(), CREDENTIAL_TYPE_PASSWORD, 1, PRIMARY_USER_ID); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretAvailable( + CREDENTIAL_TYPE_PASSWORD, password.getBytes(), PRIMARY_USER_ID); + } + + public void testVerifyCredential_forProfileWithSeparateChallenge_sendsCredentials() + throws Exception { + final byte[] pattern = "12345".getBytes(); + mService.setLockCredential( + pattern, + CREDENTIAL_TYPE_PATTERN, + null, + PASSWORD_QUALITY_SOMETHING, + MANAGED_PROFILE_USER_ID, + false); + reset(mRecoverableKeyStoreManager); + + mService.verifyCredential(pattern, CREDENTIAL_TYPE_PATTERN, 1, MANAGED_PROFILE_USER_ID); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretAvailable( + CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID); + } + + public void + testVerifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth() + throws Exception { + final String pattern = "12345"; + initializeStorageWithCredential(PRIMARY_USER_ID, pattern, CREDENTIAL_TYPE_PATTERN, 1234); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + reset(mRecoverableKeyStoreManager); + + mService.verifyCredential(pattern.getBytes(), CREDENTIAL_TYPE_PATTERN, 1, PRIMARY_USER_ID); + + // Parent sends its credentials for both the parent and profile. + verify(mRecoverableKeyStoreManager) + .lockScreenSecretAvailable( + CREDENTIAL_TYPE_PATTERN, pattern.getBytes(), PRIMARY_USER_ID); + verify(mRecoverableKeyStoreManager) + .lockScreenSecretAvailable( + CREDENTIAL_TYPE_PATTERN, pattern.getBytes(), MANAGED_PROFILE_USER_ID); + // Profile doesn't send its own random credentials. + verify(mRecoverableKeyStoreManager, never()) + .lockScreenSecretAvailable( + eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID)); + } + private void testCreateCredential(int userId, String credential, int type, int quality) throws RemoteException { mService.setLockCredential(credential.getBytes(), type, null, quality, diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 5bab65c8b642..68900175cc8f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -58,6 +58,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.widget.LockPatternUtils; import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; @@ -83,7 +84,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Map; import java.util.Random; -import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -156,6 +157,7 @@ public class RecoverableKeyStoreManagerTest { @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private ApplicationKeyStorage mApplicationKeyStorage; @Mock private CleanupManager mCleanupManager; + @Mock private ExecutorService mExecutorService; @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; @@ -188,7 +190,7 @@ public class RecoverableKeyStoreManagerTest { mMockContext, mRecoverableKeyStoreDb, mRecoverySessionStorage, - Executors.newSingleThreadExecutor(), + mExecutorService, mRecoverySnapshotStorage, mMockListenersStorage, mPlatformKeyManager, @@ -1246,6 +1248,24 @@ public class RecoverableKeyStoreManagerTest { } } + @Test + public void lockScreenSecretAvailable_syncsKeysForUser() throws Exception { + mRecoverableKeyStoreManager.lockScreenSecretAvailable( + LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11); + + verify(mExecutorService).execute(any()); + } + + @Test + public void lockScreenSecretChanged_syncsKeysForUser() throws Exception { + mRecoverableKeyStoreManager.lockScreenSecretChanged( + LockPatternUtils.CREDENTIAL_TYPE_PATTERN, + "password".getBytes(), + 11); + + verify(mExecutorService).execute(any()); + } + private static byte[] encryptedApplicationKey( SecretKey recoveryKey, byte[] applicationKey) throws Exception { return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of( |