blob: 49dc9f5a4c8eaff06a119cb4a884000f7647a4b6 [file] [log] [blame]
Robert Berrye16fa982017-12-20 15:59:37 +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
19import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
20import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
21
Dmitry Dementyevad884712017-12-20 12:38:36 -080022import static com.google.common.truth.Truth.assertThat;
Robert Berrye16fa982017-12-20 15:59:37 +000023import static org.junit.Assert.assertArrayEquals;
24import static org.junit.Assert.assertEquals;
Robert Berryb9a220b2017-12-21 12:41:01 +000025import static org.junit.Assert.fail;
Robert Berrye16fa982017-12-20 15:59:37 +000026import static org.mockito.ArgumentMatchers.any;
Robert Berrycfc990a2017-12-22 15:54:30 +000027import static org.mockito.ArgumentMatchers.anyInt;
28import static org.mockito.ArgumentMatchers.anyString;
Robert Berrye16fa982017-12-20 15:59:37 +000029import static org.mockito.ArgumentMatchers.eq;
30import static org.mockito.Mockito.times;
31import static org.mockito.Mockito.verify;
Robert Berrycfc990a2017-12-22 15:54:30 +000032import static org.mockito.Mockito.when;
Robert Berrye16fa982017-12-20 15:59:37 +000033
Robert Berrycfc990a2017-12-22 15:54:30 +000034import android.app.KeyguardManager;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -080035import android.app.PendingIntent;
Robert Berrye16fa982017-12-20 15:59:37 +000036import android.content.Context;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -080037import android.content.Intent;
38import android.os.Binder;
Robert Berrye16fa982017-12-20 15:59:37 +000039import android.os.RemoteException;
Dmitry Dementyevad884712017-12-20 12:38:36 -080040import android.os.UserHandle;
Robert Berrye16fa982017-12-20 15:59:37 +000041import android.security.recoverablekeystore.KeyDerivationParameters;
Robert Berryb9a220b2017-12-21 12:41:01 +000042import android.security.recoverablekeystore.KeyEntryRecoveryData;
Robert Berrye16fa982017-12-20 15:59:37 +000043import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
44import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
45import android.support.test.InstrumentationRegistry;
46import android.support.test.filters.SmallTest;
47import android.support.test.runner.AndroidJUnit4;
48
49import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
50import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
51
52import com.google.common.collect.ImmutableList;
Robert Berryb9a220b2017-12-21 12:41:01 +000053import com.google.common.collect.ImmutableMap;
Robert Berrye16fa982017-12-20 15:59:37 +000054
55import org.junit.After;
56import org.junit.Before;
57import org.junit.Test;
58import org.junit.runner.RunWith;
59import org.mockito.Mock;
60import org.mockito.MockitoAnnotations;
61
62import java.io.File;
63import java.nio.charset.StandardCharsets;
Robert Berry4a534ec2017-12-21 15:44:02 +000064import java.util.concurrent.Executors;
Dmitry Dementyevad884712017-12-20 12:38:36 -080065import java.util.Map;
Robert Berryb9a220b2017-12-21 12:41:01 +000066import java.util.Random;
67
Robert Berrycfc990a2017-12-22 15:54:30 +000068import javax.crypto.Cipher;
Robert Berryb9a220b2017-12-21 12:41:01 +000069import javax.crypto.SecretKey;
Robert Berrycfc990a2017-12-22 15:54:30 +000070import javax.crypto.spec.GCMParameterSpec;
Robert Berryb9a220b2017-12-21 12:41:01 +000071import javax.crypto.spec.SecretKeySpec;
Robert Berrye16fa982017-12-20 15:59:37 +000072
73@SmallTest
74@RunWith(AndroidJUnit4.class)
75public class RecoverableKeyStoreManagerTest {
76 private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
77
Robert Berrycfc990a2017-12-22 15:54:30 +000078 private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
Robert Berrye16fa982017-12-20 15:59:37 +000079 private static final String TEST_SESSION_ID = "karlin";
80 private static final byte[] TEST_PUBLIC_KEY = new byte[] {
81 (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
82 (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
83 (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03,
84 (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xb8,
85 (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e,
86 (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07,
87 (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc,
88 (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
89 (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f, (byte) 0xd2,
90 (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55,
91 (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32, (byte) 0x7d,
92 (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1,
93 (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa};
94 private static final byte[] TEST_SALT = getUtf8Bytes("salt");
95 private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
96 private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
97 private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params");
98 private static final int TEST_USER_ID = 10009;
99 private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
Robert Berryb9a220b2017-12-21 12:41:01 +0000100 private static final byte[] RECOVERY_RESPONSE_HEADER =
101 "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
102 private static final String TEST_ALIAS = "nick";
Robert Berrycfc990a2017-12-22 15:54:30 +0000103 private static final int RECOVERABLE_KEY_SIZE_BYTES = 32;
Dmitry Dementyevad884712017-12-20 12:38:36 -0800104 private static final int GENERATION_ID = 1;
105 private static final byte[] NONCE = getUtf8Bytes("nonce");
106 private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial");
Robert Berrycfc990a2017-12-22 15:54:30 +0000107 private static final int GCM_TAG_SIZE_BITS = 128;
Dmitry Dementyevad884712017-12-20 12:38:36 -0800108
Robert Berrye16fa982017-12-20 15:59:37 +0000109 @Mock private Context mMockContext;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800110 @Mock private ListenersStorage mMockListenersStorage;
Robert Berrycfc990a2017-12-22 15:54:30 +0000111 @Mock private KeyguardManager mKeyguardManager;
Robert Berrye16fa982017-12-20 15:59:37 +0000112
113 private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
114 private File mDatabaseFile;
115 private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
116 private RecoverySessionStorage mRecoverySessionStorage;
117
118 @Before
119 public void setUp() {
120 MockitoAnnotations.initMocks(this);
121
122 Context context = InstrumentationRegistry.getTargetContext();
123 mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
124 mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
Robert Berrycfc990a2017-12-22 15:54:30 +0000125
Robert Berrye16fa982017-12-20 15:59:37 +0000126 mRecoverySessionStorage = new RecoverySessionStorage();
Robert Berrycfc990a2017-12-22 15:54:30 +0000127
128 when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
129 when(mMockContext.getSystemServiceName(any())).thenReturn("test");
130 when(mKeyguardManager.isDeviceSecure(anyInt())).thenReturn(true);
131
Robert Berrye16fa982017-12-20 15:59:37 +0000132 mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
133 mMockContext,
134 mRecoverableKeyStoreDb,
Robert Berry4a534ec2017-12-21 15:44:02 +0000135 mRecoverySessionStorage,
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800136 Executors.newSingleThreadExecutor(),
137 mMockListenersStorage);
Robert Berrye16fa982017-12-20 15:59:37 +0000138 }
139
140 @After
141 public void tearDown() {
142 mRecoverableKeyStoreDb.close();
143 mDatabaseFile.delete();
144 }
145
146 @Test
Robert Berrycfc990a2017-12-22 15:54:30 +0000147 public void generateAndStoreKey_storesTheKey() throws Exception {
148 int uid = Binder.getCallingUid();
149
150 mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
151
152 assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
153 }
154
155 @Test
156 public void generateAndStoreKey_returnsAKeyOfAppropriateSize() throws Exception {
157 assertThat(mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS))
158 .hasLength(RECOVERABLE_KEY_SIZE_BYTES);
159 }
160
161 @Test
162 public void generateAndStoreKey_storesTheWrappedFormOfTheReturnedBytes() throws Exception {
163 int uid = Binder.getCallingUid();
164
165 byte[] rawKey = mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
166
167 WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS);
168 PlatformEncryptionKey encryptionKey = PlatformKeyManager.getInstance(
169 mMockContext,
170 mRecoverableKeyStoreDb,
171 Binder.getCallingUserHandle().getIdentifier())
172 .getEncryptKey();
173 Cipher cipher = Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM);
174 cipher.init(
175 Cipher.ENCRYPT_MODE,
176 encryptionKey.getKey(),
177 new GCMParameterSpec(GCM_TAG_SIZE_BITS, wrappedKey.getNonce()));
178 byte[] encryptedBytes = cipher.update(rawKey);
179 assertArrayEquals(encryptedBytes, wrappedKey.getKeyMaterial());
180 }
181
182 @Test
Robert Berrye16fa982017-12-20 15:59:37 +0000183 public void startRecoverySession_checksPermissionFirst() throws Exception {
184 mRecoverableKeyStoreManager.startRecoverySession(
185 TEST_SESSION_ID,
186 TEST_PUBLIC_KEY,
187 TEST_VAULT_PARAMS,
188 TEST_VAULT_CHALLENGE,
Dmitry Dementyevad884712017-12-20 12:38:36 -0800189 ImmutableList.of(
190 new KeyStoreRecoveryMetadata(
191 TYPE_LOCKSCREEN,
192 TYPE_PASSWORD,
193 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
194 TEST_SECRET)),
Robert Berrye16fa982017-12-20 15:59:37 +0000195 TEST_USER_ID);
196
Dmitry Dementyevad884712017-12-20 12:38:36 -0800197 verify(mMockContext, times(1))
198 .enforceCallingOrSelfPermission(
199 eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE), any());
Robert Berrye16fa982017-12-20 15:59:37 +0000200 }
201
202 @Test
203 public void startRecoverySession_storesTheSessionInfo() throws Exception {
204 mRecoverableKeyStoreManager.startRecoverySession(
205 TEST_SESSION_ID,
206 TEST_PUBLIC_KEY,
207 TEST_VAULT_PARAMS,
208 TEST_VAULT_CHALLENGE,
Dmitry Dementyevad884712017-12-20 12:38:36 -0800209 ImmutableList.of(
210 new KeyStoreRecoveryMetadata(
211 TYPE_LOCKSCREEN,
212 TYPE_PASSWORD,
213 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
214 TEST_SECRET)),
Robert Berrye16fa982017-12-20 15:59:37 +0000215 TEST_USER_ID);
216
217 assertEquals(1, mRecoverySessionStorage.size());
Dmitry Dementyevad884712017-12-20 12:38:36 -0800218 RecoverySessionStorage.Entry entry =
219 mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID);
Robert Berrye16fa982017-12-20 15:59:37 +0000220 assertArrayEquals(TEST_SECRET, entry.getLskfHash());
221 assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
222 }
223
224 @Test
225 public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception {
226 try {
227 mRecoverableKeyStoreManager.startRecoverySession(
228 TEST_SESSION_ID,
229 TEST_PUBLIC_KEY,
230 TEST_VAULT_PARAMS,
231 TEST_VAULT_CHALLENGE,
232 ImmutableList.of(),
233 TEST_USER_ID);
Robert Berryb9a220b2017-12-21 12:41:01 +0000234 fail("should have thrown");
Robert Berrye16fa982017-12-20 15:59:37 +0000235 } catch (RemoteException e) {
Dmitry Dementyevad884712017-12-20 12:38:36 -0800236 assertEquals("Only a single KeyStoreRecoveryMetadata is supported", e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000237 }
238 }
239
240 @Test
241 public void startRecoverySession_throwsIfBadKey() throws Exception {
242 try {
243 mRecoverableKeyStoreManager.startRecoverySession(
244 TEST_SESSION_ID,
245 getUtf8Bytes("0"),
246 TEST_VAULT_PARAMS,
247 TEST_VAULT_CHALLENGE,
Dmitry Dementyevad884712017-12-20 12:38:36 -0800248 ImmutableList.of(
249 new KeyStoreRecoveryMetadata(
250 TYPE_LOCKSCREEN,
251 TYPE_PASSWORD,
252 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
253 TEST_SECRET)),
Robert Berrye16fa982017-12-20 15:59:37 +0000254 TEST_USER_ID);
Robert Berryb9a220b2017-12-21 12:41:01 +0000255 fail("should have thrown");
Robert Berrye16fa982017-12-20 15:59:37 +0000256 } catch (RemoteException e) {
Dmitry Dementyevad884712017-12-20 12:38:36 -0800257 assertEquals("Not a valid X509 key", e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000258 }
259 }
260
Robert Berryb9a220b2017-12-21 12:41:01 +0000261 @Test
262 public void recoverKeys_throwsIfNoSessionIsPresent() throws Exception {
263 try {
264 mRecoverableKeyStoreManager.recoverKeys(
265 TEST_SESSION_ID,
266 /*recoveryKeyBlob=*/ randomBytes(32),
267 /*applicationKeys=*/ ImmutableList.of(
268 new KeyEntryRecoveryData(getUtf8Bytes("alias"), randomBytes(32))
269 ),
270 TEST_USER_ID);
271 fail("should have thrown");
272 } catch (RemoteException e) {
273 assertEquals("User 10009 does not have pending session 'karlin'",
274 e.getMessage());
275 }
276 }
277
278 @Test
279 public void recoverKeys_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception {
280 mRecoverableKeyStoreManager.startRecoverySession(
281 TEST_SESSION_ID,
282 TEST_PUBLIC_KEY,
283 TEST_VAULT_PARAMS,
284 TEST_VAULT_CHALLENGE,
285 ImmutableList.of(new KeyStoreRecoveryMetadata(
286 TYPE_LOCKSCREEN,
287 TYPE_PASSWORD,
288 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
289 TEST_SECRET)),
290 TEST_USER_ID);
291
292 try {
293 mRecoverableKeyStoreManager.recoverKeys(
294 TEST_SESSION_ID,
295 /*encryptedRecoveryKey=*/ randomBytes(60),
296 /*applicationKeys=*/ ImmutableList.of(),
297 /*uid=*/ TEST_USER_ID);
298 fail("should have thrown");
299 } catch (RemoteException e) {
300 assertEquals("Failed to decrypt recovery key", e.getMessage());
301 }
302 }
303
304 @Test
305 public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() throws Exception {
306 mRecoverableKeyStoreManager.startRecoverySession(
307 TEST_SESSION_ID,
308 TEST_PUBLIC_KEY,
309 TEST_VAULT_PARAMS,
310 TEST_VAULT_CHALLENGE,
311 ImmutableList.of(new KeyStoreRecoveryMetadata(
312 TYPE_LOCKSCREEN,
313 TYPE_PASSWORD,
314 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
315 TEST_SECRET)),
316 TEST_USER_ID);
317 byte[] keyClaimant = mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID)
318 .getKeyClaimant();
319 SecretKey recoveryKey = randomRecoveryKey();
320 byte[] encryptedClaimResponse = encryptClaimResponse(
321 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
322 KeyEntryRecoveryData badApplicationKey = new KeyEntryRecoveryData(
323 TEST_ALIAS.getBytes(StandardCharsets.UTF_8),
324 randomBytes(32));
325
326 try {
327 mRecoverableKeyStoreManager.recoverKeys(
328 TEST_SESSION_ID,
329 /*encryptedRecoveryKey=*/ encryptedClaimResponse,
330 /*applicationKeys=*/ ImmutableList.of(badApplicationKey),
331 /*uid=*/ TEST_USER_ID);
332 fail("should have thrown");
333 } catch (RemoteException e) {
334 assertEquals("Failed to recover key with alias 'nick'", e.getMessage());
335 }
336 }
337
338 @Test
339 public void recoverKeys_doesNotThrowIfAllIsOk() throws Exception {
340 mRecoverableKeyStoreManager.startRecoverySession(
341 TEST_SESSION_ID,
342 TEST_PUBLIC_KEY,
343 TEST_VAULT_PARAMS,
344 TEST_VAULT_CHALLENGE,
345 ImmutableList.of(new KeyStoreRecoveryMetadata(
346 TYPE_LOCKSCREEN,
347 TYPE_PASSWORD,
348 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
349 TEST_SECRET)),
350 TEST_USER_ID);
351 byte[] keyClaimant = mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID)
352 .getKeyClaimant();
353 SecretKey recoveryKey = randomRecoveryKey();
354 byte[] encryptedClaimResponse = encryptClaimResponse(
355 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
356 KeyEntryRecoveryData applicationKey = new KeyEntryRecoveryData(
357 TEST_ALIAS.getBytes(StandardCharsets.UTF_8),
358 randomEncryptedApplicationKey(recoveryKey)
359 );
360
361 mRecoverableKeyStoreManager.recoverKeys(
362 TEST_SESSION_ID,
363 encryptedClaimResponse,
364 ImmutableList.of(applicationKey),
365 TEST_USER_ID);
366 }
367
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800368 @Test
369 public void setSnapshotCreatedPendingIntent() throws Exception {
370 int uid = Binder.getCallingUid();
371 PendingIntent intent = PendingIntent.getBroadcast(
372 InstrumentationRegistry.getTargetContext(), /*requestCode=*/1,
373 new Intent(), /*flags=*/ 0);
374 mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, /*userId=*/ 0);
375 verify(mMockListenersStorage).setSnapshotListener(eq(uid), any(PendingIntent.class));
376 }
377
Dmitry Dementyevad884712017-12-20 12:38:36 -0800378 @Test
379 public void setRecoveryStatus_forOneAlias() throws Exception {
380 int userId = UserHandle.getCallingUserId();
381 int uid = Binder.getCallingUid();
382 int status = 100;
383 int status2 = 200;
384 String alias = "key1";
385 WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status);
386 mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
387 Map<String, Integer> statuses =
388 mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
389 assertThat(statuses).hasSize(1);
390 assertThat(statuses).containsEntry(alias, status);
391
392 mRecoverableKeyStoreManager.setRecoveryStatus(
393 /*packageName=*/ null, new String[] {alias}, status2, userId);
394 statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
395 assertThat(statuses).hasSize(1);
396 assertThat(statuses).containsEntry(alias, status2); // updated
397 }
398
399 @Test
400 public void setRecoveryStatus_for2Aliases() throws Exception {
401 int userId = UserHandle.getCallingUserId();
402 int uid = Binder.getCallingUid();
403 int status = 100;
404 int status2 = 200;
405 int status3 = 300;
406 String alias = "key1";
407 String alias2 = "key2";
408 WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status);
409 mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
410 mRecoverableKeyStoreDb.insertKey(userId, uid, alias2, wrappedKey);
411 Map<String, Integer> statuses =
412 mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
413 assertThat(statuses).hasSize(2);
414 assertThat(statuses).containsEntry(alias, status);
415 assertThat(statuses).containsEntry(alias2, status);
416
417 mRecoverableKeyStoreManager.setRecoveryStatus(
418 /*packageName=*/ null, /*aliases=*/ null, status2, userId);
419 statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
420 assertThat(statuses).hasSize(2);
421 assertThat(statuses).containsEntry(alias, status2); // updated
422 assertThat(statuses).containsEntry(alias2, status2); // updated
423
424 mRecoverableKeyStoreManager.setRecoveryStatus(
425 /*packageName=*/ null, new String[] {alias2}, status3, userId);
426
427 statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
428 assertThat(statuses).hasSize(2);
429 assertThat(statuses).containsEntry(alias, status2);
430 assertThat(statuses).containsEntry(alias2, status3); // updated
431
432 mRecoverableKeyStoreManager.setRecoveryStatus(
433 /*packageName=*/ null, new String[] {alias, alias2}, status, userId);
434
435 statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId);
436 assertThat(statuses).hasSize(2);
437 assertThat(statuses).containsEntry(alias, status); // updated
438 assertThat(statuses).containsEntry(alias2, status); // updated
439 }
440
Robert Berryb9a220b2017-12-21 12:41:01 +0000441 private static byte[] randomEncryptedApplicationKey(SecretKey recoveryKey) throws Exception {
442 return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
443 "alias", new SecretKeySpec(randomBytes(32), "AES")
444 )).get("alias");
445 }
446
447 private static byte[] encryptClaimResponse(
448 byte[] keyClaimant,
449 byte[] lskfHash,
450 byte[] vaultParams,
451 SecretKey recoveryKey) throws Exception {
452 byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey(
453 lskfHash, recoveryKey);
454 return SecureBox.encrypt(
455 /*theirPublicKey=*/ null,
456 /*sharedSecret=*/ keyClaimant,
457 /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
458 /*payload=*/ locallyEncryptedRecoveryKey);
459 }
460
461 private static SecretKey randomRecoveryKey() {
462 return new SecretKeySpec(randomBytes(32), "AES");
463 }
464
Robert Berrye16fa982017-12-20 15:59:37 +0000465 private static byte[] getUtf8Bytes(String s) {
466 return s.getBytes(StandardCharsets.UTF_8);
467 }
Robert Berryb9a220b2017-12-21 12:41:01 +0000468
469 private static byte[] randomBytes(int n) {
470 byte[] bytes = new byte[n];
471 new Random().nextBytes(bytes);
472 return bytes;
473 }
Robert Berrye16fa982017-12-20 15:59:37 +0000474}