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