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