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