• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.recoverablekeystore;
18 
19 import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;
20 import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PASSWORD;
21 import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
22 import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
23 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
24 
25 import static com.google.common.truth.Truth.assertThat;
26 
27 import static org.junit.Assert.assertArrayEquals;
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.fail;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyInt;
32 import static org.mockito.ArgumentMatchers.anyLong;
33 import static org.mockito.ArgumentMatchers.anyString;
34 import static org.mockito.ArgumentMatchers.eq;
35 import static org.mockito.Mockito.atLeast;
36 import static org.mockito.Mockito.times;
37 import static org.mockito.Mockito.verify;
38 import static org.mockito.Mockito.when;
39 
40 import android.Manifest;
41 import android.app.KeyguardManager;
42 import android.app.PendingIntent;
43 import android.app.RemoteLockscreenValidationResult;
44 import android.app.RemoteLockscreenValidationSession;
45 import android.content.Context;
46 import android.content.Intent;
47 import android.os.Binder;
48 import android.os.ServiceSpecificException;
49 import android.os.UserHandle;
50 import android.security.keystore.KeyGenParameterSpec;
51 import android.security.keystore.KeyProperties;
52 import android.security.keystore.recovery.KeyChainProtectionParams;
53 import android.security.keystore.recovery.KeyDerivationParams;
54 import android.security.keystore.recovery.RecoveryCertPath;
55 import android.security.keystore.recovery.TrustedRootCertificates;
56 import android.security.keystore.recovery.WrappedApplicationKey;
57 import android.util.Pair;
58 
59 import androidx.test.InstrumentationRegistry;
60 import androidx.test.filters.SmallTest;
61 import androidx.test.runner.AndroidJUnit4;
62 
63 import com.android.internal.util.ArrayUtils;
64 import com.android.internal.widget.LockPatternUtils;
65 import com.android.internal.widget.LockscreenCredential;
66 import com.android.internal.widget.VerifyCredentialResponse;
67 import com.android.security.SecureBox;
68 import com.android.server.locksettings.LockSettingsService;
69 import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
70 import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager;
71 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
72 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
73 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
74 import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage;
75 
76 import com.google.common.collect.ImmutableList;
77 import com.google.common.collect.ImmutableMap;
78 
79 import org.junit.After;
80 import org.junit.Before;
81 import org.junit.Ignore;
82 import org.junit.Test;
83 import org.junit.runner.RunWith;
84 import org.mockito.Mock;
85 import org.mockito.MockitoAnnotations;
86 import org.mockito.Spy;
87 
88 import java.io.File;
89 import java.nio.charset.StandardCharsets;
90 import java.security.PublicKey;
91 import java.security.cert.CertPath;
92 import java.security.cert.CertificateFactory;
93 import java.security.cert.X509Certificate;
94 import java.util.ArrayList;
95 import java.util.Arrays;
96 import java.util.Map;
97 import java.util.Random;
98 import java.util.concurrent.ScheduledExecutorService;
99 
100 import javax.crypto.KeyGenerator;
101 import javax.crypto.SecretKey;
102 import javax.crypto.spec.SecretKeySpec;
103 
104 @SmallTest
105 @RunWith(AndroidJUnit4.class)
106 public class RecoverableKeyStoreManagerTest {
107     private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
108 
109     private static final String ROOT_CERTIFICATE_ALIAS = "";
110     private static final String DEFAULT_ROOT_CERT_ALIAS =
111             TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
112     private static final String INSECURE_CERTIFICATE_ALIAS =
113             TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS;
114     private static final String TEST_SESSION_ID = "karlin";
115     private static final byte[] TEST_PUBLIC_KEY = TestData.CERT_1_PUBLIC_KEY.getEncoded();
116     private static final byte[] TEST_SALT = getUtf8Bytes("salt");
117     private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
118     private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
119     private static final byte[] TEST_VAULT_PARAMS = new byte[] {
120         // backend_key
121         (byte) 0x04, (byte) 0x8e, (byte) 0x0c, (byte) 0x11, (byte) 0x4a, (byte) 0x79, (byte) 0x20,
122         (byte) 0x7c, (byte) 0x00, (byte) 0x4c, (byte) 0xd7, (byte) 0xe9, (byte) 0x06, (byte) 0xe2,
123         (byte) 0x58, (byte) 0x21, (byte) 0x45, (byte) 0xfa, (byte) 0x24, (byte) 0xcb, (byte) 0x07,
124         (byte) 0x66, (byte) 0xde, (byte) 0xfd, (byte) 0xf1, (byte) 0x83, (byte) 0xb4, (byte) 0x26,
125         (byte) 0x55, (byte) 0x98, (byte) 0xcb, (byte) 0xa9, (byte) 0xd5, (byte) 0x55, (byte) 0xad,
126         (byte) 0x65, (byte) 0xc5, (byte) 0xff, (byte) 0x5c, (byte) 0xfb, (byte) 0x1c, (byte) 0x4e,
127         (byte) 0x34, (byte) 0x98, (byte) 0x7e, (byte) 0x4f, (byte) 0x96, (byte) 0xa2, (byte) 0xa3,
128         (byte) 0x7e, (byte) 0xf4, (byte) 0x46, (byte) 0x52, (byte) 0x04, (byte) 0xba, (byte) 0x2a,
129         (byte) 0xb9, (byte) 0x47, (byte) 0xbb, (byte) 0xc2, (byte) 0x1e, (byte) 0xdd, (byte) 0x15,
130         (byte) 0x1a, (byte) 0xc0,
131         // counter_id
132         (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00,
133         (byte) 0x00,
134         // device_parameter
135         (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00, (byte) 0x00, (byte) 0x00,
136         (byte) 0x0,
137         // max_attempts
138         (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00};
139     private static final int TEST_GENERATION_ID = 2;
140     private static final int TEST_USER_ID = 10009;
141     private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
142     private static final byte[] RECOVERY_RESPONSE_HEADER =
143             "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
144     private static final String TEST_ALIAS = "nick";
145     private static final String TEST_ALIAS2 = "bob";
146     private static final int APPLICATION_KEY_SIZE_BYTES = 32;
147     private static final int GENERATION_ID = 1;
148     private static final byte[] NONCE = getUtf8Bytes("nonce");
149     private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial");
150     private static final byte[] KEY_METADATA_NULL = null;
151     private static final byte[] KEY_METADATA_NON_NULL = getUtf8Bytes("keymetametadata");
152     private static final String KEY_ALGORITHM = "AES";
153     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
154     private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey";
155     private static final String TEST_DEFAULT_ROOT_CERT_ALIAS = "";
156     private static final KeyChainProtectionParams TEST_PROTECTION_PARAMS =
157     new KeyChainProtectionParams.Builder()
158             .setUserSecretType(TYPE_LOCKSCREEN)
159             .setLockScreenUiFormat(UI_FORMAT_PASSWORD)
160             .setKeyDerivationParams(KeyDerivationParams.createSha256Params(TEST_SALT))
161             .setSecret(TEST_SECRET)
162             .build();
163     private static final byte[] VALID_GUESS = getUtf8Bytes("password123");
164     private static final byte[] INVALID_GUESS = getUtf8Bytes("not_password");
165     private static final byte[] GUESS_LOCKOUT = getUtf8Bytes("need_to_wait");
166     private static final int TIMEOUT_MILLIS = 30 * 1000;
167 
168     @Mock private Context mMockContext;
169     @Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
170     @Mock private KeyguardManager mKeyguardManager;
171     @Mock private PlatformKeyManager mPlatformKeyManager;
172     @Mock private ApplicationKeyStorage mApplicationKeyStorage;
173     @Mock private CleanupManager mCleanupManager;
174     @Mock private ScheduledExecutorService mExecutorService;
175     @Mock private LockSettingsService mLockSettingsService;
176     @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
177 
178     private int mUserId;
179     private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
180     private File mDatabaseFile;
181     private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
182     private RecoverySessionStorage mRecoverySessionStorage;
183     private RecoverySnapshotStorage mRecoverySnapshotStorage;
184     private PlatformEncryptionKey mPlatformEncryptionKey;
185     private RemoteLockscreenValidationSessionStorage mRemoteLockscreenValidationSessionStorage;
186 
187     @Before
setUp()188     public void setUp() throws Exception {
189         MockitoAnnotations.initMocks(this);
190 
191         Context context = InstrumentationRegistry.getTargetContext();
192         mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
193         mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
194 
195         mUserId = UserHandle.getCallingUserId();
196         mRecoverySessionStorage = new RecoverySessionStorage();
197         mRemoteLockscreenValidationSessionStorage = new RemoteLockscreenValidationSessionStorage();
198 
199         when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
200         when(mMockContext.getSystemServiceName(any())).thenReturn("test");
201         when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
202         when(mKeyguardManager.isDeviceSecure(TEST_USER_ID)).thenReturn(true);
203 
204         mPlatformEncryptionKey =
205                 new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey());
206         when(mPlatformKeyManager.getEncryptKey(anyInt())).thenReturn(mPlatformEncryptionKey);
207 
208         mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
209                 mMockContext,
210                 mRecoverableKeyStoreDb,
211                 mRecoverySessionStorage,
212                 mExecutorService,
213                 mRecoverySnapshotStorage,
214                 mMockListenersStorage,
215                 mPlatformKeyManager,
216                 mApplicationKeyStorage,
217                 mTestOnlyInsecureCertificateHelper,
218                 mCleanupManager,
219                 mRemoteLockscreenValidationSessionStorage);
220         when(mLockSettingsService.verifyCredential(
221                 any(LockscreenCredential.class), anyInt(), anyInt())).thenAnswer(args -> {
222                     LockscreenCredential argument = (LockscreenCredential) args.getArguments()[0];
223                     if (Arrays.equals(argument.getCredential(), VALID_GUESS)) {
224                         return VerifyCredentialResponse.OK;
225                     } else if (Arrays.equals(argument.getCredential(), INVALID_GUESS)) {
226                         return VerifyCredentialResponse.ERROR;
227                     } else return VerifyCredentialResponse.fromTimeout(TIMEOUT_MILLIS);
228                 });
229     }
230 
231     @After
tearDown()232     public void tearDown() {
233         if (mRemoteLockscreenValidationSessionStorage != null) {
234             mRemoteLockscreenValidationSessionStorage.finishSession(mUserId);
235         }
236         if (mRecoverableKeyStoreDb != null) {
237             mRecoverableKeyStoreDb.close();
238         }
239         if (mDatabaseFile != null) {
240             mDatabaseFile.delete();
241         }
242     }
243 
244     @Test
importKey_storesTheKey()245     public void importKey_storesTheKey() throws Exception {
246         int uid = Binder.getCallingUid();
247         int userId = UserHandle.getCallingUserId();
248         byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES);
249 
250         mRecoverableKeyStoreManager.importKey(TEST_ALIAS, keyMaterial);
251 
252         assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
253         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
254     }
255 
256     @Test
importKey_throwsIfInvalidLength()257     public void importKey_throwsIfInvalidLength() throws Exception {
258         byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES - 1);
259         try {
260             mRecoverableKeyStoreManager.importKey(TEST_ALIAS, keyMaterial);
261             fail("should have thrown");
262         } catch (ServiceSpecificException e) {
263             assertThat(e.getMessage()).contains("not contain 256 bits");
264         }
265     }
266 
267     @Test
importKey_throwsIfNullKey()268     public void importKey_throwsIfNullKey() throws Exception {
269         try {
270             mRecoverableKeyStoreManager.importKey(TEST_ALIAS, /*keyBytes=*/ null);
271             fail("should have thrown");
272         } catch (NullPointerException e) {
273             assertThat(e.getMessage()).contains("is null");
274         }
275     }
276 
277     @Test
importKeyWithMetadata_nullMetadata_storesTheKey()278     public void importKeyWithMetadata_nullMetadata_storesTheKey() throws Exception {
279         int uid = Binder.getCallingUid();
280         int userId = UserHandle.getCallingUserId();
281         byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES);
282 
283         mRecoverableKeyStoreManager.importKeyWithMetadata(
284                 TEST_ALIAS, keyMaterial, KEY_METADATA_NULL);
285 
286         assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
287         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
288     }
289 
290     @Test
importKeyWithMetadata_nonNullMetadata_storesTheKey()291     public void importKeyWithMetadata_nonNullMetadata_storesTheKey() throws Exception {
292         int uid = Binder.getCallingUid();
293         int userId = UserHandle.getCallingUserId();
294         byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES);
295 
296         mRecoverableKeyStoreManager.importKeyWithMetadata(
297                 TEST_ALIAS, keyMaterial, KEY_METADATA_NON_NULL);
298 
299         assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
300         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
301     }
302 
303     @Test
importKeyWithMetadata_throwsIfInvalidLength()304     public void importKeyWithMetadata_throwsIfInvalidLength() throws Exception {
305         byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES - 1);
306         try {
307             mRecoverableKeyStoreManager.importKeyWithMetadata(
308                     TEST_ALIAS, keyMaterial, KEY_METADATA_NON_NULL);
309             fail("should have thrown");
310         } catch (ServiceSpecificException e) {
311             assertThat(e.getMessage()).contains("not contain 256 bits");
312         }
313     }
314 
315     @Test
importKeyWithMetadata_throwsIfNullKey()316     public void importKeyWithMetadata_throwsIfNullKey() throws Exception {
317         try {
318             mRecoverableKeyStoreManager.importKeyWithMetadata(
319                     TEST_ALIAS, /*keyBytes=*/ null, KEY_METADATA_NON_NULL);
320             fail("should have thrown");
321         } catch (NullPointerException e) {
322             assertThat(e.getMessage()).contains("is null");
323         }
324     }
325 
326     @Test
generateKeyWithMetadata_nullMetadata_storesTheKey()327     public void generateKeyWithMetadata_nullMetadata_storesTheKey() throws Exception {
328         int uid = Binder.getCallingUid();
329         int userId = UserHandle.getCallingUserId();
330 
331         mRecoverableKeyStoreManager.generateKeyWithMetadata(TEST_ALIAS, KEY_METADATA_NULL);
332 
333         assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
334         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
335     }
336 
337     @Test
generateKeyWithMetadata_nonNullMetadata_storesTheKey()338     public void generateKeyWithMetadata_nonNullMetadata_storesTheKey() throws Exception {
339         int uid = Binder.getCallingUid();
340         int userId = UserHandle.getCallingUserId();
341 
342         mRecoverableKeyStoreManager.generateKeyWithMetadata(TEST_ALIAS, KEY_METADATA_NON_NULL);
343 
344         assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
345         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
346     }
347 
348     @Test
removeKey_removesAKey()349     public void removeKey_removesAKey() throws Exception {
350         int uid = Binder.getCallingUid();
351         mRecoverableKeyStoreManager.generateKey(TEST_ALIAS);
352 
353         mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
354 
355         assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNull();
356     }
357 
358     @Test
removeKey_updatesShouldCreateSnapshot()359     public void removeKey_updatesShouldCreateSnapshot() throws Exception {
360         int uid = Binder.getCallingUid();
361         int userId = UserHandle.getCallingUserId();
362         mRecoverableKeyStoreManager.generateKey(TEST_ALIAS);
363         // Pretend that key was synced
364         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
365 
366         mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
367 
368         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
369     }
370 
371     @Test
removeKey_failureDoesNotUpdateShouldCreateSnapshot()372     public void removeKey_failureDoesNotUpdateShouldCreateSnapshot() throws Exception {
373         int uid = Binder.getCallingUid();
374         int userId = UserHandle.getCallingUserId();
375         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
376         // Key did not exist
377         mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
378 
379         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
380     }
381 
382     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
383     @Test
initRecoveryService_succeedsWithCertFile()384     public void initRecoveryService_succeedsWithCertFile() throws Exception {
385         int uid = Binder.getCallingUid();
386         int userId = UserHandle.getCallingUserId();
387         long certSerial = 1000L;
388         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
389 
390         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
391                 TestData.getCertXmlWithSerial(certSerial));
392 
393         verify(mTestOnlyInsecureCertificateHelper, atLeast(1))
394                 .getDefaultCertificateAliasIfEmpty(ROOT_CERTIFICATE_ALIAS);
395 
396         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
397         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
398                 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1);
399         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
400                 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial);
401         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
402     }
403 
404     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
405     @Test
initRecoveryService_updatesShouldCreatesnapshotOnCertUpdate()406     public void initRecoveryService_updatesShouldCreatesnapshotOnCertUpdate() throws Exception {
407         int uid = Binder.getCallingUid();
408         int userId = UserHandle.getCallingUserId();
409         long certSerial = 1000L;
410         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
411 
412         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
413                 TestData.getCertXmlWithSerial(certSerial));
414 
415         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
416 
417         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
418                 TestData.getCertXmlWithSerial(certSerial + 1));
419 
420         // Since there were no recoverable keys, new snapshot will not be created.
421         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
422 
423         generateKeyAndSimulateSync(userId, uid, 10);
424 
425         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
426                 TestData.getCertXmlWithSerial(certSerial + 2));
427 
428         // Since there were a recoverable key, new serial number triggers snapshot creation
429         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
430     }
431 
432     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
433     @Test
initRecoveryService_triesToFilterRootAlias()434     public void initRecoveryService_triesToFilterRootAlias() throws Exception {
435         int uid = Binder.getCallingUid();
436         int userId = UserHandle.getCallingUserId();
437         long certSerial = 1000L;
438         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
439 
440         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
441                 TestData.getCertXmlWithSerial(certSerial));
442 
443         verify(mTestOnlyInsecureCertificateHelper, atLeast(1))
444                 .getDefaultCertificateAliasIfEmpty(eq(ROOT_CERTIFICATE_ALIAS));
445 
446         verify(mTestOnlyInsecureCertificateHelper, atLeast(1))
447                 .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS));
448 
449         String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid);
450         assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS);
451 
452     }
453 
454     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
455     @Test
initRecoveryService_usesProdCertificateForEmptyRootAlias()456     public void initRecoveryService_usesProdCertificateForEmptyRootAlias() throws Exception {
457         int uid = Binder.getCallingUid();
458         int userId = UserHandle.getCallingUserId();
459         long certSerial = 1000L;
460         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
461 
462         mRecoverableKeyStoreManager.initRecoveryService(/*rootCertificateAlias=*/ "",
463                 TestData.getCertXmlWithSerial(certSerial));
464 
465         verify(mTestOnlyInsecureCertificateHelper, atLeast(1))
466                 .getDefaultCertificateAliasIfEmpty(eq(""));
467 
468         verify(mTestOnlyInsecureCertificateHelper, atLeast(1))
469                 .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS));
470 
471         String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid);
472         assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS);
473     }
474 
475     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
476     @Test
initRecoveryService_usesProdCertificateForNullRootAlias()477     public void initRecoveryService_usesProdCertificateForNullRootAlias() throws Exception {
478         int uid = Binder.getCallingUid();
479         int userId = UserHandle.getCallingUserId();
480         long certSerial = 1000L;
481         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
482 
483         mRecoverableKeyStoreManager.initRecoveryService(/*rootCertificateAlias=*/ null,
484                 TestData.getCertXmlWithSerial(certSerial));
485 
486         verify(mTestOnlyInsecureCertificateHelper, atLeast(1))
487                 .getDefaultCertificateAliasIfEmpty(null);
488 
489         verify(mTestOnlyInsecureCertificateHelper, atLeast(1))
490                 .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS));
491 
492         String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid);
493         assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS);
494     }
495 
496     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
497     @Test
initRecoveryService_regeneratesCounterId()498     public void initRecoveryService_regeneratesCounterId() throws Exception {
499         int uid = Binder.getCallingUid();
500         int userId = UserHandle.getCallingUserId();
501         long certSerial = 1000L;
502 
503         Long counterId0 = mRecoverableKeyStoreDb.getCounterId(userId, uid);
504         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
505                 TestData.getCertXmlWithSerial(certSerial));
506         Long counterId1 = mRecoverableKeyStoreDb.getCounterId(userId, uid);
507         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
508                 TestData.getCertXmlWithSerial(certSerial + 1));
509         Long counterId2 = mRecoverableKeyStoreDb.getCounterId(userId, uid);
510 
511         assertThat(!counterId1.equals(counterId0) || !counterId2.equals(counterId1)).isTrue();
512     }
513 
514     @Test
initRecoveryService_throwsIfInvalidCert()515     public void initRecoveryService_throwsIfInvalidCert() throws Exception {
516         byte[] modifiedCertXml = TestData.getCertXml();
517         modifiedCertXml[modifiedCertXml.length - 50] ^= 1;  // Flip a bit in the certificate
518         try {
519             mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
520                     modifiedCertXml);
521             fail("should have thrown");
522         } catch (ServiceSpecificException e) {
523             assertThat(e.errorCode).isEqualTo(ERROR_INVALID_CERTIFICATE);
524         }
525     }
526 
527     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
528     @Test
initRecoveryService_updatesWithLargerSerial()529     public void initRecoveryService_updatesWithLargerSerial() throws Exception {
530         int uid = Binder.getCallingUid();
531         int userId = UserHandle.getCallingUserId();
532         long certSerial = 1000L;
533 
534         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
535                 TestData.getCertXmlWithSerial(certSerial));
536         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
537                 TestData.getCertXmlWithSerial(certSerial + 1));
538 
539         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
540                 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial + 1);
541         // There were no keys.
542         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
543     }
544 
545     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
546     @Test
initRecoveryService_throwsExceptionOnSmallerSerial()547     public void initRecoveryService_throwsExceptionOnSmallerSerial() throws Exception {
548         long certSerial = 1000L;
549 
550         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
551                 TestData.getCertXmlWithSerial(certSerial));
552         try {
553             mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
554                     TestData.getCertXmlWithSerial(certSerial - 1));
555             fail();
556         } catch (ServiceSpecificException e) {
557             assertThat(e.errorCode).isEqualTo(ERROR_DOWNGRADE_CERTIFICATE);
558         }
559     }
560 
561     @Ignore("Causing breakages so ignoring to resolve, b/231667368")
562     @Test
initRecoveryService_alwaysUpdatesCertsWhenTestRootCertIsUsed()563     public void initRecoveryService_alwaysUpdatesCertsWhenTestRootCertIsUsed() throws Exception {
564         int uid = Binder.getCallingUid();
565         int userId = UserHandle.getCallingUserId();
566         int certSerial = 3333;
567 
568         String testRootCertAlias = TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS;
569 
570         mRecoverableKeyStoreManager.initRecoveryService(testRootCertAlias,
571                 TestData.getInsecureCertXmlBytesWithEndpoint1(certSerial));
572         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
573                 testRootCertAlias)).isEqualTo(certSerial);
574         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
575                 testRootCertAlias)).isEqualTo(TestData.getInsecureCertPathForEndpoint1());
576 
577         mRecoverableKeyStoreManager.initRecoveryService(testRootCertAlias,
578                 TestData.getInsecureCertXmlBytesWithEndpoint2(certSerial - 1));
579         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
580                 testRootCertAlias)).isEqualTo(certSerial - 1);
581         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
582                 testRootCertAlias)).isEqualTo(TestData.getInsecureCertPathForEndpoint2());
583     }
584 
585     @Ignore("Causing breakages so ignoring to resolve, b/231667368")
586     @Test
initRecoveryService_updatesCertsIndependentlyForDifferentRoots()587     public void initRecoveryService_updatesCertsIndependentlyForDifferentRoots() throws Exception {
588         int uid = Binder.getCallingUid();
589         int userId = UserHandle.getCallingUserId();
590 
591         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
592                 TestData.getCertXmlWithSerial(1111L));
593         mRecoverableKeyStoreManager.initRecoveryService(
594                 TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS,
595                 TestData.getInsecureCertXmlBytesWithEndpoint1(2222));
596 
597         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
598                 ROOT_CERTIFICATE_ALIAS)).isEqualTo(1111L);
599         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
600                 TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS)).isEqualTo(2222L);
601 
602         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
603                 ROOT_CERTIFICATE_ALIAS)).isEqualTo(TestData.CERT_PATH_1);
604         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
605                 TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS)).isEqualTo(
606                         TestData.getInsecureCertPathForEndpoint1());
607     }
608 
609     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
610     @Test
initRecoveryService_ignoresTheSameSerial()611     public void initRecoveryService_ignoresTheSameSerial() throws Exception {
612         int uid = Binder.getCallingUid();
613         int userId = UserHandle.getCallingUserId();
614         long certSerial = 1000L;
615 
616         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
617                 TestData.getCertXmlWithSerial(certSerial));
618 
619         generateKeyAndSimulateSync(userId, uid, 10);
620 
621         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
622                 TestData.getCertXmlWithSerial(certSerial));
623 
624         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
625     }
626 
627     @Test
initRecoveryService_throwsIfRawPublicKey()628     public void initRecoveryService_throwsIfRawPublicKey() throws Exception {
629         int uid = Binder.getCallingUid();
630         int userId = UserHandle.getCallingUserId();
631         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
632 
633         try {
634             mRecoverableKeyStoreManager
635                     .initRecoveryService(ROOT_CERTIFICATE_ALIAS, TEST_PUBLIC_KEY);
636             fail("should have thrown");
637         } catch (ServiceSpecificException e) {
638             assertThat(e.errorCode).isEqualTo(ERROR_BAD_CERTIFICATE_FORMAT);
639         }
640 
641         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
642         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
643                 DEFAULT_ROOT_CERT_ALIAS)).isNull();
644         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
645                 DEFAULT_ROOT_CERT_ALIAS)).isNull();
646         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
647     }
648 
649     @Test
initRecoveryService_throwsIfUnknownRootCertAlias()650     public void initRecoveryService_throwsIfUnknownRootCertAlias() throws Exception {
651         try {
652             mRecoverableKeyStoreManager.initRecoveryService(
653                     "unknown-root-cert-alias", TestData.getCertXml());
654             fail("should have thrown");
655         } catch (ServiceSpecificException e) {
656             assertThat(e.errorCode).isEqualTo(ERROR_INVALID_CERTIFICATE);
657         }
658     }
659 
660     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
661     @Test
initRecoveryServiceWithSigFile_succeeds()662     public void initRecoveryServiceWithSigFile_succeeds() throws Exception {
663         int uid = Binder.getCallingUid();
664         int userId = UserHandle.getCallingUserId();
665         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
666 
667         mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
668                 ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(), TestData.getSigXml());
669 
670         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
671         assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
672                 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1);
673         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
674     }
675 
676     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
677     @Test
initRecoveryServiceWithSigFile_usesProdCertificateForNullRootAlias()678     public void initRecoveryServiceWithSigFile_usesProdCertificateForNullRootAlias()
679             throws Exception {
680         int uid = Binder.getCallingUid();
681         int userId = UserHandle.getCallingUserId();
682         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
683 
684         mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
685                 /*rootCertificateAlias=*/null, TestData.getCertXml(), TestData.getSigXml());
686 
687         verify(mTestOnlyInsecureCertificateHelper, atLeast(1))
688                 .getDefaultCertificateAliasIfEmpty(null);
689 
690         verify(mTestOnlyInsecureCertificateHelper, atLeast(1))
691                 .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS));
692     }
693 
694     @Test
initRecoveryServiceWithSigFile_throwsIfNullCertFile()695     public void initRecoveryServiceWithSigFile_throwsIfNullCertFile() throws Exception {
696         try {
697             mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
698                     ROOT_CERTIFICATE_ALIAS, /*recoveryServiceCertFile=*/ null,
699                     TestData.getSigXml());
700             fail("should have thrown");
701         } catch (NullPointerException e) {
702             assertThat(e.getMessage()).contains("is null");
703         }
704     }
705 
706     @Test
initRecoveryServiceWithSigFile_throwsIfNullSigFile()707     public void initRecoveryServiceWithSigFile_throwsIfNullSigFile() throws Exception {
708         try {
709             mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
710                     ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(),
711                     /*recoveryServiceSigFile=*/ null);
712             fail("should have thrown");
713         } catch (NullPointerException e) {
714             assertThat(e.getMessage()).contains("is null");
715         }
716     }
717 
718     @Test
initRecoveryServiceWithSigFile_throwsIfWrongSigFileFormat()719     public void initRecoveryServiceWithSigFile_throwsIfWrongSigFileFormat() throws Exception {
720         try {
721             mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
722                     ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(),
723                     getUtf8Bytes("wrong-sig-file-format"));
724             fail("should have thrown");
725         } catch (ServiceSpecificException e) {
726             assertThat(e.errorCode).isEqualTo(ERROR_BAD_CERTIFICATE_FORMAT);
727         }
728     }
729 
730     @Test
initRecoveryServiceWithSigFile_throwsIfTestAliasUsedWithProdCert()731     public void initRecoveryServiceWithSigFile_throwsIfTestAliasUsedWithProdCert()
732             throws Exception {
733         try {
734         mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
735                 INSECURE_CERTIFICATE_ALIAS, TestData.getCertXml(), TestData.getSigXml());
736             fail("should have thrown");
737         } catch (ServiceSpecificException e) {
738             assertThat(e.errorCode).isEqualTo(ERROR_INVALID_CERTIFICATE);
739         }
740     }
741 
742     @Test
initRecoveryServiceWithSigFile_throwsIfInvalidFileSignature()743     public void initRecoveryServiceWithSigFile_throwsIfInvalidFileSignature() throws Exception {
744         byte[] modifiedCertXml = TestData.getCertXml();
745         modifiedCertXml[modifiedCertXml.length - 1] = 0;  // Change the last new line char to a zero
746         try {
747             mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
748                     ROOT_CERTIFICATE_ALIAS, modifiedCertXml, TestData.getSigXml());
749             fail("should have thrown");
750         } catch (ServiceSpecificException e) {
751             assertThat(e.getMessage()).contains("is invalid");
752         }
753     }
754 
755     @Test
startRecoverySession_checksPermissionFirst()756     public void startRecoverySession_checksPermissionFirst() throws Exception {
757         mRecoverableKeyStoreManager.startRecoverySession(
758                 TEST_SESSION_ID,
759                 TEST_PUBLIC_KEY,
760                 TEST_VAULT_PARAMS,
761                 TEST_VAULT_CHALLENGE,
762                 ImmutableList.of(TEST_PROTECTION_PARAMS));
763 
764         verify(mMockContext, times(1))
765                 .enforceCallingOrSelfPermission(
766                         eq(Manifest.permission.RECOVER_KEYSTORE), any());
767     }
768 
769     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
770     @Test
startRecoverySessionWithCertPath_storesTheSessionInfo()771     public void startRecoverySessionWithCertPath_storesTheSessionInfo() throws Exception {
772         mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
773                 TEST_SESSION_ID,
774                 TEST_DEFAULT_ROOT_CERT_ALIAS,
775                 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
776                 TEST_VAULT_PARAMS,
777                 TEST_VAULT_CHALLENGE,
778                 ImmutableList.of(TEST_PROTECTION_PARAMS));
779 
780         assertEquals(1, mRecoverySessionStorage.size());
781         RecoverySessionStorage.Entry entry =
782                 mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID);
783         assertArrayEquals(TEST_SECRET, entry.getLskfHash());
784         assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
785     }
786 
787     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
788     @Test
startRecoverySessionWithCertPath_checksPermissionFirst()789     public void startRecoverySessionWithCertPath_checksPermissionFirst() throws Exception {
790         mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
791                 TEST_SESSION_ID,
792                 TEST_DEFAULT_ROOT_CERT_ALIAS,
793                 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
794                 TEST_VAULT_PARAMS,
795                 TEST_VAULT_CHALLENGE,
796                 ImmutableList.of(TEST_PROTECTION_PARAMS));
797 
798         verify(mMockContext, times(2))
799                 .enforceCallingOrSelfPermission(
800                         eq(Manifest.permission.RECOVER_KEYSTORE), any());
801     }
802 
803     @Test
startRecoverySession_storesTheSessionInfo()804     public void startRecoverySession_storesTheSessionInfo() throws Exception {
805         mRecoverableKeyStoreManager.startRecoverySession(
806                 TEST_SESSION_ID,
807                 TEST_PUBLIC_KEY,
808                 TEST_VAULT_PARAMS,
809                 TEST_VAULT_CHALLENGE,
810                 ImmutableList.of(TEST_PROTECTION_PARAMS));
811 
812         assertEquals(1, mRecoverySessionStorage.size());
813         RecoverySessionStorage.Entry entry =
814                 mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID);
815         assertArrayEquals(TEST_SECRET, entry.getLskfHash());
816         assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
817     }
818 
819     @Test
closeSession_closesASession()820     public void closeSession_closesASession() throws Exception {
821         mRecoverableKeyStoreManager.startRecoverySession(
822                 TEST_SESSION_ID,
823                 TEST_PUBLIC_KEY,
824                 TEST_VAULT_PARAMS,
825                 TEST_VAULT_CHALLENGE,
826                 ImmutableList.of(TEST_PROTECTION_PARAMS));
827 
828         mRecoverableKeyStoreManager.closeSession(TEST_SESSION_ID);
829 
830         assertEquals(0, mRecoverySessionStorage.size());
831     }
832 
833     @Test
closeSession_doesNotCloseUnrelatedSessions()834     public void closeSession_doesNotCloseUnrelatedSessions() throws Exception {
835         mRecoverableKeyStoreManager.startRecoverySession(
836                 TEST_SESSION_ID,
837                 TEST_PUBLIC_KEY,
838                 TEST_VAULT_PARAMS,
839                 TEST_VAULT_CHALLENGE,
840                 ImmutableList.of(TEST_PROTECTION_PARAMS));
841 
842         mRecoverableKeyStoreManager.closeSession("some random session");
843 
844         assertEquals(1, mRecoverySessionStorage.size());
845     }
846 
847     @Test
closeSession_throwsIfNullSession()848     public void closeSession_throwsIfNullSession() throws Exception {
849         try {
850             mRecoverableKeyStoreManager.closeSession(/*sessionId=*/ null);
851             fail("should have thrown");
852         } catch (NullPointerException e) {
853             assertThat(e.getMessage()).contains("invalid");
854         }
855     }
856 
857     @Test
startRecoverySession_throwsIfBadNumberOfSecrets()858     public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception {
859         try {
860             mRecoverableKeyStoreManager.startRecoverySession(
861                     TEST_SESSION_ID,
862                     TEST_PUBLIC_KEY,
863                     TEST_VAULT_PARAMS,
864                     TEST_VAULT_CHALLENGE,
865                     ImmutableList.of());
866             fail("should have thrown");
867         } catch (UnsupportedOperationException e) {
868             assertThat(e.getMessage()).startsWith(
869                     "Only a single KeyChainProtectionParams is supported");
870         }
871     }
872 
873     @Test
startRecoverySession_throwsIfPublicKeysMismatch()874     public void startRecoverySession_throwsIfPublicKeysMismatch() throws Exception {
875         byte[] vaultParams = TEST_VAULT_PARAMS.clone();
876         vaultParams[1] ^= (byte) 1;  // Flip 1 bit
877 
878         try {
879             mRecoverableKeyStoreManager.startRecoverySession(
880                     TEST_SESSION_ID,
881                     TEST_PUBLIC_KEY,
882                     vaultParams,
883                     TEST_VAULT_CHALLENGE,
884                     ImmutableList.of(TEST_PROTECTION_PARAMS));
885             fail("should have thrown");
886         } catch (ServiceSpecificException e) {
887             assertThat(e.getMessage()).contains("do not match");
888         }
889     }
890 
891     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
892     @Test
startRecoverySessionWithCertPath_throwsIfBadNumberOfSecrets()893     public void startRecoverySessionWithCertPath_throwsIfBadNumberOfSecrets() throws Exception {
894         try {
895             mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
896                     TEST_SESSION_ID,
897                     TEST_DEFAULT_ROOT_CERT_ALIAS,
898                     RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
899                     TEST_VAULT_PARAMS,
900                     TEST_VAULT_CHALLENGE,
901                     ImmutableList.of());
902             fail("should have thrown");
903         } catch (UnsupportedOperationException e) {
904             assertThat(e.getMessage()).startsWith(
905                     "Only a single KeyChainProtectionParams is supported");
906         }
907     }
908 
909     @Ignore("Causing breakages so ignoring to resolve, b/281583079")
910     @Test
startRecoverySessionWithCertPath_throwsIfPublicKeysMismatch()911     public void startRecoverySessionWithCertPath_throwsIfPublicKeysMismatch() throws Exception {
912         byte[] vaultParams = TEST_VAULT_PARAMS.clone();
913         vaultParams[1] ^= (byte) 1;  // Flip 1 bit
914         try {
915             mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
916                     TEST_SESSION_ID,
917                     TEST_DEFAULT_ROOT_CERT_ALIAS,
918                     RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
919                     vaultParams,
920                     TEST_VAULT_CHALLENGE,
921                     ImmutableList.of(TEST_PROTECTION_PARAMS));
922             fail("should have thrown");
923         } catch (ServiceSpecificException e) {
924             assertThat(e.getMessage()).contains("do not match");
925         }
926     }
927 
928     @Test
startRecoverySessionWithCertPath_throwsIfEmptyCertPath()929     public void startRecoverySessionWithCertPath_throwsIfEmptyCertPath() throws Exception {
930         CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
931         CertPath emptyCertPath = certFactory.generateCertPath(new ArrayList<X509Certificate>());
932         try {
933             mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
934                     TEST_SESSION_ID,
935                     TEST_DEFAULT_ROOT_CERT_ALIAS,
936                     RecoveryCertPath.createRecoveryCertPath(emptyCertPath),
937                     TEST_VAULT_PARAMS,
938                     TEST_VAULT_CHALLENGE,
939                     ImmutableList.of(TEST_PROTECTION_PARAMS));
940             fail("should have thrown");
941         } catch (ServiceSpecificException e) {
942             assertThat(e.getMessage()).contains("empty");
943         }
944     }
945 
946     @Test
startRecoverySessionWithCertPath_throwsIfInvalidCertPath()947     public void startRecoverySessionWithCertPath_throwsIfInvalidCertPath() throws Exception {
948         CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
949         CertPath shortCertPath = certFactory.generateCertPath(
950                 TestData.CERT_PATH_1.getCertificates()
951                         .subList(0, TestData.CERT_PATH_1.getCertificates().size() - 1));
952         try {
953             mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
954                     TEST_SESSION_ID,
955                     TEST_DEFAULT_ROOT_CERT_ALIAS,
956                     RecoveryCertPath.createRecoveryCertPath(shortCertPath),
957                     TEST_VAULT_PARAMS,
958                     TEST_VAULT_CHALLENGE,
959                     ImmutableList.of(TEST_PROTECTION_PARAMS));
960             fail("should have thrown");
961         } catch (ServiceSpecificException e) {
962             // expected
963         }
964     }
965 
966     @Test
recoverKeyChainSnapshot_throwsIfNoSessionIsPresent()967     public void recoverKeyChainSnapshot_throwsIfNoSessionIsPresent() throws Exception {
968         try {
969             WrappedApplicationKey applicationKey = new WrappedApplicationKey.Builder()
970                 .setAlias(TEST_ALIAS)
971                 .setEncryptedKeyMaterial(randomBytes(32))
972                 .build();
973             mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
974                     TEST_SESSION_ID,
975                     /*recoveryKeyBlob=*/ randomBytes(32),
976                     /*applicationKeys=*/ ImmutableList.of(applicationKey));
977             fail("should have thrown");
978         } catch (ServiceSpecificException e) {
979             // expected
980         }
981     }
982 
983     @Test
recoverKeyChainSnapshot_throwsIfRecoveryClaimCannotBeDecrypted()984     public void recoverKeyChainSnapshot_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception {
985         mRecoverableKeyStoreManager.startRecoverySession(
986                 TEST_SESSION_ID,
987                 TEST_PUBLIC_KEY,
988                 TEST_VAULT_PARAMS,
989                 TEST_VAULT_CHALLENGE,
990                 ImmutableList.of(TEST_PROTECTION_PARAMS));
991 
992         try {
993             mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
994                     TEST_SESSION_ID,
995                     /*encryptedRecoveryKey=*/ randomBytes(60),
996                     /*applicationKeys=*/ ImmutableList.of());
997             fail("should have thrown");
998         } catch (ServiceSpecificException e) {
999             assertThat(e.getMessage()).startsWith("Failed to decrypt recovery key");
1000         }
1001     }
1002 
1003     @Test
recoverKeyChainSnapshot_throwsIfFailedToDecryptAllApplicationKeys()1004     public void recoverKeyChainSnapshot_throwsIfFailedToDecryptAllApplicationKeys()
1005             throws Exception {
1006         mRecoverableKeyStoreManager.startRecoverySession(
1007                 TEST_SESSION_ID,
1008                 TEST_PUBLIC_KEY,
1009                 TEST_VAULT_PARAMS,
1010                 TEST_VAULT_CHALLENGE,
1011                 ImmutableList.of(TEST_PROTECTION_PARAMS));
1012         byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
1013                 .getKeyClaimant();
1014         SecretKey recoveryKey = randomRecoveryKey();
1015         byte[] encryptedClaimResponse = encryptClaimResponse(
1016                 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
1017         WrappedApplicationKey badApplicationKey = new WrappedApplicationKey.Builder()
1018                 .setAlias(TEST_ALIAS)
1019                 .setEncryptedKeyMaterial(
1020                             encryptedApplicationKey(randomRecoveryKey(), randomBytes(32)))
1021                 .build();
1022         try {
1023             mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
1024                     TEST_SESSION_ID,
1025                     /*encryptedRecoveryKey=*/ encryptedClaimResponse,
1026                     /*applicationKeys=*/ ImmutableList.of(badApplicationKey));
1027             fail("should have thrown");
1028         } catch (ServiceSpecificException e) {
1029             assertThat(e.getMessage()).startsWith("Failed to recover any of the application keys");
1030         }
1031     }
1032 
1033     @Test
recoverKeyChainSnapshot_doesNotThrowIfNoApplicationKeysToBeDecrypted()1034     public void recoverKeyChainSnapshot_doesNotThrowIfNoApplicationKeysToBeDecrypted()
1035             throws Exception {
1036         mRecoverableKeyStoreManager.startRecoverySession(
1037                 TEST_SESSION_ID,
1038                 TEST_PUBLIC_KEY,
1039                 TEST_VAULT_PARAMS,
1040                 TEST_VAULT_CHALLENGE,
1041                 ImmutableList.of(TEST_PROTECTION_PARAMS));
1042         byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
1043                 .getKeyClaimant();
1044         SecretKey recoveryKey = randomRecoveryKey();
1045         byte[] encryptedClaimResponse = encryptClaimResponse(
1046                 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
1047 
1048         mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
1049                 TEST_SESSION_ID,
1050                 /*encryptedRecoveryKey=*/ encryptedClaimResponse,
1051                 /*applicationKeys=*/ ImmutableList.of());
1052     }
1053 
1054     @Test
recoverKeyChainSnapshot_returnsDecryptedKeys()1055     public void recoverKeyChainSnapshot_returnsDecryptedKeys() throws Exception {
1056         mRecoverableKeyStoreManager.startRecoverySession(
1057                 TEST_SESSION_ID,
1058                 TEST_PUBLIC_KEY,
1059                 TEST_VAULT_PARAMS,
1060                 TEST_VAULT_CHALLENGE,
1061                 ImmutableList.of(TEST_PROTECTION_PARAMS));
1062         byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
1063                 .getKeyClaimant();
1064         SecretKey recoveryKey = randomRecoveryKey();
1065         byte[] encryptedClaimResponse = encryptClaimResponse(
1066                 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
1067         byte[] applicationKeyBytes = randomBytes(32);
1068         WrappedApplicationKey applicationKey = new WrappedApplicationKey.Builder()
1069                     .setAlias(TEST_ALIAS)
1070                     .setEncryptedKeyMaterial(
1071                             encryptedApplicationKey(recoveryKey, applicationKeyBytes))
1072                     .build();
1073 
1074         Map<String, String> recoveredKeys = mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
1075                 TEST_SESSION_ID,
1076                 encryptedClaimResponse,
1077                 ImmutableList.of(applicationKey));
1078 
1079         assertThat(recoveredKeys).hasSize(1);
1080         assertThat(recoveredKeys).containsKey(TEST_ALIAS);
1081     }
1082 
1083     @Test
recoverKeyChainSnapshot_worksOnOtherApplicationKeysIfOneDecryptionFails()1084     public void recoverKeyChainSnapshot_worksOnOtherApplicationKeysIfOneDecryptionFails()
1085             throws Exception {
1086         mRecoverableKeyStoreManager.startRecoverySession(
1087                 TEST_SESSION_ID,
1088                 TEST_PUBLIC_KEY,
1089                 TEST_VAULT_PARAMS,
1090                 TEST_VAULT_CHALLENGE,
1091                 ImmutableList.of(TEST_PROTECTION_PARAMS));
1092         byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
1093                 .getKeyClaimant();
1094         SecretKey recoveryKey = randomRecoveryKey();
1095         byte[] encryptedClaimResponse = encryptClaimResponse(
1096                 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
1097 
1098         byte[] applicationKeyBytes1 = randomBytes(32);
1099         byte[] applicationKeyBytes2 = randomBytes(32);
1100         WrappedApplicationKey applicationKey1 = new WrappedApplicationKey.Builder()
1101                     .setAlias(TEST_ALIAS)
1102                      // Use a different recovery key here, so the decryption will fail
1103                     .setEncryptedKeyMaterial(
1104                             encryptedApplicationKey(randomRecoveryKey(), applicationKeyBytes1))
1105                     .build();
1106         WrappedApplicationKey applicationKey2 = new WrappedApplicationKey.Builder()
1107                     .setAlias(TEST_ALIAS2)
1108                     .setEncryptedKeyMaterial(
1109                             encryptedApplicationKey(recoveryKey, applicationKeyBytes2))
1110                     .build();
1111 
1112         Map<String, String> recoveredKeys = mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
1113                 TEST_SESSION_ID,
1114                 encryptedClaimResponse,
1115                 ImmutableList.of(applicationKey1, applicationKey2));
1116 
1117         assertThat(recoveredKeys).hasSize(1);
1118         assertThat(recoveredKeys).containsKey(TEST_ALIAS2);
1119     }
1120 
1121     @Test
setSnapshotCreatedPendingIntent()1122     public void setSnapshotCreatedPendingIntent() throws Exception {
1123         int uid = Binder.getCallingUid();
1124         PendingIntent intent = PendingIntent.getBroadcast(
1125                 InstrumentationRegistry.getTargetContext(), /*requestCode=*/1,
1126                 new Intent()
1127                         .setPackage(InstrumentationRegistry.getTargetContext().getPackageName()),
1128                 /*flags=*/ PendingIntent.FLAG_MUTABLE);
1129         mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent);
1130         verify(mMockListenersStorage).setSnapshotListener(eq(uid), any(PendingIntent.class));
1131     }
1132 
1133     @Test
setServerParams_updatesServerParams()1134     public void setServerParams_updatesServerParams() throws Exception {
1135         int uid = Binder.getCallingUid();
1136         int userId = UserHandle.getCallingUserId();
1137         byte[] serverParams = new byte[] { 1 };
1138 
1139         mRecoverableKeyStoreManager.setServerParams(serverParams);
1140 
1141         assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(serverParams);
1142     }
1143 
1144     @Test
setServerParams_doesNotSetSnapshotPendingIfInitializing()1145     public void setServerParams_doesNotSetSnapshotPendingIfInitializing() throws Exception {
1146         int uid = Binder.getCallingUid();
1147         int userId = UserHandle.getCallingUserId();
1148         byte[] serverParams = new byte[] { 1 };
1149 
1150         mRecoverableKeyStoreManager.setServerParams(serverParams);
1151 
1152         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
1153     }
1154 
1155     @Test
setServerParams_doesNotSetSnapshotPendingIfSettingSameValue()1156     public void setServerParams_doesNotSetSnapshotPendingIfSettingSameValue() throws Exception {
1157         int uid = Binder.getCallingUid();
1158         int userId = UserHandle.getCallingUserId();
1159         byte[] serverParams = new byte[] { 1 };
1160 
1161         mRecoverableKeyStoreManager.setServerParams(serverParams);
1162 
1163         generateKeyAndSimulateSync(userId, uid, 10);
1164 
1165         mRecoverableKeyStoreManager.setServerParams(serverParams);
1166 
1167         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
1168     }
1169 
1170     @Test
setServerParams_setsSnapshotPendingIfUpdatingValue()1171     public void setServerParams_setsSnapshotPendingIfUpdatingValue() throws Exception {
1172         int uid = Binder.getCallingUid();
1173         int userId = UserHandle.getCallingUserId();
1174 
1175         mRecoverableKeyStoreManager.setServerParams(new byte[] { 1 });
1176 
1177         generateKeyAndSimulateSync(userId, uid, 10);
1178 
1179         mRecoverableKeyStoreManager.setServerParams(new byte[] { 2 });
1180 
1181         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
1182     }
1183 
1184     @Test
setRecoverySecretTypes_updatesSecretTypes()1185     public void setRecoverySecretTypes_updatesSecretTypes() throws Exception {
1186         int[] types1 = new int[]{11, 2000};
1187         int[] types2 = new int[]{1, 2, 3};
1188         int[] types3 = new int[]{};
1189 
1190         mRecoverableKeyStoreManager.setRecoverySecretTypes(types1);
1191         assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
1192                 types1);
1193 
1194         mRecoverableKeyStoreManager.setRecoverySecretTypes(types2);
1195         assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
1196                 types2);
1197 
1198         mRecoverableKeyStoreManager.setRecoverySecretTypes(types3);
1199         assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
1200                 types3);
1201     }
1202 
1203     @Test
setRecoverySecretTypes_doesNotSetSnapshotPendingIfIniting()1204     public void setRecoverySecretTypes_doesNotSetSnapshotPendingIfIniting() throws Exception {
1205         int uid = Binder.getCallingUid();
1206         int userId = UserHandle.getCallingUserId();
1207         int[] secretTypes = new int[] { 101 };
1208 
1209         mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
1210 
1211         // There were no keys.
1212         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
1213     }
1214 
1215     @Test
setRecoverySecretTypes_doesNotSetSnapshotPendingIfSettingSameValue()1216     public void setRecoverySecretTypes_doesNotSetSnapshotPendingIfSettingSameValue()
1217             throws Exception {
1218         int uid = Binder.getCallingUid();
1219         int userId = UserHandle.getCallingUserId();
1220         int[] secretTypes = new int[] { 101 };
1221 
1222         mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
1223 
1224         generateKeyAndSimulateSync(userId, uid, 10);
1225 
1226         mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
1227 
1228         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
1229     }
1230 
1231     @Test
setRecoverySecretTypes_setsSnapshotPendingIfUpdatingValue()1232     public void setRecoverySecretTypes_setsSnapshotPendingIfUpdatingValue() throws Exception {
1233         int uid = Binder.getCallingUid();
1234         int userId = UserHandle.getCallingUserId();
1235 
1236         mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 101 });
1237 
1238         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
1239 
1240         generateKeyAndSimulateSync(userId, uid, 10);
1241 
1242         mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 102 });
1243 
1244         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
1245     }
1246 
1247     @Test
setRecoverySecretTypes_throwsIfNullTypes()1248     public void setRecoverySecretTypes_throwsIfNullTypes() throws Exception {
1249         try {
1250             mRecoverableKeyStoreManager.setRecoverySecretTypes(/*types=*/ null);
1251             fail("should have thrown");
1252         } catch (NullPointerException e) {
1253             assertThat(e.getMessage()).contains("is null");
1254         }
1255     }
1256 
1257     @Test
setRecoverySecretTypes_updatesShouldCreateSnapshot()1258     public void setRecoverySecretTypes_updatesShouldCreateSnapshot() throws Exception {
1259         int uid = Binder.getCallingUid();
1260         int userId = UserHandle.getCallingUserId();
1261         mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 1 });
1262 
1263         generateKeyAndSimulateSync(userId, uid, 10);
1264 
1265         mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 2 });
1266 
1267         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
1268     }
1269 
1270     @Test
setRecoveryStatus()1271     public void setRecoveryStatus() throws Exception {
1272         int userId = UserHandle.getCallingUserId();
1273         int uid = Binder.getCallingUid();
1274         int status = 100;
1275         int status2 = 200;
1276         String alias = "key1";
1277         byte[] keyMetadata = null;
1278 
1279         WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, keyMetadata, GENERATION_ID,
1280                 status);
1281         mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
1282         Map<String, Integer> statuses =
1283                 mRecoverableKeyStoreManager.getRecoveryStatus();
1284         assertThat(statuses).hasSize(1);
1285         assertThat(statuses).containsEntry(alias, status);
1286 
1287         mRecoverableKeyStoreManager.setRecoveryStatus(alias, status2);
1288         statuses = mRecoverableKeyStoreManager.getRecoveryStatus();
1289         assertThat(statuses).hasSize(1);
1290         assertThat(statuses).containsEntry(alias, status2); // updated
1291     }
1292 
1293     @Test
setRecoveryStatus_throwsIfNullAlias()1294     public void setRecoveryStatus_throwsIfNullAlias() throws Exception {
1295         try {
1296             mRecoverableKeyStoreManager.setRecoveryStatus(/*alias=*/ null, /*status=*/ 100);
1297             fail("should have thrown");
1298         } catch (NullPointerException e) {
1299             assertThat(e.getMessage()).contains("is null");
1300         }
1301     }
1302 
1303     @Test
lockScreenSecretAvailable_syncsKeysForUser()1304     public void lockScreenSecretAvailable_syncsKeysForUser() throws Exception {
1305         mRecoverableKeyStoreManager.lockScreenSecretAvailable(
1306                 LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11);
1307 
1308         verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
1309     }
1310 
1311     @Test
lockScreenSecretChanged_syncsKeysForUser()1312     public void lockScreenSecretChanged_syncsKeysForUser() throws Exception {
1313         mRecoverableKeyStoreManager.lockScreenSecretChanged(
1314                 LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
1315                 "password".getBytes(),
1316                 11);
1317 
1318         verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
1319     }
1320 
1321     @Test
startRemoteLockscreenValidation_credentialsNotSet()1322     public void startRemoteLockscreenValidation_credentialsNotSet() throws Exception {
1323         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1324                 LockPatternUtils.CREDENTIAL_TYPE_NONE);
1325         try {
1326             mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
1327             fail("should have thrown");
1328         } catch (IllegalStateException e) {
1329             assertThat(e.getMessage()).contains("not set");
1330         }
1331         verify(mLockSettingsService).getCredentialType(mUserId);
1332     }
1333     @Test
startRemoteLockscreenValidation_checksPermission()1334     public void startRemoteLockscreenValidation_checksPermission() throws Exception {
1335         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1336                 LockPatternUtils.CREDENTIAL_TYPE_PIN);
1337 
1338         mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
1339 
1340         verify(mMockContext, times(1))
1341                 .enforceCallingOrSelfPermission(
1342                         eq(Manifest.permission.CHECK_REMOTE_LOCKSCREEN), any());
1343         mRemoteLockscreenValidationSessionStorage.finishSession(mUserId);
1344     }
1345     @Test
startRemoteLockscreenValidation_returnsCredentailsType()1346     public void startRemoteLockscreenValidation_returnsCredentailsType() throws Exception {
1347         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1348                 LockPatternUtils.CREDENTIAL_TYPE_PIN);
1349 
1350         RemoteLockscreenValidationSession request =
1351                 mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
1352 
1353         assertThat(request.getLockType()).isEqualTo(KeyguardManager.PIN);
1354         assertThat(request.getRemainingAttempts()).isEqualTo(5);
1355         verify(mLockSettingsService).getCredentialType(anyInt());
1356     }
1357     @Test
startRemoteLockscreenValidation_returnsRemainingAttempts()1358     public void startRemoteLockscreenValidation_returnsRemainingAttempts() throws Exception {
1359         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1360                 LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
1361         mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 3);
1362 
1363         RemoteLockscreenValidationSession request =
1364                 mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
1365 
1366         assertThat(request.getLockType()).isEqualTo(KeyguardManager.PATTERN);
1367         assertThat(request.getRemainingAttempts()).isEqualTo(2);
1368     }
1369     @Test
startRemoteLockscreenValidation_password()1370     public void startRemoteLockscreenValidation_password() throws Exception {
1371         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1372                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
1373         mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 7);
1374 
1375         RemoteLockscreenValidationSession request =
1376                 mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
1377 
1378         assertThat(request.getRemainingAttempts()).isEqualTo(0);
1379         assertThat(request.getLockType()).isEqualTo(KeyguardManager.PASSWORD);
1380     }
1381     @Test
validateRemoteLockscreen_noActiveSession()1382     public void validateRemoteLockscreen_noActiveSession() throws Exception {
1383         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1384                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
1385 
1386         RemoteLockscreenValidationResult result =
1387                 mRecoverableKeyStoreManager.validateRemoteLockscreen(INVALID_GUESS,
1388                         mLockSettingsService);
1389 
1390         assertThat(result.getResultCode()).isEqualTo(
1391                 RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED);
1392     }
1393     @Test
validateRemoteLockscreen_decryptionError()1394     public void validateRemoteLockscreen_decryptionError() throws Exception {
1395         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1396                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
1397         mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4);
1398 
1399         mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
1400 
1401         try {
1402             mRecoverableKeyStoreManager.validateRemoteLockscreen(
1403                         new byte[] {1, 2, 3},
1404                         mLockSettingsService);
1405             fail("should have thrown");
1406         } catch (IllegalStateException e) {
1407             // Decryption error
1408         }
1409     }
1410     @Test
validateRemoteLockscreen_zeroRemainingAttempts()1411     public void validateRemoteLockscreen_zeroRemainingAttempts() throws Exception {
1412         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1413                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
1414         mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 5);
1415         mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
1416 
1417         RemoteLockscreenValidationResult result =
1418                     mRecoverableKeyStoreManager.validateRemoteLockscreen(
1419                     encryptCredentialsForNewSession(VALID_GUESS),
1420                         mLockSettingsService);
1421 
1422         assertThat(result.getResultCode()).isEqualTo(
1423                 RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS);
1424     }
1425     @Test
validateRemoteLockscreen_guessValid()1426     public void validateRemoteLockscreen_guessValid() throws Exception {
1427         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1428                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
1429         mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4);
1430         mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
1431 
1432         RemoteLockscreenValidationResult result =
1433                 mRecoverableKeyStoreManager.validateRemoteLockscreen(
1434                         encryptCredentialsForNewSession(VALID_GUESS),
1435                         mLockSettingsService);
1436 
1437         assertThat(result.getResultCode()).isEqualTo(
1438                 RemoteLockscreenValidationResult.RESULT_GUESS_VALID);
1439         // Valid guess resets counter
1440         assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId)).isEqualTo(0);
1441     }
1442     @Test
validateRemoteLockscreen_timeout()1443     public void validateRemoteLockscreen_timeout() throws Exception {
1444         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1445                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
1446         mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4);
1447 
1448         RemoteLockscreenValidationResult result =
1449                 mRecoverableKeyStoreManager.validateRemoteLockscreen(
1450                         encryptCredentialsForNewSession(GUESS_LOCKOUT),
1451                         mLockSettingsService);
1452 
1453         assertThat(result.getResultCode()).isEqualTo(
1454                 RemoteLockscreenValidationResult.RESULT_LOCKOUT);
1455         assertThat(result.getTimeoutMillis()).isEqualTo((long) TIMEOUT_MILLIS);
1456         // Counter was not changed
1457         assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId)).isEqualTo(4);
1458     }
1459     @Test
validateRemoteLockscreen_guessInvalid()1460     public void validateRemoteLockscreen_guessInvalid() throws Exception {
1461         when(mLockSettingsService.getCredentialType(anyInt())).thenReturn(
1462                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
1463         mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4);
1464         mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
1465 
1466         RemoteLockscreenValidationResult result =
1467                 mRecoverableKeyStoreManager.validateRemoteLockscreen(
1468                         encryptCredentialsForNewSession(INVALID_GUESS),
1469                         mLockSettingsService);
1470 
1471         assertThat(result.getResultCode()).isEqualTo(
1472                 RemoteLockscreenValidationResult.RESULT_GUESS_INVALID);
1473         assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId)).isEqualTo(5);
1474     }
1475 
encryptCredentialsForNewSession(byte[] credentials)1476     private byte[] encryptCredentialsForNewSession(byte[] credentials) throws Exception {
1477         RemoteLockscreenValidationSession request =
1478                 mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
1479         PublicKey publicKey = SecureBox.decodePublicKey(request.getSourcePublicKey());
1480         return SecureBox.encrypt(
1481               publicKey,
1482               /* sharedSecret= */ null,
1483               LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER,
1484               credentials);
1485     }
1486 
encryptedApplicationKey( SecretKey recoveryKey, byte[] applicationKey)1487     private static byte[] encryptedApplicationKey(
1488             SecretKey recoveryKey, byte[] applicationKey) throws Exception {
1489         return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
1490                 TEST_ALIAS,
1491                 Pair.create(new SecretKeySpec(applicationKey, "AES"), /*metadata=*/ null)
1492         )).get(TEST_ALIAS);
1493     }
1494 
encryptClaimResponse( byte[] keyClaimant, byte[] lskfHash, byte[] vaultParams, SecretKey recoveryKey)1495     private static byte[] encryptClaimResponse(
1496             byte[] keyClaimant,
1497             byte[] lskfHash,
1498             byte[] vaultParams,
1499             SecretKey recoveryKey) throws Exception {
1500         byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey(
1501                 lskfHash, recoveryKey);
1502         return SecureBox.encrypt(
1503                 /*theirPublicKey=*/ null,
1504                 /*sharedSecret=*/ keyClaimant,
1505                 /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
1506                 /*payload=*/ locallyEncryptedRecoveryKey);
1507     }
1508 
randomRecoveryKey()1509     private static SecretKey randomRecoveryKey() {
1510         return new SecretKeySpec(randomBytes(32), "AES");
1511     }
1512 
getUtf8Bytes(String s)1513     private static byte[] getUtf8Bytes(String s) {
1514         return s.getBytes(StandardCharsets.UTF_8);
1515     }
1516 
randomBytes(int n)1517     private static byte[] randomBytes(int n) {
1518         byte[] bytes = new byte[n];
1519         new Random().nextBytes(bytes);
1520         return bytes;
1521     }
1522 
generateKeyAndSimulateSync(int userId, int uid, int snapshotVersion)1523     private void generateKeyAndSimulateSync(int userId, int uid, int snapshotVersion)
1524             throws Exception{
1525         mRecoverableKeyStoreManager.generateKeyWithMetadata(TEST_ALIAS, KEY_METADATA_NULL);
1526         // Simulate key sync.
1527         mRecoverableKeyStoreDb.setSnapshotVersion(userId, uid, snapshotVersion);
1528         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
1529     }
1530 
generateAndroidKeyStoreKey()1531     private SecretKey generateAndroidKeyStoreKey() throws Exception {
1532         KeyGenerator keyGenerator = KeyGenerator.getInstance(
1533                 KEY_ALGORITHM,
1534                 ANDROID_KEY_STORE_PROVIDER);
1535         keyGenerator.init(new KeyGenParameterSpec.Builder(
1536                 WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
1537                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
1538                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
1539                 .build());
1540         return keyGenerator.generateKey();
1541     }
1542 }
1543