Move LockSettingsService into locksettings package.
This service now has a large number of support classes so move them into
their own package to keep things tidy and easier to refactor.
Bug: 37090873
Test: runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests
Test: runtest frameworks-services -c com.android.server.locksettings.LockSettingsShellCommandTest
Test: runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests
Change-Id: Ic3cd00e6565749defd74498a3491c3d9b914ad90
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
new file mode 100644
index 0000000..a2b4568
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2016 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.locksettings;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.pm.UserInfo;
+import android.os.FileUtils;
+import android.os.IProgressListener;
+import android.os.UserManager;
+import android.security.KeyStore;
+import android.test.AndroidTestCase;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+
+public class BaseLockSettingsServiceTests extends AndroidTestCase {
+ protected static final int PRIMARY_USER_ID = 0;
+ 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;
+
+ private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
+ UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+ private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
+ UserInfo.FLAG_INITIALIZED);
+
+ private ArrayList<UserInfo> mPrimaryUserProfiles = new ArrayList<>();
+
+ LockSettingsService mService;
+
+ MockLockSettingsContext mContext;
+ LockSettingsStorageTestable mStorage;
+
+ LockPatternUtils mLockPatternUtils;
+ MockGateKeeperService mGateKeeperService;
+ NotificationManager mNotificationManager;
+ UserManager mUserManager;
+ MockStorageManager mStorageManager;
+ IActivityManager mActivityManager;
+ DevicePolicyManager mDevicePolicyManager;
+ KeyStore mKeyStore;
+ MockSyntheticPasswordManager mSpManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mLockPatternUtils = mock(LockPatternUtils.class);
+ mGateKeeperService = new MockGateKeeperService();
+ mNotificationManager = mock(NotificationManager.class);
+ mUserManager = mock(UserManager.class);
+ mStorageManager = new MockStorageManager();
+ mActivityManager = mock(IActivityManager.class);
+ mDevicePolicyManager = mock(DevicePolicyManager.class);
+
+ mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
+ mDevicePolicyManager);
+ mStorage = new LockSettingsStorageTestable(mContext,
+ new File(getContext().getFilesDir(), "locksettings"));
+ File storageDir = mStorage.mStorageDir;
+ if (storageDir.exists()) {
+ FileUtils.deleteContents(storageDir);
+ } else {
+ storageDir.mkdirs();
+ }
+
+ mSpManager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService);
+ mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils,
+ mStorage, mGateKeeperService, mKeyStore, mStorageManager, mActivityManager,
+ mSpManager);
+ 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.getUsers(anyBoolean())).thenReturn(mPrimaryUserProfiles);
+ when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserProfiles);
+ when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
+
+ when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ mStorageManager.unlockUser((int)args[0], (byte[])args[2],
+ (IProgressListener) args[3]);
+ return true;
+ }
+ });
+
+ when(mLockPatternUtils.getLockSettings()).thenReturn(mService);
+
+ // Adding a fake Device Owner app which will enable escrow token support in LSS.
+ when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(
+ new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
+ }
+
+ private UserInfo installChildProfile(int profileId) {
+ final UserInfo userInfo = new UserInfo(
+ profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
+ mPrimaryUserProfiles.add(userInfo);
+ when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo);
+ when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
+ when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
+ when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
+ return userInfo;
+ }
+
+ private UserInfo installAndTurnOffChildProfile(int profileId) {
+ final UserInfo userInfo = installChildProfile(profileId);
+ userInfo.flags |= UserInfo.FLAG_QUIET_MODE;
+ when(mUserManager.isUserRunning(eq(profileId))).thenReturn(false);
+ when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(false);
+ return userInfo;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mStorage.closeDatabase();
+ File db = getContext().getDatabasePath("locksettings.db");
+ assertTrue(!db.exists() || db.delete());
+
+ 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/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
new file mode 100644
index 0000000..3a4aa2d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -0,0 +1,140 @@
+/*
+ * 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.locksettings;
+
+import static org.mockito.Mockito.mock;
+
+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.security.keystore.KeyPermanentlyInvalidatedException;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.io.FileNotFoundException;
+
+public class LockSettingsServiceTestable extends LockSettingsService {
+
+ private static class MockInjector extends LockSettingsService.Injector {
+
+ private LockSettingsStorage mLockSettingsStorage;
+ private KeyStore mKeyStore;
+ private IActivityManager mActivityManager;
+ private LockPatternUtils mLockPatternUtils;
+ private IStorageManager mStorageManager;
+ private SyntheticPasswordManager mSpManager;
+
+ public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
+ IActivityManager activityManager, LockPatternUtils lockPatternUtils,
+ IStorageManager storageManager, SyntheticPasswordManager spManager) {
+ super(context);
+ mLockSettingsStorage = storage;
+ mKeyStore = keyStore;
+ mActivityManager = activityManager;
+ mLockPatternUtils = lockPatternUtils;
+ mStorageManager = storageManager;
+ mSpManager = spManager;
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mock(Handler.class);
+ }
+
+ @Override
+ public LockSettingsStorage getStorage() {
+ return mLockSettingsStorage;
+ }
+
+ @Override
+ public LockSettingsStrongAuth getStrongAuth() {
+ return mock(LockSettingsStrongAuth.class);
+ }
+
+ @Override
+ public SynchronizedStrongAuthTracker getStrongAuthTracker() {
+ return mock(SynchronizedStrongAuthTracker.class);
+ }
+
+ @Override
+ public IActivityManager getActivityManager() {
+ return mActivityManager;
+ }
+
+ @Override
+ public LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
+ @Override
+ public KeyStore getKeyStore() {
+ return mKeyStore;
+ }
+
+ @Override
+ public IStorageManager getStorageManager() {
+ return mStorageManager;
+ }
+
+ @Override
+ public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
+ return mSpManager;
+ }
+
+ @Override
+ public int binderGetCallingUid() {
+ return Process.SYSTEM_UID;
+ }
+
+
+ }
+
+ protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
+ LockSettingsStorage storage, MockGateKeeperService gatekeeper, KeyStore keystore,
+ IStorageManager storageManager, IActivityManager mActivityManager,
+ SyntheticPasswordManager spManager) {
+ super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
+ storageManager, spManager));
+ mGateKeeperService = gatekeeper;
+ }
+
+ @Override
+ protected void tieProfileLockToParent(int userId, String password) {
+ mStorage.writeChildProfileLock(userId, password.getBytes());
+ }
+
+ @Override
+ 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/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
new file mode 100644
index 0000000..2f0ac38
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2016 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.locksettings;
+
+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 android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
+import com.android.server.locksettings.MockGateKeeperService.VerifyHandle;
+
+/**
+ * runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests
+ */
+public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testCreatePasswordPrimaryUser() throws RemoteException {
+ testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD);
+ }
+
+ public void testCreatePatternPrimaryUser() throws RemoteException {
+ testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN);
+ }
+
+ public void testChangePasswordPrimaryUser() throws RemoteException {
+ testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
+ "asdfghjk", CREDENTIAL_TYPE_PASSWORD);
+ }
+
+ public void testChangePatternPrimaryUser() throws RemoteException {
+ testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
+ "1596321", CREDENTIAL_TYPE_PATTERN);
+ }
+
+ public void testChangePasswordFailPrimaryUser() throws RemoteException {
+ final long sid = 1234;
+ final String FAILED_MESSAGE = "Failed to enroll password";
+ initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+
+ try {
+ mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, "badpwd",
+ PRIMARY_USER_ID);
+ fail("Did not fail when enrolling using incorrect credential");
+ } catch (RemoteException expected) {
+ assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
+ }
+ try {
+ mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ fail("Did not fail when enrolling using incorrect credential");
+ } catch (RemoteException expected) {
+ assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
+ }
+ assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+ }
+
+ public void testClearPasswordPrimaryUser() throws RemoteException {
+ final String PASSWORD = "password";
+ initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
+ mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD, PRIMARY_USER_ID);
+ assertFalse(mService.havePassword(PRIMARY_USER_ID));
+ assertFalse(mService.havePattern(PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testManagedProfileUnifiedChallenge() throws RemoteException {
+ final String UnifiedPassword = "testManagedProfileUnifiedChallenge-pwd";
+ 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 long turnedOffProfileSid =
+ mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+ assertTrue(turnedOffProfileSid != 0);
+ assertTrue(turnedOffProfileSid != primarySid);
+ assertTrue(turnedOffProfileSid != profileSid);
+
+ // clear auth token and wait for verify challenge from primary user to re-generate it.
+ mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+ mGateKeeperService.clearAuthToken(TURNED_OFF_PROFILE_USER_ID);
+ // verify credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ .getResponseCode());
+
+ // Verify that we have a new auth token for the profile
+ assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+ // Verify that profile which aren't running (e.g. turn off work) don't get unlocked
+ assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
+
+ /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
+ * credential as part of verifyCredential() before the new credential is committed in
+ * StorageManager. So we relax the check in our mock StorageManager to allow that.
+ */
+ mStorageManager.setIgnoreBadUnlock(true);
+ // Change primary password and verify that profile SID remains
+ mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ UnifiedPassword, PRIMARY_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_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));
+ assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
+ }
+
+ public void testManagedProfileSeparateChallenge() throws RemoteException {
+ final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
+ final String profilePassword = "testManagedProfileSeparateChallenge-profile";
+ mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PRIMARY_USER_ID);
+ /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
+ * credential as part of verifyCredential() before the new credential is committed in
+ * StorageManager. So we relax the check in our mock StorageManager to allow that.
+ */
+ mStorageManager.setIgnoreBadUnlock(true);
+ mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ MANAGED_PROFILE_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // clear auth token and make sure verify challenge from primary user does not regenerate it.
+ mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+ // verify primary credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ .getResponseCode());
+ assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+
+ // verify profile credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+ // Change primary credential and make sure we don't affect profile
+ mStorageManager.setIgnoreBadUnlock(true);
+ mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ primaryPassword, PRIMARY_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ }
+
+ private void testCreateCredential(int userId, String credential, int type)
+ throws RemoteException {
+ mService.setLockCredential(credential, type, null, userId);
+ assertVerifyCredentials(userId, credential, type, -1);
+ }
+
+ private void testChangeCredentials(int userId, String newCredential, int newType,
+ String oldCredential, int oldType) throws RemoteException {
+ final long sid = 1234;
+ initializeStorageWithCredential(userId, oldCredential, oldType, sid);
+ mService.setLockCredential(newCredential, newType, oldCredential, userId);
+ assertVerifyCredentials(userId, newCredential, newType, sid);
+ }
+
+ private void assertVerifyCredentials(int userId, String credential, int type, long sid)
+ throws RemoteException{
+ final long challenge = 54321;
+ VerifyCredentialResponse response = mService.verifyCredential(credential, type, challenge,
+ userId);
+
+ assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
+ if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
+ final int incorrectType;
+ if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ assertTrue(mService.havePassword(userId));
+ assertFalse(mService.havePattern(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+ } else if (type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN){
+ assertFalse(mService.havePassword(userId));
+ assertTrue(mService.havePattern(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ } else {
+ assertFalse(mService.havePassword(userId));
+ assertFalse(mService.havePassword(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ }
+ // check for bad type
+ assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(credential,
+ incorrectType, challenge, userId).getResponseCode());
+ // check for bad credential
+ assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential("0" + credential,
+ type, challenge, userId).getResponseCode());
+ }
+
+ private void initializeStorageWithCredential(int userId, String credential, int type, long sid) {
+ byte[] oldHash = new VerifyHandle(credential.getBytes(), sid).toBytes();
+ if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ mStorage.writeCredentialHash(CredentialHash.create(oldHash,
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
+ } else {
+ mStorage.writeCredentialHash(CredentialHash.create(oldHash,
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
new file mode 100644
index 0000000..424c08c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 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.locksettings;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+
+import static com.android.internal.widget.LockPatternUtils.stringToPattern;
+
+import static junit.framework.Assert.*;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static java.io.FileDescriptor.*;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+
+/**
+ * Test class for {@link LockSettingsShellCommand}.
+ *
+ * runtest frameworks-services -c com.android.server.locksettings.LockSettingsShellCommandTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LockSettingsShellCommandTest {
+
+ private LockSettingsShellCommand mCommand;
+
+ private @Mock LockPatternUtils mLockPatternUtils;
+ private int mUserId;
+ private final Binder mBinder = new Binder();
+ private final ShellCallback mShellCallback = new ShellCallback();
+ private final ResultReceiver mResultReceiver = new ResultReceiver(
+ new Handler(Looper.getMainLooper()));
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ final Context context = InstrumentationRegistry.getTargetContext();
+ mUserId = ActivityManager.getCurrentUser();
+ mCommand = new LockSettingsShellCommand(context, mLockPatternUtils);
+ }
+
+ @Test
+ public void testWrongPassword() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(false);
+ assertEquals(-1, mCommand.exec(mBinder, in, out, err,
+ new String[] { "set-pin", "--old", "1234" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils, never()).saveLockPassword(any(), any(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testChangePin() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pin", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).saveLockPassword("4321", "1234", PASSWORD_QUALITY_NUMERIC,
+ mUserId);
+ }
+
+ @Test
+ public void testChangePassword() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-password", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).saveLockPassword("4321", "1234", PASSWORD_QUALITY_ALPHABETIC,
+ mUserId);
+ }
+
+ @Test
+ public void testChangePattern() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pattern", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).saveLockPattern(stringToPattern("4321"), "1234", mUserId);
+ }
+
+ @Test
+ public void testClear() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "clear", "--old", "1234" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).clearLock("1234", mUserId);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
new file mode 100644
index 0000000..4bdd1c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -0,0 +1,66 @@
+/*
+ * 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.locksettings;
+
+import android.content.Context;
+
+import java.io.File;
+
+public class LockSettingsStorageTestable extends LockSettingsStorage {
+
+ public File mStorageDir;
+
+ public LockSettingsStorageTestable(Context context, File storageDir) {
+ super(context);
+ mStorageDir = storageDir;
+ }
+
+ @Override
+ String getLockPatternFilename(int userId) {
+ return makeDirs(mStorageDir,
+ super.getLockPatternFilename(userId)).getAbsolutePath();
+ }
+
+ @Override
+ String getLockPasswordFilename(int userId) {
+ return makeDirs(mStorageDir,
+ super.getLockPasswordFilename(userId)).getAbsolutePath();
+ }
+
+ @Override
+ String getChildProfileLockFile(int userId) {
+ 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/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
new file mode 100644
index 0000000..4665441
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2014 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.locksettings;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.UserInfo;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.FileUtils;
+import android.os.UserManager;
+import android.test.AndroidTestCase;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * runtest frameworks-services -c com.android.server.locksettings.LockSettingsStorageTests
+ */
+public class LockSettingsStorageTests extends AndroidTestCase {
+ private final byte[] PASSWORD_0 = "thepassword0".getBytes();
+ private final byte[] PASSWORD_1 = "password1".getBytes();
+ private final byte[] PATTERN_0 = "123654".getBytes();
+ private final byte[] PATTERN_1 = "147852369".getBytes();
+
+ LockSettingsStorage mStorage;
+ File mStorageDir;
+
+ private File mDb;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mStorageDir = new File(getContext().getFilesDir(), "locksettings");
+ mDb = getContext().getDatabasePath("locksettings.db");
+
+ assertTrue(mStorageDir.exists() || mStorageDir.mkdirs());
+ assertTrue(FileUtils.deleteContents(mStorageDir));
+ assertTrue(!mDb.exists() || mDb.delete());
+
+ final UserManager mockUserManager = mock(UserManager.class);
+ // User 2 is a profile of user 1.
+ when(mockUserManager.getProfileParent(eq(2))).thenReturn(new UserInfo(1, "name", 0));
+ // User 3 is a profile of user 0.
+ when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0));
+
+ MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager,
+ mock(NotificationManager.class), mock(DevicePolicyManager.class));
+ mStorage = new LockSettingsStorageTestable(context,
+ new File(getContext().getFilesDir(), "locksettings"));
+ mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
+ @Override
+ public void initialize(SQLiteDatabase db) {
+ mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0);
+ }
+ });
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mStorage.closeDatabase();
+ }
+
+ public void testKeyValue_InitializeWorked() {
+ assertEquals("initialValue", mStorage.readKeyValue("initializedKey", "default", 0));
+ mStorage.clearCache();
+ assertEquals("initialValue", mStorage.readKeyValue("initializedKey", "default", 0));
+ }
+
+ public void testKeyValue_WriteThenRead() {
+ mStorage.writeKeyValue("key", "value", 0);
+ assertEquals("value", mStorage.readKeyValue("key", "default", 0));
+ mStorage.clearCache();
+ assertEquals("value", mStorage.readKeyValue("key", "default", 0));
+ }
+
+ public void testKeyValue_DefaultValue() {
+ assertEquals("default", mStorage.readKeyValue("unititialized key", "default", 0));
+ assertEquals("default2", mStorage.readKeyValue("unititialized key", "default2", 0));
+ }
+
+ public void testKeyValue_Concurrency() {
+ final Object monitor = new Object();
+ List<Thread> threads = new ArrayList<>();
+ for (int i = 0; i < 100; i++) {
+ final int threadId = i;
+ threads.add(new Thread() {
+ @Override
+ public void run() {
+ synchronized (monitor) {
+ try {
+ monitor.wait();
+ } catch (InterruptedException e) {
+ return;
+ }
+ mStorage.writeKeyValue("key", "1 from thread " + threadId, 0);
+ mStorage.readKeyValue("key", "default", 0);
+ mStorage.writeKeyValue("key", "2 from thread " + threadId, 0);
+ mStorage.readKeyValue("key", "default", 0);
+ mStorage.writeKeyValue("key", "3 from thread " + threadId, 0);
+ mStorage.readKeyValue("key", "default", 0);
+ mStorage.writeKeyValue("key", "4 from thread " + threadId, 0);
+ mStorage.readKeyValue("key", "default", 0);
+ mStorage.writeKeyValue("key", "5 from thread " + threadId, 0);
+ mStorage.readKeyValue("key", "default", 0);
+ }
+ }
+ });
+ threads.get(i).start();
+ }
+ mStorage.writeKeyValue("key", "initalValue", 0);
+ synchronized (monitor) {
+ monitor.notifyAll();
+ }
+ for (int i = 0; i < threads.size(); i++) {
+ try {
+ threads.get(i).join();
+ } catch (InterruptedException e) {
+ }
+ }
+ assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0));
+ mStorage.clearCache();
+ assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0));
+ }
+
+ public void testKeyValue_CacheStarvedWriter() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ List<Thread> threads = new ArrayList<>();
+ for (int i = 0; i < 100; i++) {
+ final int threadId = i;
+ threads.add(new Thread() {
+ @Override
+ public void run() {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ return;
+ }
+ if (threadId == 50) {
+ mStorage.writeKeyValue("starvedWriterKey", "value", 0);
+ } else {
+ mStorage.readKeyValue("starvedWriterKey", "default", 0);
+ }
+ }
+ });
+ threads.get(i).start();
+ }
+ latch.countDown();
+ for (int i = 0; i < threads.size(); i++) {
+ try {
+ threads.get(i).join();
+ } catch (InterruptedException e) {
+ }
+ }
+ String cached = mStorage.readKeyValue("key", "default", 0);
+ mStorage.clearCache();
+ String storage = mStorage.readKeyValue("key", "default", 0);
+ assertEquals("Cached value didn't match stored value", storage, cached);
+ }
+
+ public void testRemoveUser() {
+ mStorage.writeKeyValue("key", "value", 0);
+ writePasswordBytes(PASSWORD_0, 0);
+ writePatternBytes(PATTERN_0, 0);
+
+ mStorage.writeKeyValue("key", "value", 1);
+ writePasswordBytes(PASSWORD_1, 1);
+ writePatternBytes(PATTERN_1, 1);
+
+ mStorage.removeUser(0);
+
+ assertEquals("value", mStorage.readKeyValue("key", "default", 1));
+ assertEquals("default", mStorage.readKeyValue("key", "default", 0));
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_NONE, mStorage.readCredentialHash(0).type);
+ assertPatternBytes(PATTERN_1, 1);
+ }
+
+ public void testCredential_Default() {
+ assertEquals(mStorage.readCredentialHash(0).type, LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ }
+
+ public void testPassword_Write() {
+ writePasswordBytes(PASSWORD_0, 0);
+
+ assertPasswordBytes(PASSWORD_0, 0);
+ mStorage.clearCache();
+ assertPasswordBytes(PASSWORD_0, 0);
+ }
+
+ public void testPassword_WriteProfileWritesParent() {
+ writePasswordBytes(PASSWORD_0, 1);
+ writePasswordBytes(PASSWORD_1, 2);
+
+ assertPasswordBytes(PASSWORD_0, 1);
+ assertPasswordBytes(PASSWORD_1, 2);
+ mStorage.clearCache();
+ assertPasswordBytes(PASSWORD_0, 1);
+ assertPasswordBytes(PASSWORD_1, 2);
+ }
+
+ public void testLockType_WriteProfileWritesParent() {
+ writePasswordBytes(PASSWORD_0, 10);
+ writePatternBytes(PATTERN_0, 20);
+
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ mStorage.readCredentialHash(10).type);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ mStorage.readCredentialHash(20).type);
+ mStorage.clearCache();
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ mStorage.readCredentialHash(10).type);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ mStorage.readCredentialHash(20).type);
+ }
+
+ public void testPassword_WriteParentWritesProfile() {
+ writePasswordBytes(PASSWORD_0, 2);
+ writePasswordBytes(PASSWORD_1, 1);
+
+ assertPasswordBytes(PASSWORD_1, 1);
+ assertPasswordBytes(PASSWORD_0, 2);
+ mStorage.clearCache();
+ assertPasswordBytes(PASSWORD_1, 1);
+ assertPasswordBytes(PASSWORD_0, 2);
+ }
+
+ public void testProfileLock_ReadWriteChildProfileLock() {
+ assertFalse(mStorage.hasChildProfileLock(20));
+ mStorage.writeChildProfileLock(20, PASSWORD_0);
+ assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20));
+ assertTrue(mStorage.hasChildProfileLock(20));
+ mStorage.clearCache();
+ assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20));
+ assertTrue(mStorage.hasChildProfileLock(20));
+ }
+
+ public void testPattern_Write() {
+ writePatternBytes(PATTERN_0, 0);
+
+ assertPatternBytes(PATTERN_0, 0);
+ mStorage.clearCache();
+ assertPatternBytes(PATTERN_0, 0);
+ }
+
+ public void testPattern_WriteProfileWritesParent() {
+ writePatternBytes(PATTERN_0, 1);
+ writePatternBytes(PATTERN_1, 2);
+
+ assertPatternBytes(PATTERN_0, 1);
+ assertPatternBytes(PATTERN_1, 2);
+ mStorage.clearCache();
+ assertPatternBytes(PATTERN_0, 1);
+ assertPatternBytes(PATTERN_1, 2);
+ }
+
+ public void testPattern_WriteParentWritesProfile() {
+ writePatternBytes(PATTERN_1, 2);
+ writePatternBytes(PATTERN_0, 1);
+
+ assertPatternBytes(PATTERN_0, 1);
+ assertPatternBytes(PATTERN_1, 2);
+ mStorage.clearCache();
+ assertPatternBytes(PATTERN_0, 1);
+ assertPatternBytes(PATTERN_1, 2);
+ }
+
+ public void testPrefetch() {
+ mStorage.writeKeyValue("key", "toBeFetched", 0);
+ writePatternBytes(PATTERN_0, 0);
+
+ mStorage.clearCache();
+ mStorage.prefetchUser(0);
+
+ assertEquals("toBeFetched", mStorage.readKeyValue("key", "default", 0));
+ assertPatternBytes(PATTERN_0, 0);
+ }
+
+ public void testFileLocation_Owner() {
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
+
+ assertEquals("/data/system/gesture.key", storage.getLegacyLockPatternFilename(0));
+ assertEquals("/data/system/password.key", storage.getLegacyLockPasswordFilename(0));
+ assertEquals("/data/system/gatekeeper.pattern.key", storage.getLockPatternFilename(0));
+ assertEquals("/data/system/gatekeeper.password.key", storage.getLockPasswordFilename(0));
+ }
+
+ public void testFileLocation_SecondaryUser() {
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
+
+ assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1));
+ assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1));
+ }
+
+ public void testFileLocation_ProfileToSecondary() {
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
+
+ assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2));
+ assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2));
+ }
+
+ public void testFileLocation_ProfileToOwner() {
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
+
+ assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3));
+ 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) +
+ "> but was:<" + Arrays.toString(actual) + ">");
+ }
+ }
+
+ private void writePasswordBytes(byte[] password, int userId) {
+ mStorage.writeCredentialHash(CredentialHash.create(
+ password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
+ }
+
+ private void writePatternBytes(byte[] pattern, int userId) {
+ mStorage.writeCredentialHash(CredentialHash.create(
+ pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
+ }
+
+ private void assertPasswordBytes(byte[] password, int userId) {
+ CredentialHash cred = mStorage.readCredentialHash(userId);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, cred.type);
+ assertArrayEquals(password, cred.hash);
+ }
+
+ private void assertPatternBytes(byte[] pattern, int userId) {
+ CredentialHash cred = mStorage.readCredentialHash(userId);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN, cred.type);
+ assertArrayEquals(pattern, cred.hash);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java
new file mode 100644
index 0000000..eefd361
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java
@@ -0,0 +1,194 @@
+/*
+ * 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.locksettings;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.util.ArrayMap;
+
+import junit.framework.AssertionFailedError;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+public class MockGateKeeperService implements IGateKeeperService {
+ static class VerifyHandle {
+ public byte[] password;
+ public long sid;
+
+ public VerifyHandle(byte[] password, long sid) {
+ this.password = password;
+ this.sid = sid;
+ }
+
+ public VerifyHandle(byte[] handle) {
+ ByteBuffer buffer = ByteBuffer.allocate(handle.length);
+ buffer.put(handle, 0, handle.length);
+ buffer.flip();
+ int version = buffer.get();
+ sid = buffer.getLong();
+ password = new byte[buffer.remaining()];
+ buffer.get(password);
+ }
+
+ public byte[] toBytes() {
+ ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + password.length);
+ buffer.put((byte)0);
+ buffer.putLong(sid);
+ buffer.put(password);
+ return buffer.array();
+ }
+ }
+
+ static class AuthToken {
+ public long challenge;
+ public long sid;
+
+ public AuthToken(long challenge, long sid) {
+ this.challenge = challenge;
+ this.sid = sid;
+ }
+
+ public AuthToken(byte[] handle) {
+ ByteBuffer buffer = ByteBuffer.allocate(handle.length);
+ buffer.put(handle, 0, handle.length);
+ buffer.flip();
+ int version = buffer.get();
+ challenge = buffer.getLong();
+ sid = buffer.getLong();
+ }
+
+ public byte[] toBytes() {
+ ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + Long.BYTES);
+ buffer.put((byte)0);
+ buffer.putLong(challenge);
+ buffer.putLong(sid);
+ return buffer.array();
+ }
+ }
+
+ private ArrayMap<Integer, Long> sidMap = new ArrayMap<>();
+ private ArrayMap<Integer, AuthToken> authTokenMap = new ArrayMap<>();
+
+ private ArrayMap<Integer, byte[]> handleMap = new ArrayMap<>();
+
+ @Override
+ public GateKeeperResponse enroll(int uid, byte[] currentPasswordHandle, byte[] currentPassword,
+ byte[] desiredPassword) throws android.os.RemoteException {
+
+ if (currentPasswordHandle != null) {
+ VerifyHandle handle = new VerifyHandle(currentPasswordHandle);
+ if (Arrays.equals(currentPassword, handle.password)) {
+ // Trusted enroll
+ VerifyHandle newHandle = new VerifyHandle(desiredPassword, handle.sid);
+ refreshSid(uid, handle.sid, false);
+ handleMap.put(uid, newHandle.toBytes());
+ return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
+ } else {
+ return null;
+ }
+ } else {
+ // Untrusted enroll
+ long newSid = new Random().nextLong();
+ VerifyHandle newHandle = new VerifyHandle(desiredPassword, newSid);
+ refreshSid(uid, newSid, true);
+ handleMap.put(uid, newHandle.toBytes());
+ return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
+ }
+ }
+
+ @Override
+ public GateKeeperResponse verify(int uid, byte[] enrolledPasswordHandle,
+ byte[] providedPassword) throws android.os.RemoteException {
+ return verifyChallenge(uid, 0, enrolledPasswordHandle, providedPassword);
+ }
+
+ @Override
+ public GateKeeperResponse verifyChallenge(int uid, long challenge,
+ byte[] enrolledPasswordHandle, byte[] providedPassword) throws RemoteException {
+
+ VerifyHandle handle = new VerifyHandle(enrolledPasswordHandle);
+ if (Arrays.equals(handle.password, providedPassword)) {
+ byte[] knownHandle = handleMap.get(uid);
+ if (knownHandle != null) {
+ if (!Arrays.equals(knownHandle, enrolledPasswordHandle)) {
+ throw new AssertionFailedError("Got correct but obsolete handle");
+ }
+ }
+ refreshSid(uid, handle.sid, false);
+ AuthToken token = new AuthToken(challenge, handle.sid);
+ refreshAuthToken(uid, token);
+ return GateKeeperResponse.createOkResponse(token.toBytes(), false);
+ } else {
+ return GateKeeperResponse.createGenericResponse(GateKeeperResponse.RESPONSE_ERROR);
+ }
+ }
+
+ private void refreshAuthToken(int uid, AuthToken token) {
+ authTokenMap.put(uid, token);
+ }
+
+ public AuthToken getAuthToken(int uid) {
+ 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);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearSecureUserId(int userId) throws RemoteException {
+ sidMap.remove(userId);
+ }
+
+ @Override
+ public long getSecureUserId(int userId) throws RemoteException {
+ if (sidMap.containsKey(userId)) {
+ return sidMap.get(userId);
+ } else {
+ return 0L;
+ }
+ }
+
+ private void refreshSid(int uid, long sid, boolean force) {
+ if (!sidMap.containsKey(uid) || force) {
+ sidMap.put(uid, sid);
+ } else{
+ if (sidMap.get(uid) != sid) {
+ throw new AssertionFailedError("Inconsistent SID");
+ }
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
new file mode 100644
index 0000000..c76a83e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
@@ -0,0 +1,57 @@
+/*
+ * 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.locksettings;
+
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.UserManager;
+
+public class MockLockSettingsContext extends ContextWrapper {
+
+ private UserManager mUserManager;
+ private NotificationManager mNotificationManager;
+ private DevicePolicyManager mDevicePolicyManager;
+
+ public MockLockSettingsContext(Context base, UserManager userManager,
+ NotificationManager notificationManager, DevicePolicyManager devicePolicyManager) {
+ super(base);
+ mUserManager = userManager;
+ mNotificationManager = notificationManager;
+ mDevicePolicyManager = devicePolicyManager;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (USER_SERVICE.equals(name)) {
+ return mUserManager;
+ } else if (NOTIFICATION_SERVICE.equals(name)) {
+ return mNotificationManager;
+ } else if (DEVICE_POLICY_SERVICE.equals(name)) {
+ return mDevicePolicyManager;
+ } else {
+ throw new RuntimeException("System service not mocked: " + name);
+ }
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ // Skip permission checks for unit tests.
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockStorageManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockStorageManager.java
new file mode 100644
index 0000000..ac46bae
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockStorageManager.java
@@ -0,0 +1,503 @@
+/*
+ * 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.locksettings;
+
+import android.content.pm.IPackageMoveObserver;
+import android.os.IBinder;
+import android.os.IProgressListener;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.storage.DiskInfo;
+import android.os.storage.IObbActionListener;
+import android.os.storage.IStorageEventListener;
+import android.os.storage.IStorageManager;
+import android.os.storage.IStorageShutdownObserver;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+import android.os.storage.VolumeRecord;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import com.android.internal.os.AppFuseMount;
+
+import junit.framework.AssertionFailedError;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MockStorageManager implements IStorageManager {
+
+ private ArrayMap<Integer, ArrayList<Pair<byte[], byte[]>>> mAuth = new ArrayMap<>();
+ private boolean mIgnoreBadUnlock;
+
+ @Override
+ public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret)
+ throws RemoteException {
+ getUserAuth(userId).add(new Pair<>(token, secret));
+ }
+
+ @Override
+ public void fixateNewestUserKeyAuth(int userId) throws RemoteException {
+ ArrayList<Pair<byte[], byte[]>> auths = mAuth.get(userId);
+ Pair<byte[], byte[]> latest = auths.get(auths.size() - 1);
+ auths.clear();
+ auths.add(latest);
+ }
+
+ private ArrayList<Pair<byte[], byte[]>> getUserAuth(int userId) {
+ if (!mAuth.containsKey(userId)) {
+ ArrayList<Pair<byte[], byte[]>> auths = new ArrayList<Pair<byte[], byte[]>>();
+ auths.add(new Pair(null, null));
+ mAuth.put(userId, auths);
+ }
+ return mAuth.get(userId);
+ }
+
+ public byte[] getUserUnlockToken(int userId) {
+ ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+ if (auths.size() != 1) {
+ throw new AssertionFailedError("More than one secret exists");
+ }
+ return auths.get(0).second;
+ }
+
+ public void unlockUser(int userId, byte[] secret, IProgressListener listener)
+ throws RemoteException {
+ listener.onStarted(userId, null);
+ listener.onFinished(userId, null);
+ ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+ if (secret != null) {
+ if (auths.size() > 1) {
+ throw new AssertionFailedError("More than one secret exists");
+ }
+ Pair<byte[], byte[]> auth = auths.get(0);
+ if ((!mIgnoreBadUnlock) && auth.second != null && !Arrays.equals(secret, auth.second)) {
+ throw new AssertionFailedError("Invalid secret to unlock user");
+ }
+ } else {
+ if (auths != null && auths.size() > 0) {
+ throw new AssertionFailedError("Cannot unlock encrypted user with empty token");
+ }
+ }
+ }
+
+ public void setIgnoreBadUnlock(boolean ignore) {
+ mIgnoreBadUnlock = ignore;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void registerListener(IStorageEventListener listener) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unregisterListener(IStorageEventListener listener) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUsbMassStorageConnected() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setUsbMassStorageEnabled(boolean enable) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUsbMassStorageEnabled() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mountVolume(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmountVolume(String mountPoint, boolean force, boolean removeEncryption)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public int formatVolume(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int[] getStorageUsers(String path) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getVolumeState(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid,
+ boolean external) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int finalizeSecureContainer(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int destroySecureContainer(String id, boolean force) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int unmountSecureContainer(String id, boolean force) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isSecureContainerMounted(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int renameSecureContainer(String oldId, String newId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSecureContainerPath(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String[] getSecureContainerList() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void shutdown(IStorageShutdownObserver observer) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void finishMediaUpdate() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void mountObb(String rawPath, String canonicalPath, String key, IObbActionListener token,
+ int nonce) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isObbMounted(String rawPath) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getMountedObbPath(String rawPath) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isExternalStorageEmulated() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int decryptStorage(String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int encryptStorage(int type, String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int changeEncryptionPassword(int type, String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public StorageVolume[] getVolumeList(int uid, String packageName, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSecureContainerFilesystemPath(String cid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getEncryptionState() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int verifyEncryptionPassword(String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int fixPermissionsSecureContainer(String id, int gid, String filename)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mkdirs(String callingPkg, String path) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getPasswordType() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPassword() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearPassword() throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public void setField(String field, String contents) throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public String getField(String field) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int resizeSecureContainer(String id, int sizeMb, String key) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long lastMaintenance() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void runMaintenance() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void waitForAsecScan() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DiskInfo[] getDisks() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public VolumeInfo[] getVolumes(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public VolumeRecord[] getVolumeRecords(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void mount(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmount(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void format(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionPublic(String diskId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionPrivate(String diskId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionMixed(String diskId, int ratio) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void forgetVolume(String fsUuid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void forgetAllVolumes() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPrimaryStorageUuid() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long benchmark(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDebugFlags(int flags, int mask) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void createUserKey(int userId, int serialNumber, boolean ephemeral)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void destroyUserKey(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void lockUserKey(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUserKeyUnlocked(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void destroyUserStorage(String volumeUuid, int userId, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isConvertibleToFBE() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void fstrim(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getCacheQuotaBytes(String volumeUuid, int uid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getCacheSizeBytes(String volumeUuid, int uid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getAllocatableBytes(String path, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void allocateBytes(String path, long bytes, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
new file mode 100644
index 0000000..ddef5dc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -0,0 +1,115 @@
+/*
+ * 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.locksettings;
+
+import android.hardware.weaver.V1_0.IWeaver;
+import android.os.RemoteException;
+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;
+ private IWeaver mWeaverService;
+
+ 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;
+ }
+ }
+
+ @Override
+ protected IWeaver getWeaverService() throws RemoteException {
+ return mWeaverService;
+ }
+
+ public void enableWeaver() {
+ mWeaverService = new MockWeaverService();
+ initWeaverService();
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockWeaverService.java b/services/tests/servicestests/src/com/android/server/locksettings/MockWeaverService.java
new file mode 100644
index 0000000..34831cd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockWeaverService.java
@@ -0,0 +1,108 @@
+package com.android.server.locksettings;
+
+import android.hardware.weaver.V1_0.IWeaver;
+import android.hardware.weaver.V1_0.WeaverConfig;
+import android.hardware.weaver.V1_0.WeaverReadResponse;
+import android.hardware.weaver.V1_0.WeaverStatus;
+import android.hidl.base.V1_0.DebugInfo;
+import android.os.IHwBinder;
+import android.os.IHwBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.Pair;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MockWeaverService implements IWeaver {
+
+ private static final int MAX_SLOTS = 8;
+ private static final int KEY_LENGTH = 256 / 8;
+ private static final int VALUE_LENGTH = 256 / 8;
+
+ private Pair<ArrayList<Byte>, ArrayList<Byte>>[] slots = new Pair[MAX_SLOTS];
+ @Override
+ public void getConfig(getConfigCallback cb) throws RemoteException {
+ WeaverConfig config = new WeaverConfig();
+ config.keySize = KEY_LENGTH;
+ config.valueSize = VALUE_LENGTH;
+ config.slots = MAX_SLOTS;
+ cb.onValues(WeaverStatus.OK, config);
+ }
+
+ @Override
+ public int write(int slotId, ArrayList<Byte> key, ArrayList<Byte> value)
+ throws RemoteException {
+ if (slotId < 0 || slotId >= MAX_SLOTS) {
+ throw new RuntimeException("Invalid slot id");
+ }
+ slots[slotId] = Pair.create((ArrayList<Byte>) key.clone(), (ArrayList<Byte>) value.clone());
+ return WeaverStatus.OK;
+ }
+
+ @Override
+ public void read(int slotId, ArrayList<Byte> key, readCallback cb) throws RemoteException {
+ if (slotId < 0 || slotId >= MAX_SLOTS) {
+ throw new RuntimeException("Invalid slot id");
+ }
+
+ WeaverReadResponse response = new WeaverReadResponse();
+ if (key.equals(slots[slotId].first)) {
+ response.value.addAll(slots[slotId].second);
+ cb.onValues(WeaverStatus.OK, response);
+ } else {
+ cb.onValues(WeaverStatus.FAILED, response);
+ }
+ }
+
+ @Override
+ public IHwBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ArrayList<String> interfaceChain() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String interfaceDescriptor() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setHALInstrumentation() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean linkToDeath(DeathRecipient recipient, long cookie) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void ping() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DebugInfo getDebugInfo() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void notifySyspropsChanged() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean unlinkToDeath(DeathRecipient recipient) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ArrayList<byte[]> getHashChain() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
new file mode 100644
index 0000000..0d35385
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -0,0 +1,349 @@
+/*
+ * 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.locksettings;
+
+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.locksettings.SyntheticPasswordManager.AuthenticationResult;
+import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+
+
+/**
+ * runtest frameworks-services -c com.android.server.locksettings.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));
+ }
+
+ public void testTokenBasedResetPassword() throws RemoteException {
+ final String PASSWORD = "password";
+ final String PATTERN = "123654";
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ public void testTokenBasedClearPassword() throws RemoteException {
+ final String PASSWORD = "password";
+ final String PATTERN = "123654";
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+ mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException {
+ final String PASSWORD = "password";
+ final String PATTERN = "123654";
+ final String NEWPASSWORD = "password";
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD, PRIMARY_USER_ID);
+
+ mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration() throws RemoteException {
+ final String TOKEN = "some-high-entropy-secure-token";
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ }
+
+ public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration() throws RemoteException {
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(null, PRIMARY_USER_ID);
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ }
+
+ public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration() throws RemoteException {
+ final String TOKEN = "some-high-entropy-secure-token";
+ final String PASSWORD = "password";
+ // Set up pre-SP user password
+ disableSyntheticPassword(PRIMARY_USER_ID);
+ mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PRIMARY_USER_ID);
+ enableSyntheticPassword(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ // Token not activated immediately since user password exists
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ // Activate token (password gets migrated to SP at the same time)
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ PRIMARY_USER_ID).getResponseCode());
+ // Verify token is activated
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_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
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
new file mode 100644
index 0000000..5e56704
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -0,0 +1,11 @@
+package com.android.server.locksettings;
+
+public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mSpManager.enableWeaver();
+ }
+
+}