Home | History | Annotate | Download | only in recoverablekeystore
      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 import static org.junit.Assert.assertArrayEquals;
     27 import static org.junit.Assert.assertEquals;
     28 import static org.junit.Assert.fail;
     29 import static org.mockito.ArgumentMatchers.any;
     30 import static org.mockito.ArgumentMatchers.anyInt;
     31 import static org.mockito.ArgumentMatchers.anyString;
     32 import static org.mockito.ArgumentMatchers.eq;
     33 import static org.mockito.Mockito.atLeast;
     34 import static org.mockito.Mockito.times;
     35 import static org.mockito.Mockito.verify;
     36 import static org.mockito.Mockito.when;
     37 
     38 import android.app.KeyguardManager;
     39 import android.app.PendingIntent;
     40 import android.content.Context;
     41 import android.content.Intent;
     42 import android.Manifest;
     43 import android.os.Binder;
     44 import android.os.ServiceSpecificException;
     45 import android.os.UserHandle;
     46 import android.security.KeyStore;
     47 import android.security.keystore.AndroidKeyStoreProvider;
     48 import android.security.keystore.AndroidKeyStoreSecretKey;
     49 import android.security.keystore.KeyGenParameterSpec;
     50 import android.security.keystore.KeyProperties;
     51 import android.security.keystore.recovery.KeyChainProtectionParams;
     52 import android.security.keystore.recovery.KeyDerivationParams;
     53 import android.security.keystore.recovery.RecoveryCertPath;
     54 import android.security.keystore.recovery.TrustedRootCertificates;
     55 import android.security.keystore.recovery.WrappedApplicationKey;
     56 import android.support.test.filters.SmallTest;
     57 import android.support.test.InstrumentationRegistry;
     58 import android.support.test.runner.AndroidJUnit4;
     59 
     60 import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
     61 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
     62 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
     63 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
     64 
     65 import com.google.common.collect.ImmutableList;
     66 import com.google.common.collect.ImmutableMap;
     67 
     68 import org.junit.After;
     69 import org.junit.Before;
     70 import org.junit.Test;
     71 import org.junit.runner.RunWith;
     72 import org.mockito.Mock;
     73 import org.mockito.MockitoAnnotations;
     74 import org.mockito.Spy;
     75 
     76 import java.io.File;
     77 import java.nio.charset.StandardCharsets;
     78 import java.security.UnrecoverableKeyException;
     79 import java.security.cert.CertPath;
     80 import java.security.cert.CertificateFactory;
     81 import java.security.cert.X509Certificate;
     82 import java.util.ArrayList;
     83 import java.util.concurrent.Executors;
     84 import java.util.Map;
     85 import java.util.Random;
     86 
     87 import javax.crypto.Cipher;
     88 import javax.crypto.KeyGenerator;
     89 import javax.crypto.SecretKey;
     90 import javax.crypto.spec.GCMParameterSpec;
     91 import javax.crypto.spec.SecretKeySpec;
     92 
     93 @SmallTest
     94 @RunWith(AndroidJUnit4.class)
     95 public 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