1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.server.locksettings;
18 
19 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
20 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
21 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
22 
23 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
24 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
25 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
26 
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertFalse;
29 import static org.junit.Assert.assertNotNull;
30 import static org.junit.Assert.assertNull;
31 import static org.junit.Assert.assertTrue;
32 import static org.junit.Assert.fail;
33 import static org.mockito.Mockito.any;
34 import static org.mockito.Mockito.atLeastOnce;
35 import static org.mockito.Mockito.never;
36 import static org.mockito.Mockito.reset;
37 import static org.mockito.Mockito.verify;
38 
39 import android.app.admin.PasswordMetrics;
40 import android.os.RemoteException;
41 import android.os.UserHandle;
42 import android.platform.test.annotations.Presubmit;
43 
44 import androidx.test.filters.SmallTest;
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import com.android.internal.widget.LockPatternUtils;
48 import com.android.internal.widget.VerifyCredentialResponse;
49 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
50 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
51 import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
52 
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 import org.mockito.ArgumentCaptor;
56 
57 import java.util.ArrayList;
58 
59 
60 /**
61  * atest FrameworksServicesTests:SyntheticPasswordTests
62  */
63 @SmallTest
64 @Presubmit
65 @RunWith(AndroidJUnit4.class)
66 public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
67 
68     public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 55};
69     public static final byte[] PAYLOAD2 = new byte[] {2, 3, -2, -3, 44, 1};
70 
71     @Test
testPasswordBasedSyntheticPassword()72     public void testPasswordBasedSyntheticPassword() throws RemoteException {
73         final int USER_ID = 10;
74         final byte[] password = "user-password".getBytes();
75         final byte[] badPassword = "bad-password".getBytes();
76         MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
77                 mGateKeeperService, mUserManager, mPasswordSlotManager);
78         AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
79                 null, USER_ID);
80         long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService,
81                 password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken,
82                 PASSWORD_QUALITY_ALPHABETIC, USER_ID);
83 
84         AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(
85                 mGateKeeperService, handle, password, USER_ID, null);
86         assertArrayEquals(result.authToken.deriveKeyStorePassword(),
87                 authToken.deriveKeyStorePassword());
88 
89         result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle,
90                 badPassword, USER_ID, null);
91         assertNull(result.authToken);
92     }
93 
disableSyntheticPassword()94     private void disableSyntheticPassword() throws RemoteException {
95         mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
96     }
97 
enableSyntheticPassword()98     private void enableSyntheticPassword() throws RemoteException {
99         mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
100     }
101 
hasSyntheticPassword(int userId)102     private boolean hasSyntheticPassword(int userId) throws RemoteException {
103         return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0;
104     }
105 
initializeCredentialUnderSP(byte[] password, int userId)106     protected void initializeCredentialUnderSP(byte[] password, int userId) throws RemoteException {
107         enableSyntheticPassword();
108         int quality = password != null ? PASSWORD_QUALITY_ALPHABETIC
109                 : PASSWORD_QUALITY_UNSPECIFIED;
110         int type = password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
111                 : LockPatternUtils.CREDENTIAL_TYPE_NONE;
112         mService.setLockCredential(password, type, null, quality, userId, false);
113     }
114 
115     @Test
testSyntheticPasswordChangeCredential()116     public void testSyntheticPasswordChangeCredential() throws RemoteException {
117         final byte[] password = "testSyntheticPasswordChangeCredential-password".getBytes();
118         final byte[] newPassword = "testSyntheticPasswordChangeCredential-newpassword".getBytes();
119 
120         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
121         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
122         mService.setLockCredential(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, password,
123                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
124         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
125                 newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
126                         .getResponseCode());
127         assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
128     }
129 
130     @Test
testSyntheticPasswordVerifyCredential()131     public void testSyntheticPasswordVerifyCredential() throws RemoteException {
132         final byte[] password = "testSyntheticPasswordVerifyCredential-password".getBytes();
133         final byte[] badPassword = "testSyntheticPasswordVerifyCredential-badpassword".getBytes();
134 
135         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
136         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
137                 password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
138                         .getResponseCode());
139 
140         assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
141                 badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
142                         .getResponseCode());
143     }
144 
145     @Test
testSyntheticPasswordClearCredential()146     public void testSyntheticPasswordClearCredential() throws RemoteException {
147         final byte[] password = "testSyntheticPasswordClearCredential-password".getBytes();
148         final byte[] badPassword = "testSyntheticPasswordClearCredential-newpassword".getBytes();
149 
150         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
151         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
152         // clear password
153         mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, password,
154                 PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
155         assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
156 
157         // set a new password
158         mService.setLockCredential(badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
159                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
160         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
161                 badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
162                         .getResponseCode());
163         assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
164     }
165 
166     @Test
testSyntheticPasswordChangeCredentialKeepsAuthSecret()167     public void testSyntheticPasswordChangeCredentialKeepsAuthSecret() throws RemoteException {
168         final byte[] password =
169                 "testSyntheticPasswordChangeCredentialKeepsAuthSecret-password".getBytes();
170         final byte[] badPassword =
171                 "testSyntheticPasswordChangeCredentialKeepsAuthSecret-new".getBytes();
172 
173         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
174         mService.setLockCredential(badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, password,
175                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
176         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
177                 badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
178                         .getResponseCode());
179 
180         // Check the same secret was passed each time
181         ArgumentCaptor<ArrayList<Byte>> secret = ArgumentCaptor.forClass(ArrayList.class);
182         verify(mAuthSecretService, atLeastOnce()).primaryUserCredential(secret.capture());
183         assertEquals(1, secret.getAllValues().stream().distinct().count());
184     }
185 
186     @Test
testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret()187     public void testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret() throws RemoteException {
188         final byte[] password =
189                 "testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret-password".getBytes();
190         final byte[] newPassword =
191                 "testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret-new".getBytes();
192 
193         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
194         reset(mAuthSecretService);
195         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password,
196                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
197                         .getResponseCode());
198         verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
199     }
200 
201     @Test
testSecondaryUserDoesNotPassAuthSecret()202     public void testSecondaryUserDoesNotPassAuthSecret() throws RemoteException {
203         final byte[] password = "testSecondaryUserDoesNotPassAuthSecret-password".getBytes();
204 
205         initializeCredentialUnderSP(password, SECONDARY_USER_ID);
206         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
207                 password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, SECONDARY_USER_ID)
208                         .getResponseCode());
209         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
210     }
211 
212     @Test
testNoSyntheticPasswordOrCredentialDoesNotPassAuthSecret()213     public void testNoSyntheticPasswordOrCredentialDoesNotPassAuthSecret() throws RemoteException {
214         // Setting null doesn't create a synthetic password
215         initializeCredentialUnderSP(null, PRIMARY_USER_ID);
216 
217         reset(mAuthSecretService);
218         mService.onUnlockUser(PRIMARY_USER_ID);
219         flushHandlerTasks();
220         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
221     }
222 
223     @Test
testSyntheticPasswordAndCredentialDoesNotPassAuthSecret()224     public void testSyntheticPasswordAndCredentialDoesNotPassAuthSecret() throws RemoteException {
225         final byte[] password = "passwordForASyntheticPassword".getBytes();
226         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
227 
228         reset(mAuthSecretService);
229         mService.onUnlockUser(PRIMARY_USER_ID);
230         flushHandlerTasks();
231         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
232     }
233 
234     @Test
testSyntheticPasswordButNoCredentialPassesAuthSecret()235     public void testSyntheticPasswordButNoCredentialPassesAuthSecret() throws RemoteException {
236         final byte[] password = "getASyntheticPassword".getBytes();
237         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
238         mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, password,
239                 PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
240 
241         reset(mAuthSecretService);
242         mService.onUnlockUser(PRIMARY_USER_ID);
243         flushHandlerTasks();
244         verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
245     }
246 
247     @Test
testTokenBasedResetPassword()248     public void testTokenBasedResetPassword() throws RemoteException {
249         final byte[] password = "password".getBytes();
250         final byte[] pattern = "123654".getBytes();
251         final byte[] token = "some-high-entropy-secure-token".getBytes();
252         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
253         final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
254 
255         assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
256         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
257         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
258         assertTrue(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
259 
260         mService.verifyCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
261                 PRIMARY_USER_ID).getResponseCode();
262         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
263         assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
264 
265         mLocalService.setLockCredentialWithToken(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
266                 handle, token, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
267 
268         // Verify DPM gets notified about new device lock
269         flushHandlerTasks();
270         final PasswordMetrics metric = PasswordMetrics.computeForCredential(
271                 LockPatternUtils.CREDENTIAL_TYPE_PATTERN, pattern);
272         verify(mDevicePolicyManager).setActivePasswordState(metric, PRIMARY_USER_ID);
273 
274         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
275                 pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID)
276                     .getResponseCode());
277         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
278     }
279 
280     @Test
testTokenBasedClearPassword()281     public void testTokenBasedClearPassword() throws RemoteException {
282         final byte[] password = "password".getBytes();
283         final byte[] pattern = "123654".getBytes();
284         final byte[] token = "some-high-entropy-secure-token".getBytes();
285         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
286         final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
287 
288         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
289         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
290 
291         mService.verifyCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
292                 0, PRIMARY_USER_ID).getResponseCode();
293         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
294 
295         mLocalService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
296                 handle, token, PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
297         flushHandlerTasks(); // flush the unlockUser() call before changing password again
298         mLocalService.setLockCredentialWithToken(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
299                 handle, token, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
300 
301         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
302                 pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID)
303                         .getResponseCode());
304         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
305     }
306 
307     @Test
testTokenBasedResetPasswordAfterCredentialChanges()308     public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException {
309         final byte[] password = "password".getBytes();
310         final byte[] pattern = "123654".getBytes();
311         final byte[] newPassword = "password".getBytes();
312         final byte[] token = "some-high-entropy-secure-token".getBytes();
313         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
314         final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
315 
316         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
317         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
318 
319         mService.verifyCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
320                 0, PRIMARY_USER_ID).getResponseCode();
321         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
322 
323         mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, password,
324                 PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID, false);
325 
326         mLocalService.setLockCredentialWithToken(newPassword,
327                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token,
328                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
329 
330         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
331                 newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
332                     .getResponseCode());
333         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
334     }
335 
336     @Test
testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration()337     public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration()
338             throws RemoteException {
339         final String token = "some-high-entropy-secure-token";
340         enableSyntheticPassword();
341         long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID, null);
342         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
343         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
344         assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
345     }
346 
347     @Test
testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration()348     public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration()
349             throws RemoteException {
350         final String token = "some-high-entropy-secure-token";
351         initializeCredentialUnderSP(null, PRIMARY_USER_ID);
352         long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID, null);
353         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
354         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
355         assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
356     }
357 
358     @Test
testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration()359     public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration()
360             throws RemoteException {
361         final byte[] token = "some-high-entropy-secure-token".getBytes();
362         final byte[] password = "password".getBytes();
363         // Set up pre-SP user password
364         disableSyntheticPassword();
365         mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
366                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
367         enableSyntheticPassword();
368 
369         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
370         // Token not activated immediately since user password exists
371         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
372         // Activate token (password gets migrated to SP at the same time)
373         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
374                 password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
375                     .getResponseCode());
376         // Verify token is activated
377         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
378     }
379 
380     @Test
testSetLockCredentialWithTokenFailsWithoutLockScreen()381     public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
382         final byte[] password = "password".getBytes();
383         final byte[] pattern = "123654".getBytes();
384         final byte[] token = "some-high-entropy-secure-token".getBytes();
385 
386         mHasSecureLockScreen = false;
387         enableSyntheticPassword();
388         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
389         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
390 
391         try {
392             mLocalService.setLockCredentialWithToken(password,
393                     LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token,
394                     PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
395             fail("An exception should have been thrown.");
396         } catch (UnsupportedOperationException e) {
397             // Success - the exception was expected.
398         }
399         assertFalse(mService.havePassword(PRIMARY_USER_ID));
400 
401         try {
402             mLocalService.setLockCredentialWithToken(pattern,
403                     LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token,
404                     PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
405             fail("An exception should have been thrown.");
406         } catch (UnsupportedOperationException e) {
407             // Success - the exception was expected.
408         }
409         assertFalse(mService.havePattern(PRIMARY_USER_ID));
410     }
411 
412     @Test
testgetHashFactorPrimaryUser()413     public void testgetHashFactorPrimaryUser() throws RemoteException {
414         final byte[] password = "password".getBytes();
415         mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
416                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
417         final byte[] hashFactor = mService.getHashFactor(password, PRIMARY_USER_ID);
418         assertNotNull(hashFactor);
419 
420         mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
421                 password, PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
422         final byte[] newHashFactor = mService.getHashFactor(null, PRIMARY_USER_ID);
423         assertNotNull(newHashFactor);
424         // Hash factor should never change after password change/removal
425         assertArrayEquals(hashFactor, newHashFactor);
426     }
427 
428     @Test
testgetHashFactorManagedProfileUnifiedChallenge()429     public void testgetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
430         final byte[] pattern = "1236".getBytes();
431         mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
432                 null, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID, false);
433         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
434         assertNotNull(mService.getHashFactor(null, MANAGED_PROFILE_USER_ID));
435     }
436 
437     @Test
testgetHashFactorManagedProfileSeparateChallenge()438     public void testgetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
439         final byte[] primaryPassword = "primary".getBytes();
440         final byte[] profilePassword = "profile".getBytes();
441         mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
442                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
443         mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
444                 PASSWORD_QUALITY_ALPHABETIC, MANAGED_PROFILE_USER_ID, false);
445         assertNotNull(mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID));
446     }
447 
448     @Test
testPasswordData_serializeDeserialize()449     public void testPasswordData_serializeDeserialize() {
450         PasswordData data = new PasswordData();
451         data.scryptN = 11;
452         data.scryptR = 22;
453         data.scryptP = 33;
454         data.passwordType = CREDENTIAL_TYPE_PASSWORD;
455         data.salt = PAYLOAD;
456         data.passwordHandle = PAYLOAD2;
457 
458         PasswordData deserialized = PasswordData.fromBytes(data.toBytes());
459 
460         assertEquals(11, deserialized.scryptN);
461         assertEquals(22, deserialized.scryptR);
462         assertEquals(33, deserialized.scryptP);
463         assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
464         assertArrayEquals(PAYLOAD, deserialized.salt);
465         assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
466     }
467 
468     @Test
testPasswordData_deserialize()469     public void testPasswordData_deserialize() {
470         // Test that we can deserialize existing PasswordData and don't inadvertently change the
471         // wire format.
472         byte[] serialized = new byte[] {
473                 0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD */
474                 11, /* scryptN */
475                 22, /* scryptR */
476                 33, /* scryptP */
477                 0, 0, 0, 5, /* salt.length */
478                 1, 2, -1, -2, 55, /* salt */
479                 0, 0, 0, 6, /* passwordHandle.length */
480                 2, 3, -2, -3, 44, 1, /* passwordHandle */
481         };
482         PasswordData deserialized = PasswordData.fromBytes(serialized);
483 
484         assertEquals(11, deserialized.scryptN);
485         assertEquals(22, deserialized.scryptR);
486         assertEquals(33, deserialized.scryptP);
487         assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
488         assertArrayEquals(PAYLOAD, deserialized.salt);
489         assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
490     }
491 
492     @Test
testGsiDisablesAuthSecret()493     public void testGsiDisablesAuthSecret() throws RemoteException {
494         mGsiService.setIsGsiRunning(true);
495 
496         final byte[] password = "testGsiDisablesAuthSecret-password".getBytes();
497 
498         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
499         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
500                 password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
501                         .getResponseCode());
502         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
503     }
504 
505     // b/62213311
506     //TODO: add non-migration work profile case, and unify/un-unify transition.
507     //TODO: test token after user resets password
508     //TODO: test token based reset after unified work challenge
509     //TODO: test clear password after unified work challenge
510 }
511