blob: e9289e5cadf36801011c3639ca59fb56125c406c [file] [log] [blame]
Robert Berry4a534ec2017-12-21 15:44:02 +00001/*
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
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080019import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;
Dmitry Dementyev122bfe12018-01-10 18:56:36 -080020
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080021import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PASSWORD;
22import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PATTERN;
23import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PIN;
Robert Berry4a534ec2017-12-21 15:44:02 +000024
25import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
26import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
27
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080028import static com.google.common.truth.Truth.assertThat;
29
Robert Berry4a534ec2017-12-21 15:44:02 +000030import static org.junit.Assert.assertArrayEquals;
31import static org.junit.Assert.assertEquals;
32import static org.junit.Assert.assertFalse;
Robert Berrybd086f12017-12-27 13:29:39 +000033import static org.junit.Assert.assertNull;
Robert Berry4a534ec2017-12-21 15:44:02 +000034import static org.junit.Assert.assertTrue;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080035import static org.mockito.Mockito.never;
Robert Berry91044042017-12-27 12:05:58 +000036import static org.mockito.Mockito.verify;
Robert Berryf0a4bea2017-12-22 13:17:32 +000037import static org.mockito.Mockito.when;
Robert Berry4a534ec2017-12-21 15:44:02 +000038
Robert Berryf0a4bea2017-12-22 13:17:32 +000039import android.content.Context;
40import android.security.keystore.AndroidKeyStoreSecretKey;
41import android.security.keystore.KeyGenParameterSpec;
42import android.security.keystore.KeyProperties;
Robert Berry81ee34b2018-01-23 11:59:59 +000043import android.security.keystore.recovery.KeyDerivationParams;
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080044import android.security.keystore.recovery.KeyChainSnapshot;
Aseem Kumar3326da52018-03-12 18:05:16 -070045import android.security.keystore.recovery.RecoveryController;
Robert Berry81ee34b2018-01-23 11:59:59 +000046import android.security.keystore.recovery.WrappedApplicationKey;
Robert Berryf0a4bea2017-12-22 13:17:32 +000047import android.support.test.InstrumentationRegistry;
Robert Berry4a534ec2017-12-21 15:44:02 +000048import android.support.test.filters.SmallTest;
49import android.support.test.runner.AndroidJUnit4;
50
Aseem Kumar3326da52018-03-12 18:05:16 -070051import android.util.Log;
Robert Berryf0a4bea2017-12-22 13:17:32 +000052import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
Robert Berrybd086f12017-12-27 13:29:39 +000053import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Robert Berryf0a4bea2017-12-22 13:17:32 +000054
55import org.junit.After;
56import org.junit.Before;
Robert Berry4a534ec2017-12-21 15:44:02 +000057import org.junit.Test;
58import org.junit.runner.RunWith;
Robert Berryf0a4bea2017-12-22 13:17:32 +000059import org.mockito.Mock;
60import org.mockito.MockitoAnnotations;
Robert Berry4a534ec2017-12-21 15:44:02 +000061
Robert Berryf0a4bea2017-12-22 13:17:32 +000062import java.io.File;
Robert Berry4a534ec2017-12-21 15:44:02 +000063import java.nio.charset.StandardCharsets;
Robert Berryf0a4bea2017-12-22 13:17:32 +000064import java.security.KeyPair;
Robert Berry4a534ec2017-12-21 15:44:02 +000065import java.util.Arrays;
Robert Berrybd086f12017-12-27 13:29:39 +000066import java.util.List;
Robert Berry4a534ec2017-12-21 15:44:02 +000067import java.util.Random;
68
Robert Berryf0a4bea2017-12-22 13:17:32 +000069import javax.crypto.KeyGenerator;
70import javax.crypto.SecretKey;
71
Robert Berry4a534ec2017-12-21 15:44:02 +000072@SmallTest
73@RunWith(AndroidJUnit4.class)
74public class KeySyncTaskTest {
Robert Berryf0a4bea2017-12-22 13:17:32 +000075 private static final String KEY_ALGORITHM = "AES";
76 private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
77 private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey";
78 private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
79 private static final int TEST_USER_ID = 1000;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -080080 private static final int TEST_RECOVERY_AGENT_UID = 10009;
81 private static final int TEST_RECOVERY_AGENT_UID2 = 10010;
Bo Zhu4ff2b3f2018-01-17 17:34:26 -080082 private static final byte[] TEST_VAULT_HANDLE =
Bo Zhu31ccba12018-01-18 11:53:57 -080083 new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17};
Robert Berryf0a4bea2017-12-22 13:17:32 +000084 private static final String TEST_APP_KEY_ALIAS = "rcleaver";
85 private static final int TEST_GENERATION_ID = 2;
86 private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD;
87 private static final String TEST_CREDENTIAL = "password1234";
88 private static final byte[] THM_ENCRYPTED_RECOVERY_KEY_HEADER =
89 "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
90
Robert Berryf0a4bea2017-12-22 13:17:32 +000091 @Mock private PlatformKeyManager mPlatformKeyManager;
Robert Berry91044042017-12-27 12:05:58 +000092 @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage;
Robert Berryf0a4bea2017-12-22 13:17:32 +000093
Robert Berrybd086f12017-12-27 13:29:39 +000094 private RecoverySnapshotStorage mRecoverySnapshotStorage;
Robert Berryf0a4bea2017-12-22 13:17:32 +000095 private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
96 private File mDatabaseFile;
97 private KeyPair mKeyPair;
98 private AndroidKeyStoreSecretKey mWrappingKey;
99 private PlatformEncryptionKey mEncryptKey;
100
101 private KeySyncTask mKeySyncTask;
102
103 @Before
104 public void setUp() throws Exception {
105 MockitoAnnotations.initMocks(this);
106
107 Context context = InstrumentationRegistry.getTargetContext();
108 mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
109 mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
110 mKeyPair = SecureBox.genKeyPair();
111
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800112 mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
113 new int[] {TYPE_LOCKSCREEN});
114 mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2,
115 new int[] {TYPE_LOCKSCREEN});
Robert Berrybd086f12017-12-27 13:29:39 +0000116 mRecoverySnapshotStorage = new RecoverySnapshotStorage();
117
Robert Berryf0a4bea2017-12-22 13:17:32 +0000118 mKeySyncTask = new KeySyncTask(
119 mRecoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +0000120 mRecoverySnapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +0000121 mSnapshotListenersStorage,
Robert Berryf0a4bea2017-12-22 13:17:32 +0000122 TEST_USER_ID,
123 TEST_CREDENTIAL_TYPE,
124 TEST_CREDENTIAL,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800125 /*credentialUpdated=*/ false,
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800126 mPlatformKeyManager);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000127
128 mWrappingKey = generateAndroidKeyStoreKey();
129 mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey);
Bo Zhu3462c832018-01-04 22:42:36 -0800130 when(mPlatformKeyManager.getDecryptKey(TEST_USER_ID)).thenReturn(
Robert Berryf0a4bea2017-12-22 13:17:32 +0000131 new PlatformDecryptionKey(TEST_GENERATION_ID, mWrappingKey));
132 }
133
134 @After
135 public void tearDown() {
136 mRecoverableKeyStoreDb.close();
137 mDatabaseFile.delete();
138 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000139
140 @Test
141 public void isPin_isTrueForNumericString() {
142 assertTrue(KeySyncTask.isPin("3298432574398654376547"));
143 }
144
145 @Test
146 public void isPin_isFalseForStringContainingLetters() {
147 assertFalse(KeySyncTask.isPin("398i54369548654"));
148 }
149
150 @Test
151 public void isPin_isFalseForStringContainingSymbols() {
152 assertFalse(KeySyncTask.isPin("-3987543643"));
153 }
154
155 @Test
156 public void hashCredentials_returnsSameHashForSameCredentialsAndSalt() {
157 String credentials = "password1234";
158 byte[] salt = randomBytes(16);
159
160 assertArrayEquals(
161 KeySyncTask.hashCredentials(salt, credentials),
162 KeySyncTask.hashCredentials(salt, credentials));
163 }
164
165 @Test
166 public void hashCredentials_returnsDifferentHashForDifferentCredentials() {
167 byte[] salt = randomBytes(16);
168
169 assertFalse(
170 Arrays.equals(
171 KeySyncTask.hashCredentials(salt, "password1234"),
172 KeySyncTask.hashCredentials(salt, "password12345")));
173 }
174
175 @Test
176 public void hashCredentials_returnsDifferentHashForDifferentSalt() {
177 String credentials = "wowmuch";
178
179 assertFalse(
180 Arrays.equals(
181 KeySyncTask.hashCredentials(randomBytes(64), credentials),
182 KeySyncTask.hashCredentials(randomBytes(64), credentials)));
183 }
184
185 @Test
186 public void hashCredentials_returnsDifferentHashEvenIfConcatIsSame() {
187 assertFalse(
188 Arrays.equals(
189 KeySyncTask.hashCredentials(utf8Bytes("123"), "4567"),
190 KeySyncTask.hashCredentials(utf8Bytes("1234"), "567")));
191 }
192
193 @Test
194 public void getUiFormat_returnsPinIfPin() {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800195 assertEquals(UI_FORMAT_PIN,
Robert Berry4a534ec2017-12-21 15:44:02 +0000196 KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234"));
197 }
198
199 @Test
200 public void getUiFormat_returnsPasswordIfPassword() {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800201 assertEquals(UI_FORMAT_PASSWORD,
Robert Berry4a534ec2017-12-21 15:44:02 +0000202 KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a"));
203 }
204
205 @Test
206 public void getUiFormat_returnsPatternIfPattern() {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800207 assertEquals(UI_FORMAT_PATTERN,
Robert Berry4a534ec2017-12-21 15:44:02 +0000208 KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234"));
209
210 }
211
Robert Berryf0a4bea2017-12-22 13:17:32 +0000212 @Test
213 public void run_doesNotSendAnythingIfNoKeysToSync() throws Exception {
Robert Berryf0a4bea2017-12-22 13:17:32 +0000214 mKeySyncTask.run();
215
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800216 assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
217 }
218
219 @Test
220 public void run_doesNotSendAnythingIfSnapshotIsUpToDate() throws Exception {
221 mKeySyncTask.run();
222
223 assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
Robert Berryf0a4bea2017-12-22 13:17:32 +0000224 }
225
226 @Test
Robert Berry91044042017-12-27 12:05:58 +0000227 public void run_doesNotSendAnythingIfNoRecoveryAgentSet() throws Exception {
228 SecretKey applicationKey = generateKey();
229 mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
230 mRecoverableKeyStoreDb.insertKey(
231 TEST_USER_ID,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800232 TEST_RECOVERY_AGENT_UID,
Robert Berry91044042017-12-27 12:05:58 +0000233 TEST_APP_KEY_ALIAS,
234 WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
235 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
236
237 mKeySyncTask.run();
238
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800239 assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
Robert Berry91044042017-12-27 12:05:58 +0000240 }
241
242 @Test
243 public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception {
244 SecretKey applicationKey = generateKey();
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800245 mRecoverableKeyStoreDb.setServerParams(
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800246 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
Robert Berry91044042017-12-27 12:05:58 +0000247 mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
248 mRecoverableKeyStoreDb.insertKey(
249 TEST_USER_ID,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800250 TEST_RECOVERY_AGENT_UID,
Robert Berry91044042017-12-27 12:05:58 +0000251 TEST_APP_KEY_ALIAS,
252 WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
253 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
254 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
255
256 mKeySyncTask.run();
257
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800258 assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
Robert Berry91044042017-12-27 12:05:58 +0000259 }
260
261 @Test
Robert Berry94ea4e42017-12-28 12:08:30 +0000262 public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception {
263 SecretKey applicationKey = generateKey();
264 mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
265 mRecoverableKeyStoreDb.insertKey(
266 TEST_USER_ID,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800267 TEST_RECOVERY_AGENT_UID,
Robert Berry94ea4e42017-12-28 12:08:30 +0000268 TEST_APP_KEY_ALIAS,
269 WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
270 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
271 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
272 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
273
274 mKeySyncTask.run();
275
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800276 assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
Robert Berry94ea4e42017-12-28 12:08:30 +0000277 }
278
279 @Test
Bo Zhu14d993d2018-02-03 21:38:48 -0800280 public void run_sendsEncryptedKeysIfAvailableToSync_withRawPublicKey() throws Exception {
Robert Berry91044042017-12-27 12:05:58 +0000281 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
282 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800283
284 mRecoverableKeyStoreDb.setServerParams(
285 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
Robert Berry91044042017-12-27 12:05:58 +0000286 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800287 SecretKey applicationKey =
288 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800289
Robert Berryf0a4bea2017-12-22 13:17:32 +0000290 mKeySyncTask.run();
291
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800292 KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
293 KeyDerivationParams keyDerivationParams =
294 keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams();
295 assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800296 KeyDerivationParams.ALGORITHM_SHA256);
Robert Berry91044042017-12-27 12:05:58 +0000297 verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000298 byte[] lockScreenHash = KeySyncTask.hashCredentials(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800299 keyDerivationParams.getSalt(),
Robert Berrybd086f12017-12-27 13:29:39 +0000300 TEST_CREDENTIAL);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800301 Long counterId = mRecoverableKeyStoreDb.getCounterId(TEST_USER_ID, TEST_RECOVERY_AGENT_UID);
Dmitry Dementyevaea1e392018-01-18 16:57:01 -0800302 counterId = 1L; // TODO: use value from the database.
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800303 assertThat(counterId).isNotNull();
Robert Berryf0a4bea2017-12-22 13:17:32 +0000304 byte[] recoveryKey = decryptThmEncryptedKey(
305 lockScreenHash,
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800306 keyChainSnapshot.getEncryptedRecoveryKeyBlob(),
Robert Berry94ea4e42017-12-28 12:08:30 +0000307 /*vaultParams=*/ KeySyncUtils.packVaultParams(
308 mKeyPair.getPublic(),
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800309 counterId,
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800310 /*maxAttempts=*/ 10,
311 TEST_VAULT_HANDLE));
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800312 List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800313 assertThat(applicationKeys).hasSize(1);
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800314 assertThat(keyChainSnapshot.getCounterId()).isEqualTo(counterId);
315 assertThat(keyChainSnapshot.getMaxAttempts()).isEqualTo(10);
316 assertThat(keyChainSnapshot.getTrustedHardwarePublicKey())
Dmitry Dementyevadd1bad2018-01-18 16:44:08 -0800317 .isEqualTo(SecureBox.encodePublicKey(mKeyPair.getPublic()));
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800318 assertThat(keyChainSnapshot.getServerParams()).isEqualTo(TEST_VAULT_HANDLE);
Robert Berry5f138702018-01-17 15:18:05 +0000319 WrappedApplicationKey keyData = applicationKeys.get(0);
Dmitry Dementyev07c765552018-01-08 17:31:59 -0800320 assertEquals(TEST_APP_KEY_ALIAS, keyData.getAlias());
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800321 assertThat(keyData.getAlias()).isEqualTo(keyData.getAlias());
Robert Berryf0a4bea2017-12-22 13:17:32 +0000322 byte[] appKey = KeySyncUtils.decryptApplicationKey(
Robert Berrybd086f12017-12-27 13:29:39 +0000323 recoveryKey, keyData.getEncryptedKeyMaterial());
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800324 assertThat(appKey).isEqualTo(applicationKey.getEncoded());
325 }
326
327 @Test
Bo Zhu14d993d2018-02-03 21:38:48 -0800328 public void run_sendsEncryptedKeysIfAvailableToSync_withCertPath() throws Exception {
329 mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
330 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
331 mRecoverableKeyStoreDb.setServerParams(
332 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
333 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
334 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
335
336 mKeySyncTask.run();
337
338 KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
339 verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
340 List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys();
341 assertThat(applicationKeys).hasSize(1);
342 assertThat(keyChainSnapshot.getTrustedHardwarePublicKey())
343 .isEqualTo(SecureBox.encodePublicKey(
344 TestData.CERT_PATH_1.getCertificates().get(0).getPublicKey()));
345 }
346
347 @Test
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800348 public void run_setsCorrectSnapshotVersion() throws Exception {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800349 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
350 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
351 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800352 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800353
354 mKeySyncTask.run();
355
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800356 KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
357 assertThat(keyChainSnapshot.getSnapshotVersion()).isEqualTo(1); // default value;
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800358 mRecoverableKeyStoreDb.setShouldCreateSnapshot(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, true);
359
360 mKeySyncTask.run();
361
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800362 keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
363 assertThat(keyChainSnapshot.getSnapshotVersion()).isEqualTo(2); // Updated
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800364 }
365
366 @Test
Dmitry Dementyev907e2752018-01-26 10:54:52 -0800367 public void run_recreatesMissingSnapshot() throws Exception {
368 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
369 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
370 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
371 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
372
373 mKeySyncTask.run();
374
375 KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
376 assertThat(keyChainSnapshot.getSnapshotVersion()).isEqualTo(1); // default value;
377
378 mRecoverySnapshotStorage.remove(TEST_RECOVERY_AGENT_UID); // corrupt snapshot.
379
380 mKeySyncTask.run();
381
382 keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
383 assertThat(keyChainSnapshot.getSnapshotVersion()).isEqualTo(1); // Same version
384 }
385
386 @Test
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800387 public void run_setsCorrectTypeForPassword() throws Exception {
388 mKeySyncTask = new KeySyncTask(
389 mRecoverableKeyStoreDb,
390 mRecoverySnapshotStorage,
391 mSnapshotListenersStorage,
392 TEST_USER_ID,
393 CREDENTIAL_TYPE_PASSWORD,
394 "password",
395 /*credentialUpdated=*/ false,
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800396 mPlatformKeyManager);
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800397
398 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
399 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
400 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
401 SecretKey applicationKey =
402 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
403
404 mKeySyncTask.run();
405
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800406 KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
407 assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
408 assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
409 isEqualTo(UI_FORMAT_PASSWORD);
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800410 }
411
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800412 @Test
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800413 public void run_setsCorrectTypeForPin() throws Exception {
414 mKeySyncTask = new KeySyncTask(
415 mRecoverableKeyStoreDb,
416 mRecoverySnapshotStorage,
417 mSnapshotListenersStorage,
418 TEST_USER_ID,
419 CREDENTIAL_TYPE_PASSWORD,
420 /*credential=*/ "1234",
421 /*credentialUpdated=*/ false,
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800422 mPlatformKeyManager);
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800423
424 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
425 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
426 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
427 SecretKey applicationKey =
428 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
429
430 mKeySyncTask.run();
431
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800432 KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
433 assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800434 // Password with only digits is changed to pin.
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800435 assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
436 isEqualTo(UI_FORMAT_PIN);
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800437 }
438
439 @Test
440 public void run_setsCorrectTypeForPattern() throws Exception {
441 mKeySyncTask = new KeySyncTask(
442 mRecoverableKeyStoreDb,
443 mRecoverySnapshotStorage,
444 mSnapshotListenersStorage,
445 TEST_USER_ID,
446 CREDENTIAL_TYPE_PATTERN,
447 "12345",
448 /*credentialUpdated=*/ false,
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800449 mPlatformKeyManager);
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800450
451 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
452 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
453 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
454 SecretKey applicationKey =
455 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
456
457 mKeySyncTask.run();
458
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800459 KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
460 assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
461 assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
462 isEqualTo(UI_FORMAT_PATTERN);
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800463 }
464
Dmitry Dementyevabd713c2018-01-09 15:08:13 -0800465 @Test
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800466 public void run_sendsEncryptedKeysWithTwoRegisteredAgents() throws Exception {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800467 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
468 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
469 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
470 TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, mKeyPair.getPublic());
471 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
472 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(true);
473 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
474 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_APP_KEY_ALIAS);
475 mKeySyncTask.run();
476
477 verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
478 verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2);
479 }
480
481 @Test
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800482 public void run_sendsEncryptedKeysOnlyForAgentWhichActiveUserSecretType() throws Exception {
483 mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800484 new int[] {TYPE_LOCKSCREEN, 1000});
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800485 // Snapshot will not be created during unlock event.
486 mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2,
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800487 new int[] {1000});
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800488
489 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
490 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
491 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
492 TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, mKeyPair.getPublic());
493 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
Dmitry Dementyev122bfe12018-01-10 18:56:36 -0800494 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(true);
495 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
496 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_APP_KEY_ALIAS);
497 mKeySyncTask.run();
498
499 verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
500 verify(mSnapshotListenersStorage, never()).
501 recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2);
502 }
503
504 @Test
505 public void run_doesNotSendKeyToNonregisteredAgent() throws Exception {
506 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
507 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
508 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
509 TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, mKeyPair.getPublic());
510 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800511 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(false);
512 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
513 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_APP_KEY_ALIAS);
514 mKeySyncTask.run();
515
516 verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
517 verify(mSnapshotListenersStorage, never()).
518 recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2);
519 }
520
Aseem Kumar3326da52018-03-12 18:05:16 -0700521 @Test
522 public void run_customLockScreen_RecoveryStatusFailure() throws Exception {
523 mKeySyncTask = new KeySyncTask(
524 mRecoverableKeyStoreDb,
525 mRecoverySnapshotStorage,
526 mSnapshotListenersStorage,
527 TEST_USER_ID,
528 /*credentialType=*/ 3,
529 "12345",
530 /*credentialUpdated=*/ false,
531 mPlatformKeyManager);
532
533 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
534
535 int status =
536 mRecoverableKeyStoreDb
537 .getStatusForAllKeys(TEST_RECOVERY_AGENT_UID)
538 .get(TEST_APP_KEY_ALIAS);
539 assertEquals(RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS, status);
540
541 mKeySyncTask.run();
542
543 status = mRecoverableKeyStoreDb
544 .getStatusForAllKeys(TEST_RECOVERY_AGENT_UID)
545 .get(TEST_APP_KEY_ALIAS);
546 assertEquals(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE, status);
547 }
548
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800549 private SecretKey addApplicationKey(int userId, int recoveryAgentUid, String alias)
550 throws Exception{
551 SecretKey applicationKey = generateKey();
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800552 mRecoverableKeyStoreDb.setServerParams(
Bo Zhu4ff2b3f2018-01-17 17:34:26 -0800553 userId, recoveryAgentUid, TEST_VAULT_HANDLE);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800554 mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, TEST_GENERATION_ID);
555
556 // Newly added key is not synced.
557 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, recoveryAgentUid, true);
558
559 mRecoverableKeyStoreDb.insertKey(
560 userId,
561 recoveryAgentUid,
562 alias,
563 WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
564 return applicationKey;
Robert Berryf0a4bea2017-12-22 13:17:32 +0000565 }
566
567 private byte[] decryptThmEncryptedKey(
568 byte[] lockScreenHash, byte[] encryptedKey, byte[] vaultParams) throws Exception {
569 byte[] locallyEncryptedKey = SecureBox.decrypt(
570 mKeyPair.getPrivate(),
571 /*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash),
572 /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
573 encryptedKey
574 );
575 return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey);
576 }
577
578 private SecretKey generateKey() throws Exception {
579 KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
580 keyGenerator.init(/*keySize=*/ 256);
581 return keyGenerator.generateKey();
582 }
583
584 private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
585 KeyGenerator keyGenerator = KeyGenerator.getInstance(
586 KEY_ALGORITHM,
587 ANDROID_KEY_STORE_PROVIDER);
588 keyGenerator.init(new KeyGenParameterSpec.Builder(
589 WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
590 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
591 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
592 .build());
593 return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
594 }
595
Robert Berry4a534ec2017-12-21 15:44:02 +0000596 private static byte[] utf8Bytes(String s) {
597 return s.getBytes(StandardCharsets.UTF_8);
598 }
599
600 private static byte[] randomBytes(int n) {
601 byte[] bytes = new byte[n];
602 new Random().nextBytes(bytes);
603 return bytes;
604 }
605}