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.KeyChainProtectionParams.UI_FORMAT_PATTERN;
22import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PIN;
23import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
24import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
25
26import static com.google.common.truth.Truth.assertThat;
27
28import static org.junit.Assert.assertArrayEquals;
29import static org.junit.Assert.assertEquals;
30import static org.junit.Assert.assertFalse;
31import static org.junit.Assert.assertNotNull;
32import static org.junit.Assert.assertNull;
33import static org.junit.Assert.assertTrue;
34
35import static org.mockito.ArgumentMatchers.any;
36import static org.mockito.ArgumentMatchers.anyInt;
37import static org.mockito.ArgumentMatchers.eq;
38import static org.mockito.Mockito.atLeast;
39import static org.mockito.Mockito.never;
40import static org.mockito.Mockito.verify;
41import static org.mockito.Mockito.when;
42
43import android.content.Context;
44import android.os.FileUtils;
45import android.security.keystore.AndroidKeyStoreSecretKey;
46import android.security.keystore.KeyGenParameterSpec;
47import android.security.keystore.KeyProperties;
48import android.security.keystore.recovery.KeyChainSnapshot;
49import android.security.keystore.recovery.KeyDerivationParams;
50import android.security.keystore.recovery.RecoveryController;
51import android.security.keystore.recovery.TrustedRootCertificates;
52import android.security.keystore.recovery.WrappedApplicationKey;
53import android.support.test.InstrumentationRegistry;
54import android.support.test.filters.SmallTest;
55import android.support.test.runner.AndroidJUnit4;
56
57import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
58import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
59
60import org.junit.After;
61import org.junit.Before;
62import org.junit.Test;
63import org.junit.runner.RunWith;
64import org.mockito.Mock;
65import org.mockito.MockitoAnnotations;
66import org.mockito.Spy;
67
68import java.io.File;
69import java.nio.charset.StandardCharsets;
70import java.util.Arrays;
71import java.util.List;
72import java.util.Random;
73
74import javax.crypto.KeyGenerator;
75import javax.crypto.SecretKey;
76
77@SmallTest
78@RunWith(AndroidJUnit4.class)
79public class KeySyncTaskTest {
80
81    private static final String SNAPSHOT_TOP_LEVEL_DIRECTORY = "recoverablekeystore";
82
83    private static final String KEY_ALGORITHM = "AES";
84    private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
85    private static final String TEST_ROOT_CERT_ALIAS = "trusted_root";
86    private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey";
87    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
88    private static final int TEST_USER_ID = 1000;
89    private static final int TEST_RECOVERY_AGENT_UID = 10009;
90    private static final int TEST_RECOVERY_AGENT_UID2 = 10010;
91    private static final byte[] TEST_VAULT_HANDLE =
92            new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17};
93    private static final String TEST_APP_KEY_ALIAS = "rcleaver";
94    private static final int TEST_GENERATION_ID = 2;
95    private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PATTERN;
96    private static final String TEST_CREDENTIAL = "pas123";
97    private static final byte[] THM_ENCRYPTED_RECOVERY_KEY_HEADER =
98            "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
99
100    @Mock private PlatformKeyManager mPlatformKeyManager;
101    @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage;
102    @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
103    @Spy private MockScrypt mMockScrypt;
104
105    private RecoverySnapshotStorage mRecoverySnapshotStorage;
106    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
107    private File mDatabaseFile;
108    private AndroidKeyStoreSecretKey mWrappingKey;
109    private PlatformEncryptionKey mEncryptKey;
110
111    private KeySyncTask mKeySyncTask;
112
113    @Before
114    public void setUp() throws Exception {
115        MockitoAnnotations.initMocks(this);
116
117        Context context = InstrumentationRegistry.getTargetContext();
118        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
119        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
120
121        mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
122                new int[] {TYPE_LOCKSCREEN});
123        mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2,
124                new int[] {TYPE_LOCKSCREEN});
125
126        mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
127                TEST_ROOT_CERT_ALIAS);
128        mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2,
129                TEST_ROOT_CERT_ALIAS);
130        mRecoverySnapshotStorage = new RecoverySnapshotStorage(context.getFilesDir());
131
132        mKeySyncTask = new KeySyncTask(
133                mRecoverableKeyStoreDb,
134                mRecoverySnapshotStorage,
135                mSnapshotListenersStorage,
136                TEST_USER_ID,
137                TEST_CREDENTIAL_TYPE,
138                TEST_CREDENTIAL,
139                /*credentialUpdated=*/ false,
140                mPlatformKeyManager,
141                mTestOnlyInsecureCertificateHelper,
142                mMockScrypt);
143
144        mWrappingKey = generateAndroidKeyStoreKey();
145        mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey);
146        when(mPlatformKeyManager.getDecryptKey(TEST_USER_ID)).thenReturn(
147                new PlatformDecryptionKey(TEST_GENERATION_ID, mWrappingKey));
148    }
149
150    @After
151    public void tearDown() {
152        mRecoverableKeyStoreDb.close();
153        mDatabaseFile.delete();
154
155        File file = new File(InstrumentationRegistry.getTargetContext().getFilesDir(),
156                SNAPSHOT_TOP_LEVEL_DIRECTORY);
157        FileUtils.deleteContentsAndDir(file);
158    }
159
160    @Test
161    public void isPin_isTrueForNumericString() {
162        assertTrue(KeySyncTask.isPin("3298432574398654376547"));
163    }
164
165    @Test
166    public void isPin_isFalseForStringContainingLetters() {
167        assertFalse(KeySyncTask.isPin("398i54369548654"));
168    }
169
170    @Test
171    public void isPin_isFalseForStringContainingSymbols() {
172        assertFalse(KeySyncTask.isPin("-3987543643"));
173    }
174
175    @Test
176    public void hashCredentialsBySaltedSha256_returnsSameHashForSameCredentialsAndSalt() {
177        String credentials = "password1234";
178        byte[] salt = randomBytes(16);
179
180        assertArrayEquals(
181                KeySyncTask.hashCredentialsBySaltedSha256(salt, credentials),
182                KeySyncTask.hashCredentialsBySaltedSha256(salt, credentials));
183    }
184
185    @Test
186    public void hashCredentialsBySaltedSha256_returnsDifferentHashForDifferentCredentials() {
187        byte[] salt = randomBytes(16);
188
189        assertFalse(
190                Arrays.equals(
191                    KeySyncTask.hashCredentialsBySaltedSha256(salt, "password1234"),
192                    KeySyncTask.hashCredentialsBySaltedSha256(salt, "password12345")));
193    }
194
195    @Test
196    public void hashCredentialsBySaltedSha256_returnsDifferentHashForDifferentSalt() {
197        String credentials = "wowmuch";
198
199        assertFalse(
200                Arrays.equals(
201                        KeySyncTask.hashCredentialsBySaltedSha256(randomBytes(64), credentials),
202                        KeySyncTask.hashCredentialsBySaltedSha256(randomBytes(64), credentials)));
203    }
204
205    @Test
206    public void hashCredentialsBySaltedSha256_returnsDifferentHashEvenIfConcatIsSame() {
207        assertFalse(
208                Arrays.equals(
209                        KeySyncTask.hashCredentialsBySaltedSha256(utf8Bytes("123"), "4567"),
210                        KeySyncTask.hashCredentialsBySaltedSha256(utf8Bytes("1234"), "567")));
211    }
212
213    @Test
214    public void getUiFormat_returnsPinIfPin() {
215        assertEquals(UI_FORMAT_PIN,
216                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234"));
217    }
218
219    @Test
220    public void getUiFormat_returnsPasswordIfPassword() {
221        assertEquals(UI_FORMAT_PASSWORD,
222                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a"));
223    }
224
225    @Test
226    public void getUiFormat_returnsPatternIfPattern() {
227        assertEquals(UI_FORMAT_PATTERN,
228                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234"));
229
230    }
231
232    @Test
233    public void run_doesNotSendAnythingIfNoKeysToSync() throws Exception {
234        mKeySyncTask.run();
235
236        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
237    }
238
239    @Test
240    public void run_doesNotSendAnythingIfSnapshotIsUpToDate() throws Exception {
241        mKeySyncTask.run();
242
243        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
244    }
245
246    @Test
247    public void run_doesNotSendAnythingIfNoRecoveryAgentSet() throws Exception {
248        SecretKey applicationKey = generateKey();
249        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
250        mRecoverableKeyStoreDb.insertKey(
251                TEST_USER_ID,
252                TEST_RECOVERY_AGENT_UID,
253                TEST_APP_KEY_ALIAS,
254                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
255        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
256
257        mKeySyncTask.run();
258
259        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
260    }
261
262    @Test
263    public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception {
264        SecretKey applicationKey = generateKey();
265        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
266        mRecoverableKeyStoreDb.insertKey(
267                TEST_USER_ID,
268                TEST_RECOVERY_AGENT_UID,
269                TEST_APP_KEY_ALIAS,
270                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
271        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
272                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
273        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
274
275        mKeySyncTask.run();
276
277        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
278    }
279
280    @Test
281    public void run_useScryptToHashPasswordInTestMode() throws Exception {
282        String password = TrustedRootCertificates.INSECURE_PASSWORD_PREFIX + "";  // The shortest
283        String appKeyAlias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX + "alias";
284        mKeySyncTask = new KeySyncTask(
285                mRecoverableKeyStoreDb,
286                mRecoverySnapshotStorage,
287                mSnapshotListenersStorage,
288                TEST_USER_ID,
289                CREDENTIAL_TYPE_PASSWORD,
290                /*credential=*/ password,
291                /*credentialUpdated=*/ false,
292                mPlatformKeyManager,
293                mTestOnlyInsecureCertificateHelper,
294                mMockScrypt);
295        mRecoverableKeyStoreDb.setServerParams(
296                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
297        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
298        mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
299                TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS);
300        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
301                TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
302                TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS,
303                TestData.getInsecureCertPathForEndpoint1());
304        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, appKeyAlias);
305
306        mKeySyncTask.run();
307
308        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
309        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
310        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
311                isEqualTo(UI_FORMAT_PASSWORD);
312        verify(mMockScrypt).scrypt(eq(password.getBytes()), any(),
313                eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
314                eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
315        KeyDerivationParams keyDerivationParams =
316                keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams();
317        assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
318                KeyDerivationParams.ALGORITHM_SCRYPT);
319        assertThat(keyDerivationParams.getMemoryDifficulty()).isEqualTo(KeySyncTask.SCRYPT_PARAM_N);
320    }
321
322    @Test
323    public void run_useSha256ToHashPatternInProdMode() throws Exception {
324        String pattern = "123456";
325        mKeySyncTask = new KeySyncTask(
326                mRecoverableKeyStoreDb,
327                mRecoverySnapshotStorage,
328                mSnapshotListenersStorage,
329                TEST_USER_ID,
330                CREDENTIAL_TYPE_PATTERN,
331                /*credential=*/ pattern,
332                /*credentialUpdated=*/ false,
333                mPlatformKeyManager,
334                mTestOnlyInsecureCertificateHelper,
335                mMockScrypt);
336        mRecoverableKeyStoreDb.setServerParams(
337                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
338        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
339        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
340        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
341                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
342
343        mKeySyncTask.run();
344
345        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
346        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
347        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
348                isEqualTo(UI_FORMAT_PATTERN);
349        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
350        KeyDerivationParams keyDerivationParams =
351                keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams();
352        assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
353                KeyDerivationParams.ALGORITHM_SHA256);
354    }
355
356    @Test
357    public void run_useScryptToHashPasswordInProdMode() throws Exception {
358        String shortPassword = "abc";
359        mKeySyncTask = new KeySyncTask(
360                mRecoverableKeyStoreDb,
361                mRecoverySnapshotStorage,
362                mSnapshotListenersStorage,
363                TEST_USER_ID,
364                CREDENTIAL_TYPE_PASSWORD,
365                /*credential=*/ shortPassword,
366                /*credentialUpdated=*/ false,
367                mPlatformKeyManager,
368                mTestOnlyInsecureCertificateHelper,
369                mMockScrypt);
370        mRecoverableKeyStoreDb.setServerParams(
371                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
372        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
373        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
374        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
375                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
376
377        mKeySyncTask.run();
378
379        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
380        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
381        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
382                isEqualTo(UI_FORMAT_PASSWORD);
383        verify(mMockScrypt).scrypt(eq(shortPassword.getBytes()), any(),
384                eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
385                eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
386        KeyDerivationParams keyDerivationParams =
387                keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams();
388        assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
389                KeyDerivationParams.ALGORITHM_SCRYPT);
390    }
391
392    @Test
393    public void run_stillCreatesSnapshotIfNoRecoveryAgentPendingIntentRegistered()
394            throws Exception {
395        mRecoverableKeyStoreDb.setServerParams(
396                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
397        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
398        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
399        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
400                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
401
402        mKeySyncTask.run();
403
404        assertNotNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
405    }
406
407    @Test
408    public void run_InTestModeWithWhitelistedCredentials() throws Exception {
409        mRecoverableKeyStoreDb.setServerParams(
410                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
411        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
412        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
413        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
414                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
415
416        // Enter test mode with whitelisted credentials
417        when(mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(any())).thenReturn(true);
418        when(mTestOnlyInsecureCertificateHelper.doesCredentialSupportInsecureMode(anyInt(), any()))
419                .thenReturn(true);
420        mKeySyncTask.run();
421
422        verify(mTestOnlyInsecureCertificateHelper)
423                .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS));
424
425        // run whitelist checks
426        verify(mTestOnlyInsecureCertificateHelper)
427                .doesCredentialSupportInsecureMode(anyInt(), any());
428        verify(mTestOnlyInsecureCertificateHelper)
429                .keepOnlyWhitelistedInsecureKeys(any());
430
431        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
432        assertNotNull(keyChainSnapshot); // created snapshot
433        List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys();
434        assertThat(applicationKeys).hasSize(0); // non whitelisted key is not included
435        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
436    }
437
438    @Test
439    public void run_InTestModeWithNonWhitelistedCredentials() throws Exception {
440        mRecoverableKeyStoreDb.setServerParams(
441                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
442        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
443        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
444        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
445                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
446
447        // Enter test mode with non whitelisted credentials
448        when(mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(any())).thenReturn(true);
449        when(mTestOnlyInsecureCertificateHelper.doesCredentialSupportInsecureMode(anyInt(), any()))
450                .thenReturn(false);
451        mKeySyncTask.run();
452
453        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID)); // not created
454        verify(mTestOnlyInsecureCertificateHelper)
455                .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS));
456        verify(mTestOnlyInsecureCertificateHelper)
457                .doesCredentialSupportInsecureMode(anyInt(), any());
458        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
459    }
460
461    @Test
462    public void run_doesNotFilterCredentialsAndAliasesInProd() throws Exception {
463        mRecoverableKeyStoreDb.setServerParams(
464                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
465        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
466        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
467        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
468                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
469
470        mKeySyncTask.run();
471        assertNotNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
472
473        verify(mTestOnlyInsecureCertificateHelper)
474                .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS));
475        verify(mTestOnlyInsecureCertificateHelper, atLeast(1))
476                .isTestOnlyCertificateAlias(eq(TEST_ROOT_CERT_ALIAS));
477
478        // no whitelists check
479        verify(mTestOnlyInsecureCertificateHelper, never())
480                .doesCredentialSupportInsecureMode(anyInt(), any());
481        verify(mTestOnlyInsecureCertificateHelper, never())
482                .keepOnlyWhitelistedInsecureKeys(any());
483    }
484
485    @Test
486    public void run_replacesNullActiveRootAliasWithDefaultValue() throws Exception {
487        mRecoverableKeyStoreDb.setServerParams(
488                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
489        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
490        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
491        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
492                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
493        mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
494                /*alias=*/ null);
495
496        when(mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(null))
497                .thenReturn(TEST_ROOT_CERT_ALIAS); // override default.
498        mKeySyncTask.run();
499
500        verify(mTestOnlyInsecureCertificateHelper).getDefaultCertificateAliasIfEmpty(null);
501    }
502
503    @Test
504    public void run_sendsEncryptedKeysIfAvailableToSync_withRawPublicKey() throws Exception {
505        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
506                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS,
507                TestData.getInsecureCertPathForEndpoint1());
508
509        mRecoverableKeyStoreDb.setServerParams(
510                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
511        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
512        SecretKey applicationKey =
513                addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
514
515        mKeySyncTask.run();
516
517        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
518        KeyDerivationParams keyDerivationParams =
519                keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams();
520        assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
521                KeyDerivationParams.ALGORITHM_SHA256);
522        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
523        byte[] lockScreenHash = KeySyncTask.hashCredentialsBySaltedSha256(
524                keyDerivationParams.getSalt(),
525                TEST_CREDENTIAL);
526        Long counterId = mRecoverableKeyStoreDb.getCounterId(TEST_USER_ID, TEST_RECOVERY_AGENT_UID);
527        assertThat(counterId).isNotNull();
528        byte[] recoveryKey = decryptThmEncryptedKey(
529                lockScreenHash,
530                keyChainSnapshot.getEncryptedRecoveryKeyBlob(),
531                /*vaultParams=*/ KeySyncUtils.packVaultParams(
532                        TestData.getInsecureCertPathForEndpoint1().getCertificates().get(0)
533                                .getPublicKey(),
534                        counterId,
535                        /*maxAttempts=*/ 10,
536                        TEST_VAULT_HANDLE));
537        List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys();
538        assertThat(applicationKeys).hasSize(1);
539        assertThat(keyChainSnapshot.getCounterId()).isEqualTo(counterId);
540        assertThat(keyChainSnapshot.getMaxAttempts()).isEqualTo(10);
541        assertThat(keyChainSnapshot.getTrustedHardwareCertPath())
542                .isEqualTo(TestData.getInsecureCertPathForEndpoint1());
543        assertThat(keyChainSnapshot.getServerParams()).isEqualTo(TEST_VAULT_HANDLE);
544        WrappedApplicationKey keyData = applicationKeys.get(0);
545        assertEquals(TEST_APP_KEY_ALIAS, keyData.getAlias());
546        assertThat(keyData.getAlias()).isEqualTo(keyData.getAlias());
547        byte[] appKey = KeySyncUtils.decryptApplicationKey(
548                recoveryKey, keyData.getEncryptedKeyMaterial());
549        assertThat(appKey).isEqualTo(applicationKey.getEncoded());
550    }
551
552    @Test
553    public void run_sendsEncryptedKeysIfAvailableToSync_withCertPath() throws Exception {
554        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
555                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
556        mRecoverableKeyStoreDb.setServerParams(
557                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
558        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
559        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
560
561        mKeySyncTask.run();
562
563        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
564        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
565        List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys();
566        assertThat(applicationKeys).hasSize(1);
567        assertThat(keyChainSnapshot.getTrustedHardwareCertPath())
568                .isEqualTo(TestData.CERT_PATH_1);
569    }
570
571    @Test
572    public void run_setsCorrectSnapshotVersion() throws Exception {
573        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
574                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
575        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
576        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
577
578        mKeySyncTask.run();
579
580        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
581        assertThat(keyChainSnapshot.getSnapshotVersion()).isEqualTo(1); // default value;
582        mRecoverableKeyStoreDb.setShouldCreateSnapshot(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, true);
583
584        mKeySyncTask.run();
585
586        keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
587        assertThat(keyChainSnapshot.getSnapshotVersion()).isEqualTo(2); // Updated
588    }
589
590    @Test
591    public void run_recreatesMissingSnapshot() throws Exception {
592        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
593                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
594        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
595        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
596
597        mKeySyncTask.run();
598
599        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
600        assertThat(keyChainSnapshot.getSnapshotVersion()).isEqualTo(1); // default value;
601
602        mRecoverySnapshotStorage.remove(TEST_RECOVERY_AGENT_UID); // corrupt snapshot.
603
604        mKeySyncTask.run();
605
606        keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
607        assertThat(keyChainSnapshot.getSnapshotVersion()).isEqualTo(1); // Same version
608    }
609
610    @Test
611    public void run_setsCorrectTypeForPassword() throws Exception {
612        String password = "password";
613        mKeySyncTask = new KeySyncTask(
614                mRecoverableKeyStoreDb,
615                mRecoverySnapshotStorage,
616                mSnapshotListenersStorage,
617                TEST_USER_ID,
618                CREDENTIAL_TYPE_PASSWORD,
619                password,
620                /*credentialUpdated=*/ false,
621                mPlatformKeyManager,
622                mTestOnlyInsecureCertificateHelper,
623                mMockScrypt);
624
625        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
626                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
627        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
628        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
629
630        mKeySyncTask.run();
631
632        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
633        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
634        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
635                isEqualTo(UI_FORMAT_PASSWORD);
636        verify(mMockScrypt).scrypt(eq(password.getBytes()), any(),
637                eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
638                eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
639    }
640
641    @Test
642    public void run_setsCorrectTypeForPin() throws Exception {
643        String pin = "1234";
644        mKeySyncTask = new KeySyncTask(
645                mRecoverableKeyStoreDb,
646                mRecoverySnapshotStorage,
647                mSnapshotListenersStorage,
648                TEST_USER_ID,
649                CREDENTIAL_TYPE_PASSWORD,
650                /*credential=*/ pin,
651                /*credentialUpdated=*/ false,
652                mPlatformKeyManager,
653                mTestOnlyInsecureCertificateHelper,
654                mMockScrypt);
655
656        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
657                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
658        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
659        SecretKey applicationKey =
660                addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
661
662        mKeySyncTask.run();
663
664        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
665        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
666        // Password with only digits is changed to pin.
667        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
668                isEqualTo(UI_FORMAT_PIN);
669        verify(mMockScrypt).scrypt(eq(pin.getBytes()), any(),
670                eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
671                eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
672    }
673
674    @Test
675    public void run_setsCorrectTypeForPattern() throws Exception {
676        mKeySyncTask = new KeySyncTask(
677                mRecoverableKeyStoreDb,
678                mRecoverySnapshotStorage,
679                mSnapshotListenersStorage,
680                TEST_USER_ID,
681                CREDENTIAL_TYPE_PATTERN,
682                "12345",
683                /*credentialUpdated=*/ false,
684                mPlatformKeyManager,
685                mTestOnlyInsecureCertificateHelper,
686                mMockScrypt);
687
688        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
689                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
690        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
691        SecretKey applicationKey =
692                addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
693
694        mKeySyncTask.run();
695
696        KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
697        assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
698        assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
699                isEqualTo(UI_FORMAT_PATTERN);
700        verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
701    }
702
703    @Test
704    public void run_sendsEncryptedKeysWithTwoRegisteredAgents() throws Exception {
705        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
706                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
707        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
708                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
709        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
710        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(true);
711        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
712        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_APP_KEY_ALIAS);
713        mKeySyncTask.run();
714
715        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
716        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2);
717    }
718
719    @Test
720    public void run_sendsEncryptedKeysOnlyForAgentWhichActiveUserSecretType() throws Exception {
721        mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
722                new int[] {TYPE_LOCKSCREEN, 1000});
723        // Snapshot will not be created during unlock event.
724        mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2,
725                new int[] {1000});
726
727        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
728                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
729        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
730                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
731        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
732        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(true);
733        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
734        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_APP_KEY_ALIAS);
735        mKeySyncTask.run();
736
737        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
738        verify(mSnapshotListenersStorage, never()).
739                recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2);
740    }
741
742    @Test
743    public void run_notifiesNonregisteredAgent() throws Exception {
744        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
745                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
746        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
747                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
748        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
749        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(false);
750        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
751        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_APP_KEY_ALIAS);
752        mKeySyncTask.run();
753
754        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
755        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2);
756    }
757
758    @Test
759    public void run_customLockScreen_RecoveryStatusFailure() throws Exception {
760      mKeySyncTask = new KeySyncTask(
761          mRecoverableKeyStoreDb,
762          mRecoverySnapshotStorage,
763          mSnapshotListenersStorage,
764          TEST_USER_ID,
765          /*credentialType=*/ 3,
766          "12345",
767          /*credentialUpdated=*/ false,
768          mPlatformKeyManager,
769          mTestOnlyInsecureCertificateHelper,
770          mMockScrypt);
771
772      addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
773
774      int status =
775          mRecoverableKeyStoreDb
776              .getStatusForAllKeys(TEST_RECOVERY_AGENT_UID)
777              .get(TEST_APP_KEY_ALIAS);
778      assertEquals(RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS, status);
779
780      mKeySyncTask.run();
781
782      status = mRecoverableKeyStoreDb
783          .getStatusForAllKeys(TEST_RECOVERY_AGENT_UID)
784          .get(TEST_APP_KEY_ALIAS);
785      assertEquals(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE, status);
786      verify(mMockScrypt, never()).scrypt(any(), any(), anyInt(), anyInt(), anyInt(), anyInt());
787    }
788
789    private SecretKey addApplicationKey(int userId, int recoveryAgentUid, String alias)
790            throws Exception{
791        SecretKey applicationKey = generateKey();
792        mRecoverableKeyStoreDb.setServerParams(
793                userId, recoveryAgentUid, TEST_VAULT_HANDLE);
794        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, TEST_GENERATION_ID);
795
796        // Newly added key is not synced.
797        mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, recoveryAgentUid, true);
798
799        mRecoverableKeyStoreDb.insertKey(
800                userId,
801                recoveryAgentUid,
802                alias,
803                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
804        return applicationKey;
805    }
806
807    private byte[] decryptThmEncryptedKey(
808            byte[] lockScreenHash, byte[] encryptedKey, byte[] vaultParams) throws Exception {
809        byte[] locallyEncryptedKey = SecureBox.decrypt(
810                TestData.getInsecurePrivateKeyForEndpoint1(),
811                /*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash),
812                /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
813                encryptedKey
814        );
815        return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey);
816    }
817
818    private SecretKey generateKey() throws Exception {
819        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
820        keyGenerator.init(/*keySize=*/ 256);
821        return keyGenerator.generateKey();
822    }
823
824    private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
825        KeyGenerator keyGenerator = KeyGenerator.getInstance(
826                KEY_ALGORITHM,
827                ANDROID_KEY_STORE_PROVIDER);
828        keyGenerator.init(new KeyGenParameterSpec.Builder(
829                WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
830                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
831                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
832                .build());
833        return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
834    }
835
836    private static byte[] utf8Bytes(String s) {
837        return s.getBytes(StandardCharsets.UTF_8);
838    }
839
840    private static byte[] randomBytes(int n) {
841        byte[] bytes = new byte[n];
842        new Random().nextBytes(bytes);
843        return bytes;
844    }
845}
846