summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Annie Meng <anniemeng@google.com> 2019-03-29 17:43:35 +0000
committer Annie Meng <anniemeng@google.com> 2019-04-03 14:49:31 +0100
commit086ddc81d5db2ccacf8b35d116e08f0ed5a9720f (patch)
tree3952d826e7e8bcd0d0d69f27a5ada634979298f2
parent0787f0cebf4866e29fe80907e70ba5eace1eeed0 (diff)
Dont sync keys using the unified challenge profile random credential
When the work profile has a tied screen lock to its parent, its lock credentials are set to a random password. This CL adds logic to prevent syncing keys with this random credential. On set/update lock: - If creating the work profile or going from separate -> unified lock screen: don't sync keys (random password case) - If going from unified -> separate lock screen: sync keys - If removing the parent lock: invalidate unified profile keys On unlock: - If unlocking a work profile with a unified lock: don't sync keys (random password case). - If unlocking a work profile with a separate lock: sync keys - If unlocking a parent profile that has work profiles with a unified lock: sync keys for the work profiles. Design: https://docs.google.com/document/d/1y6LXcf-Rk3TMG-Ka4pJ5fpinDaK4fnlCyGi3kuGWWNg/edit?usp=sharing Bug: 128834006 Test: 1) atest frameworks/base/services/tests/servicestests/src/com/android/server/locksettings/ 2) Manual testing of the following cases by verifying key sync on the backup device and being able to unencrypt the backup set on the restore device: a) Work profile unified lock screen: all 3 types (pin/password/pattern). b) Changing parent lock screen in the unified case -> updates keys for profile. c) Unified lock screen -> separate lock screen: updates keys. d) Separate lock screen and change credentials: updates keys. e) Separate lock screen -> unified lock screen: does not update keys with random password. f) Unified lock screen -> remove lock screen: invalidates keys. Change-Id: Ie2249f4c32fd6c48aae7f791e2d1e353b4ef9939
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java111
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java223
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java24
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(