Add synthetic password to authentication flow
The user password is used to unlock a per-user synthetic password which
serves the purpose of what the user password previsouly achieves (protect
keystore, vold disk encryption, auth token generation).
Test: runtest frameworks-services -c com.android.server.SyntheticPasswordTests
Test: manual
1. Start with fresh device, enable synthetic password with "adb shell cmd lock_settings sp 1"
1.1 add device lock, reboot and verify (positive & negative); change device lock, reboot and verify.
1.2 Inflate a work profile, reboot and verify device lock. check SID with "adb shell dumpsys lock_settings"
1.3 Un-unify and add work challenge, reboot and verify work challenge and SID.
1.4 Re-unify work challenge, reboot and verify.
1.5 Clear device lock, reboot and verify lock and SID.
2. Start with a fresh device, add a device lock and inflate a work profile.
2.1 Enable synthetic password, note current SID
2.2 Reboot and unlock device. Verify synthetic password is generated and SID remains.
2.3 Clear device lock, reboot and verify (SID should be cleared)
3. Start with a fresh device, inflate a work profile, add separate work challenge
3.1 Enable synthetic password, not current SID
3.2 Reboot and unlock device and profile. Verify synthetic password is generated.
3.3 Clear device lock only, reboot and verify (work profile SID should remain)
All steps tested on marlin (FBE) and bullhead (FDE)
Bug: 33126414
Change-Id: Idb9ebfc7bba2fe40670c5fee2189e873d9704540
diff --git a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
index c89d35c..c6265bc 100644
--- a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
@@ -134,5 +134,13 @@
File storageDir = mStorage.mStorageDir;
assertTrue(FileUtils.deleteContents(storageDir));
}
+
+ protected static void assertArrayEquals(byte[] expected, byte[] actual) {
+ assertTrue(Arrays.equals(expected, actual));
+ }
+
+ protected static void assertArrayNotSame(byte[] expected, byte[] actual) {
+ assertFalse(Arrays.equals(expected, actual));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
index 613ec0b..cfdb5b1 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
@@ -21,9 +21,11 @@
import android.app.IActivityManager;
import android.content.Context;
import android.os.Handler;
+import android.os.Process;
+import android.os.RemoteException;
import android.os.storage.IStorageManager;
import android.security.KeyStore;
-import android.service.gatekeeper.IGateKeeperService;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
import com.android.internal.widget.LockPatternUtils;
@@ -38,16 +40,18 @@
private IActivityManager mActivityManager;
private LockPatternUtils mLockPatternUtils;
private IStorageManager mStorageManager;
+ private MockGateKeeperService mGatekeeper;
public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
IActivityManager activityManager, LockPatternUtils lockPatternUtils,
- IStorageManager storageManager) {
+ IStorageManager storageManager, MockGateKeeperService gatekeeper) {
super(context);
mLockSettingsStorage = storage;
mKeyStore = keyStore;
mActivityManager = activityManager;
mLockPatternUtils = lockPatternUtils;
mStorageManager = storageManager;
+ mGatekeeper = gatekeeper;
}
@Override
@@ -89,13 +93,25 @@
public IStorageManager getStorageManager() {
return mStorageManager;
}
+
+ @Override
+ public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
+ return new MockSyntheticPasswordManager(storage, mGatekeeper);
+ }
+
+ @Override
+ public int binderGetCallingUid() {
+ return Process.SYSTEM_UID;
+ }
+
+
}
protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
- LockSettingsStorage storage, IGateKeeperService gatekeeper, KeyStore keystore,
+ LockSettingsStorage storage, MockGateKeeperService gatekeeper, KeyStore keystore,
IStorageManager storageManager, IActivityManager mActivityManager) {
super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
- storageManager));
+ storageManager, gatekeeper));
mGateKeeperService = gatekeeper;
}
@@ -105,12 +121,18 @@
}
@Override
- protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException {
+ protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException, KeyPermanentlyInvalidatedException {
byte[] storedData = mStorage.readChildProfileLock(userId);
if (storedData == null) {
throw new FileNotFoundException("Child profile lock file not found");
}
+ try {
+ if (mGateKeeperService.getSecureUserId(userId) == 0) {
+ throw new KeyPermanentlyInvalidatedException();
+ }
+ } catch (RemoteException e) {
+ // shouldn't happen.
+ }
return new String(storedData);
}
-
}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
index 4c2e171..ae9762a 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
@@ -123,6 +123,12 @@
UnifiedPassword, PRIMARY_USER_ID);
mStorageManager.setIgnoreBadUnlock(false);
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+ //Clear unified challenge
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, UnifiedPassword,
+ PRIMARY_USER_ID);
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
}
public void testManagedProfileSeparateChallenge() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
index e81b02f..18da1a5 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
@@ -31,19 +31,36 @@
@Override
String getLockPatternFilename(int userId) {
- return new File(mStorageDir,
- super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath();
+ return makeDirs(mStorageDir,
+ super.getLockPatternFilename(userId)).getAbsolutePath();
}
@Override
String getLockPasswordFilename(int userId) {
- return new File(mStorageDir,
- super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath();
+ return makeDirs(mStorageDir,
+ super.getLockPasswordFilename(userId)).getAbsolutePath();
}
@Override
String getChildProfileLockFile(int userId) {
- return new File(mStorageDir,
- super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath();
+ return makeDirs(mStorageDir,
+ super.getChildProfileLockFile(userId)).getAbsolutePath();
+ }
+
+ @Override
+ protected File getSyntheticPasswordDirectoryForUser(int userId) {
+ return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser(
+ userId).getAbsolutePath());
+ }
+
+ private File makeDirs(File baseDir, String filePath) {
+ File path = new File(filePath);
+ if (path.getParent() == null) {
+ return new File(baseDir, filePath);
+ } else {
+ File mappedDir = new File(baseDir, path.getParent());
+ mappedDir.mkdirs();
+ return new File(mappedDir, path.getName());
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
index d110fea..c68fbdc 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
@@ -329,6 +329,16 @@
assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3));
}
+ public void testSyntheticPasswordState() {
+ final byte[] data = {1,2,3,4};
+ mStorage.writeSyntheticPasswordState(10, 1234L, "state", data);
+ assertArrayEquals(data, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
+ assertEquals(null, mStorage.readSyntheticPasswordState(0, 1234L, "state"));
+
+ mStorage.deleteSyntheticPasswordState(10, 1234L, "state", true);
+ assertEquals(null, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
+ }
+
private static void assertArrayEquals(byte[] expected, byte[] actual) {
if (!Arrays.equals(expected, actual)) {
fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
index 15983ca..bc93341 100644
--- a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
+++ b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
@@ -149,6 +149,15 @@
return authTokenMap.get(uid);
}
+ public AuthToken getAuthTokenForSid(long sid) {
+ for(AuthToken token : authTokenMap.values()) {
+ if (token.sid == sid) {
+ return token;
+ }
+ }
+ return null;
+ }
+
public void clearAuthToken(int uid) {
authTokenMap.remove(uid);
}
diff --git a/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java
new file mode 100644
index 0000000..93e3fc6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import android.util.ArrayMap;
+
+import junit.framework.AssertionFailedError;
+
+import java.nio.ByteBuffer;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+public class MockSyntheticPasswordManager extends SyntheticPasswordManager {
+
+ private MockGateKeeperService mGateKeeper;
+
+ public MockSyntheticPasswordManager(LockSettingsStorage storage,
+ MockGateKeeperService gatekeeper) {
+ super(storage);
+ mGateKeeper = gatekeeper;
+ }
+
+ private ArrayMap<String, byte[]> mBlobs = new ArrayMap<>();
+
+ @Override
+ protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
+ if (mBlobs.containsKey(blobKeyName) && !Arrays.equals(mBlobs.get(blobKeyName), blob)) {
+ throw new AssertionFailedError("blobKeyName content is overwritten: " + blobKeyName);
+ }
+ ByteBuffer buffer = ByteBuffer.allocate(blob.length);
+ buffer.put(blob, 0, blob.length);
+ buffer.flip();
+ int len;
+ len = buffer.getInt();
+ byte[] data = new byte[len];
+ buffer.get(data);
+ len = buffer.getInt();
+ byte[] appId = new byte[len];
+ buffer.get(appId);
+ long sid = buffer.getLong();
+ if (!Arrays.equals(appId, applicationId)) {
+ throw new AssertionFailedError("Invalid application id");
+ }
+ if (sid != 0 && mGateKeeper.getAuthTokenForSid(sid) == null) {
+ throw new AssertionFailedError("No valid auth token");
+ }
+ return data;
+ }
+
+ @Override
+ protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
+ ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + data.length + Integer.BYTES
+ + applicationId.length + Long.BYTES);
+ buffer.putInt(data.length);
+ buffer.put(data);
+ buffer.putInt(applicationId.length);
+ buffer.put(applicationId);
+ buffer.putLong(sid);
+ byte[] result = buffer.array();
+ mBlobs.put(blobKeyName, result);
+ return result;
+ }
+
+ @Override
+ protected void destroySPBlobKey(String keyAlias) {
+ }
+
+ @Override
+ protected long sidFromPasswordHandle(byte[] handle) {
+ return new MockGateKeeperService.VerifyHandle(handle).sid;
+ }
+
+ @Override
+ protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) {
+ try {
+ PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10, outLen * 8);
+ SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+ return f.generateSecret(spec).getEncoded();
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java
new file mode 100644
index 0000000..9d9595e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
+
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.SyntheticPasswordManager.AuthenticationResult;
+import com.android.server.SyntheticPasswordManager.AuthenticationToken;
+
+
+/**
+ * runtest frameworks-services -c com.android.server.SyntheticPasswordTests
+ */
+public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testPasswordBasedSyntheticPassword() throws RemoteException {
+ final int USER_ID = 10;
+ final String PASSWORD = "user-password";
+ final String BADPASSWORD = "bad-password";
+ MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService);
+ AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
+ null, USER_ID);
+ long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService, PASSWORD,
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken, USER_ID);
+
+ AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, PASSWORD, USER_ID);
+ assertEquals(result.authToken.deriveKeyStorePassword(), authToken.deriveKeyStorePassword());
+
+ result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, BADPASSWORD, USER_ID);
+ assertNull(result.authToken);
+ }
+
+ private void disableSyntheticPassword(int userId) throws RemoteException {
+ mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
+ }
+
+ private void enableSyntheticPassword(int userId) throws RemoteException {
+ mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
+ }
+
+ private boolean hasSyntheticPassword(int userId) throws RemoteException {
+ return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0;
+ }
+
+ public void testPasswordMigration() throws RemoteException {
+ final String PASSWORD = "testPasswordMigration-password";
+
+ disableSyntheticPassword(PRIMARY_USER_ID);
+ mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ // Performs migration
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+
+ // SP-based verification
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ private void initializeCredentialUnderSP(String password, int userId) throws RemoteException {
+ enableSyntheticPassword(userId);
+ mService.setLockCredential(password, password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD : LockPatternUtils.CREDENTIAL_TYPE_NONE, null, userId);
+ }
+
+ public void testSyntheticPasswordChangeCredential() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordChangeCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordChangeCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID);
+ mGateKeeperService.clearSecureUserId(PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testSyntheticPasswordVerifyCredential() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordVerifyCredential-password";
+ final String BADPASSWORD = "testSyntheticPasswordVerifyCredential-badpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(BADPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ }
+
+ public void testSyntheticPasswordClearCredential() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ // clear password
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID);
+ assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+ // set a new password
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ // clear password
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+ // set a new password
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ // Untrusted change password
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ assertNotSame(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertNotSame(sid ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+ // Verify the password
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ }
+
+
+ public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
+ final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd";
+ disableSyntheticPassword(PRIMARY_USER_ID);
+ disableSyntheticPassword(MANAGED_PROFILE_USER_ID);
+ mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // do migration
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ enableSyntheticPassword(MANAGED_PROFILE_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+
+ // verify
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
+ }
+
+ public void testManagedProfileSeparateChallengeMigration() throws RemoteException {
+ final String primaryPassword = "testManagedProfileSeparateChallengeMigration-primary";
+ final String profilePassword = "testManagedProfileSeparateChallengeMigration-profile";
+ mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, MANAGED_PROFILE_USER_ID);
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // do migration
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ enableSyntheticPassword(MANAGED_PROFILE_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode());
+
+ // verify
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
+ }
+ // b/34600579
+ //TODO: add non-migration work profile case, and unify/un-unify transition.
+ //TODO: test token after user resets password
+ //TODO: test token based reset after unified work challenge
+ //TODO: test clear password after unified work challenge
+}
+