blob: 967c3b89f2cf513134a83036d1ac8e73772d315d [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;
Dmitry Dementyev14298312018-01-04 15:19:19 -080039import android.os.ServiceSpecificException;
Dmitry Dementyevad884712017-12-20 12:38:36 -080040import android.os.UserHandle;
Bo Zhu3462c832018-01-04 22:42:36 -080041import android.security.keystore.AndroidKeyStoreSecretKey;
42import android.security.keystore.KeyGenParameterSpec;
43import android.security.keystore.KeyProperties;
Robert Berrye16fa982017-12-20 15:59:37 +000044import android.security.recoverablekeystore.KeyDerivationParameters;
Robert Berryb9a220b2017-12-21 12:41:01 +000045import android.security.recoverablekeystore.KeyEntryRecoveryData;
Robert Berrye16fa982017-12-20 15:59:37 +000046import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
47import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
48import android.support.test.InstrumentationRegistry;
49import android.support.test.filters.SmallTest;
50import android.support.test.runner.AndroidJUnit4;
51
52import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
53import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
Robert Berrybd086f12017-12-27 13:29:39 +000054import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Robert Berrye16fa982017-12-20 15:59:37 +000055
56import com.google.common.collect.ImmutableList;
Robert Berryb9a220b2017-12-21 12:41:01 +000057import com.google.common.collect.ImmutableMap;
Robert Berrye16fa982017-12-20 15:59:37 +000058
59import org.junit.After;
60import org.junit.Before;
61import org.junit.Test;
62import org.junit.runner.RunWith;
63import org.mockito.Mock;
64import org.mockito.MockitoAnnotations;
65
66import java.io.File;
67import java.nio.charset.StandardCharsets;
Robert Berry4a534ec2017-12-21 15:44:02 +000068import java.util.concurrent.Executors;
Dmitry Dementyevad884712017-12-20 12:38:36 -080069import java.util.Map;
Robert Berryb9a220b2017-12-21 12:41:01 +000070import java.util.Random;
71
Bo Zhu3462c832018-01-04 22:42:36 -080072import javax.crypto.KeyGenerator;
Robert Berryb9a220b2017-12-21 12:41:01 +000073import javax.crypto.SecretKey;
74import javax.crypto.spec.SecretKeySpec;
Robert Berrye16fa982017-12-20 15:59:37 +000075
76@SmallTest
77@RunWith(AndroidJUnit4.class)
78public class RecoverableKeyStoreManagerTest {
79 private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
80
81 private static final String TEST_SESSION_ID = "karlin";
82 private static final byte[] TEST_PUBLIC_KEY = new byte[] {
83 (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
84 (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
85 (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03,
86 (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xb8,
87 (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e,
88 (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07,
89 (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc,
90 (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
91 (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f, (byte) 0xd2,
92 (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55,
93 (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32, (byte) 0x7d,
94 (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1,
95 (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa};
96 private static final byte[] TEST_SALT = getUtf8Bytes("salt");
97 private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
98 private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
Bo Zhudef7ffd2018-01-05 14:50:52 -080099 private static final byte[] TEST_VAULT_PARAMS = new byte[] {
100 // backend_key
101 (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d,
102 (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda,
103 (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6,
104 (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95,
105 (byte) 0x0f, (byte) 0x10, (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0,
106 (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d,
107 (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b,
108 (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5,
109 (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78,
110 (byte) 0x52, (byte) 0xfa,
111 // counter_id
112 (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00,
113 (byte) 0x00,
114 // device_parameter
115 (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00, (byte) 0x00, (byte) 0x00,
116 (byte) 0x0,
117 // max_attempts
118 (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00};
Bo Zhu3462c832018-01-04 22:42:36 -0800119 private static final int TEST_GENERATION_ID = 2;
120 private static final int TEST_USER_ID = 10009;
Robert Berrye16fa982017-12-20 15:59:37 +0000121 private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
Robert Berryb9a220b2017-12-21 12:41:01 +0000122 private static final byte[] RECOVERY_RESPONSE_HEADER =
123 "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
124 private static final String TEST_ALIAS = "nick";
Robert Berrycfc990a2017-12-22 15:54:30 +0000125 private static final int RECOVERABLE_KEY_SIZE_BYTES = 32;
Dmitry Dementyevad884712017-12-20 12:38:36 -0800126 private static final int GENERATION_ID = 1;
127 private static final byte[] NONCE = getUtf8Bytes("nonce");
128 private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial");
Bo Zhu3462c832018-01-04 22:42:36 -0800129 private static final String KEY_ALGORITHM = "AES";
130 private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
131 private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey";
Dmitry Dementyevad884712017-12-20 12:38:36 -0800132
Robert Berrye16fa982017-12-20 15:59:37 +0000133 @Mock private Context mMockContext;
Robert Berry91044042017-12-27 12:05:58 +0000134 @Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
Robert Berrycfc990a2017-12-22 15:54:30 +0000135 @Mock private KeyguardManager mKeyguardManager;
Bo Zhu3462c832018-01-04 22:42:36 -0800136 @Mock private PlatformKeyManager mPlatformKeyManager;
Robert Berrye16fa982017-12-20 15:59:37 +0000137
138 private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
139 private File mDatabaseFile;
140 private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
141 private RecoverySessionStorage mRecoverySessionStorage;
Robert Berrybd086f12017-12-27 13:29:39 +0000142 private RecoverySnapshotStorage mRecoverySnapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800143 private PlatformEncryptionKey mPlatformEncryptionKey;
Robert Berrye16fa982017-12-20 15:59:37 +0000144
145 @Before
Bo Zhu3462c832018-01-04 22:42:36 -0800146 public void setUp() throws Exception {
Robert Berrye16fa982017-12-20 15:59:37 +0000147 MockitoAnnotations.initMocks(this);
148
149 Context context = InstrumentationRegistry.getTargetContext();
150 mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
151 mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
Robert Berrycfc990a2017-12-22 15:54:30 +0000152
Robert Berrye16fa982017-12-20 15:59:37 +0000153 mRecoverySessionStorage = new RecoverySessionStorage();
Robert Berrycfc990a2017-12-22 15:54:30 +0000154
155 when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
156 when(mMockContext.getSystemServiceName(any())).thenReturn("test");
Robert Berry3ae5bea2017-12-27 10:58:03 +0000157 when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
Bo Zhu3462c832018-01-04 22:42:36 -0800158 when(mKeyguardManager.isDeviceSecure(TEST_USER_ID)).thenReturn(true);
159
160 mPlatformEncryptionKey =
161 new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey());
162 when(mPlatformKeyManager.getEncryptKey(anyInt())).thenReturn(mPlatformEncryptionKey);
Robert Berrycfc990a2017-12-22 15:54:30 +0000163
Robert Berrye16fa982017-12-20 15:59:37 +0000164 mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
165 mMockContext,
166 mRecoverableKeyStoreDb,
Robert Berry4a534ec2017-12-21 15:44:02 +0000167 mRecoverySessionStorage,
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800168 Executors.newSingleThreadExecutor(),
Robert Berry91044042017-12-27 12:05:58 +0000169 mRecoverySnapshotStorage,
Bo Zhu3462c832018-01-04 22:42:36 -0800170 mMockListenersStorage,
171 mPlatformKeyManager);
Robert Berrye16fa982017-12-20 15:59:37 +0000172 }
173
174 @After
175 public void tearDown() {
176 mRecoverableKeyStoreDb.close();
177 mDatabaseFile.delete();
178 }
179
180 @Test
Robert Berrycfc990a2017-12-22 15:54:30 +0000181 public void generateAndStoreKey_storesTheKey() throws Exception {
182 int uid = Binder.getCallingUid();
183
184 mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
185
186 assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
187 }
188
189 @Test
190 public void generateAndStoreKey_returnsAKeyOfAppropriateSize() throws Exception {
191 assertThat(mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS))
192 .hasLength(RECOVERABLE_KEY_SIZE_BYTES);
193 }
194
195 @Test
Robert Berry5daccec2018-01-06 19:16:25 +0000196 public void removeKey_removesAKey() throws Exception {
197 int uid = Binder.getCallingUid();
198 mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
199
200 mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
201
202 assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNull();
203 }
204
205 @Test
Robert Berrye16fa982017-12-20 15:59:37 +0000206 public void startRecoverySession_checksPermissionFirst() throws Exception {
207 mRecoverableKeyStoreManager.startRecoverySession(
208 TEST_SESSION_ID,
209 TEST_PUBLIC_KEY,
210 TEST_VAULT_PARAMS,
211 TEST_VAULT_CHALLENGE,
Dmitry Dementyevad884712017-12-20 12:38:36 -0800212 ImmutableList.of(
213 new KeyStoreRecoveryMetadata(
214 TYPE_LOCKSCREEN,
215 TYPE_PASSWORD,
216 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
Dmitry Dementyev14298312018-01-04 15:19:19 -0800217 TEST_SECRET)));
Robert Berrye16fa982017-12-20 15:59:37 +0000218
Dmitry Dementyevad884712017-12-20 12:38:36 -0800219 verify(mMockContext, times(1))
220 .enforceCallingOrSelfPermission(
221 eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE), any());
Robert Berrye16fa982017-12-20 15:59:37 +0000222 }
223
224 @Test
225 public void startRecoverySession_storesTheSessionInfo() throws Exception {
226 mRecoverableKeyStoreManager.startRecoverySession(
227 TEST_SESSION_ID,
228 TEST_PUBLIC_KEY,
229 TEST_VAULT_PARAMS,
230 TEST_VAULT_CHALLENGE,
Dmitry Dementyevad884712017-12-20 12:38:36 -0800231 ImmutableList.of(
232 new KeyStoreRecoveryMetadata(
233 TYPE_LOCKSCREEN,
234 TYPE_PASSWORD,
235 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
Dmitry Dementyev14298312018-01-04 15:19:19 -0800236 TEST_SECRET)));
Robert Berrye16fa982017-12-20 15:59:37 +0000237
238 assertEquals(1, mRecoverySessionStorage.size());
Dmitry Dementyevad884712017-12-20 12:38:36 -0800239 RecoverySessionStorage.Entry entry =
Dmitry Dementyev14298312018-01-04 15:19:19 -0800240 mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID);
Robert Berrye16fa982017-12-20 15:59:37 +0000241 assertArrayEquals(TEST_SECRET, entry.getLskfHash());
242 assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
243 }
244
245 @Test
246 public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception {
247 try {
248 mRecoverableKeyStoreManager.startRecoverySession(
249 TEST_SESSION_ID,
250 TEST_PUBLIC_KEY,
251 TEST_VAULT_PARAMS,
252 TEST_VAULT_CHALLENGE,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800253 ImmutableList.of());
Robert Berryb9a220b2017-12-21 12:41:01 +0000254 fail("should have thrown");
Bo Zhu3462c832018-01-04 22:42:36 -0800255 } catch (ServiceSpecificException e) {
Dmitry Dementyevad884712017-12-20 12:38:36 -0800256 assertEquals("Only a single KeyStoreRecoveryMetadata is supported", e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000257 }
258 }
259
260 @Test
261 public void startRecoverySession_throwsIfBadKey() throws Exception {
262 try {
263 mRecoverableKeyStoreManager.startRecoverySession(
264 TEST_SESSION_ID,
265 getUtf8Bytes("0"),
266 TEST_VAULT_PARAMS,
267 TEST_VAULT_CHALLENGE,
Dmitry Dementyevad884712017-12-20 12:38:36 -0800268 ImmutableList.of(
269 new KeyStoreRecoveryMetadata(
270 TYPE_LOCKSCREEN,
271 TYPE_PASSWORD,
272 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
Dmitry Dementyev14298312018-01-04 15:19:19 -0800273 TEST_SECRET)));
Robert Berryb9a220b2017-12-21 12:41:01 +0000274 fail("should have thrown");
Bo Zhu3462c832018-01-04 22:42:36 -0800275 } catch (ServiceSpecificException e) {
Dmitry Dementyevad884712017-12-20 12:38:36 -0800276 assertEquals("Not a valid X509 key", e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000277 }
278 }
279
Robert Berryb9a220b2017-12-21 12:41:01 +0000280 @Test
Bo Zhudef7ffd2018-01-05 14:50:52 -0800281 public void startRecoverySession_throwsIfPublicKeysMismatch() throws Exception {
282 byte[] vaultParams = TEST_VAULT_PARAMS.clone();
283 vaultParams[1] ^= (byte) 1; // Flip 1 bit
284 try {
285 mRecoverableKeyStoreManager.startRecoverySession(
286 TEST_SESSION_ID,
287 TEST_PUBLIC_KEY,
288 vaultParams,
289 TEST_VAULT_CHALLENGE,
290 ImmutableList.of(
291 new KeyStoreRecoveryMetadata(
292 TYPE_LOCKSCREEN,
293 TYPE_PASSWORD,
294 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
295 TEST_SECRET)));
296 fail("should have thrown");
297 } catch (ServiceSpecificException e) {
298 assertThat(e.getMessage()).contains("do not match");
299 }
300 }
301
302 @Test
Robert Berryb9a220b2017-12-21 12:41:01 +0000303 public void recoverKeys_throwsIfNoSessionIsPresent() throws Exception {
304 try {
305 mRecoverableKeyStoreManager.recoverKeys(
306 TEST_SESSION_ID,
307 /*recoveryKeyBlob=*/ randomBytes(32),
308 /*applicationKeys=*/ ImmutableList.of(
309 new KeyEntryRecoveryData(getUtf8Bytes("alias"), randomBytes(32))
Dmitry Dementyev14298312018-01-04 15:19:19 -0800310 ));
Robert Berryb9a220b2017-12-21 12:41:01 +0000311 fail("should have thrown");
Dmitry Dementyev14298312018-01-04 15:19:19 -0800312 } catch (ServiceSpecificException e) {
313 // expected
Robert Berryb9a220b2017-12-21 12:41:01 +0000314 }
315 }
316
317 @Test
318 public void recoverKeys_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception {
319 mRecoverableKeyStoreManager.startRecoverySession(
320 TEST_SESSION_ID,
321 TEST_PUBLIC_KEY,
322 TEST_VAULT_PARAMS,
323 TEST_VAULT_CHALLENGE,
324 ImmutableList.of(new KeyStoreRecoveryMetadata(
325 TYPE_LOCKSCREEN,
326 TYPE_PASSWORD,
327 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
Dmitry Dementyev14298312018-01-04 15:19:19 -0800328 TEST_SECRET)));
Robert Berryb9a220b2017-12-21 12:41:01 +0000329
330 try {
331 mRecoverableKeyStoreManager.recoverKeys(
332 TEST_SESSION_ID,
333 /*encryptedRecoveryKey=*/ randomBytes(60),
Dmitry Dementyev14298312018-01-04 15:19:19 -0800334 /*applicationKeys=*/ ImmutableList.of());
Robert Berryb9a220b2017-12-21 12:41:01 +0000335 fail("should have thrown");
Dmitry Dementyev14298312018-01-04 15:19:19 -0800336 } catch (ServiceSpecificException e) {
337 assertThat(e.getMessage()).startsWith("Failed to decrypt recovery key");
Robert Berryb9a220b2017-12-21 12:41:01 +0000338 }
339 }
340
341 @Test
342 public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() throws Exception {
343 mRecoverableKeyStoreManager.startRecoverySession(
344 TEST_SESSION_ID,
345 TEST_PUBLIC_KEY,
346 TEST_VAULT_PARAMS,
347 TEST_VAULT_CHALLENGE,
348 ImmutableList.of(new KeyStoreRecoveryMetadata(
349 TYPE_LOCKSCREEN,
350 TYPE_PASSWORD,
351 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
Dmitry Dementyev14298312018-01-04 15:19:19 -0800352 TEST_SECRET)));
353 byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
Robert Berryb9a220b2017-12-21 12:41:01 +0000354 .getKeyClaimant();
355 SecretKey recoveryKey = randomRecoveryKey();
356 byte[] encryptedClaimResponse = encryptClaimResponse(
357 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
358 KeyEntryRecoveryData badApplicationKey = new KeyEntryRecoveryData(
359 TEST_ALIAS.getBytes(StandardCharsets.UTF_8),
360 randomBytes(32));
361
362 try {
363 mRecoverableKeyStoreManager.recoverKeys(
364 TEST_SESSION_ID,
365 /*encryptedRecoveryKey=*/ encryptedClaimResponse,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800366 /*applicationKeys=*/ ImmutableList.of(badApplicationKey));
Robert Berryb9a220b2017-12-21 12:41:01 +0000367 fail("should have thrown");
Bo Zhu3462c832018-01-04 22:42:36 -0800368 } catch (ServiceSpecificException e) {
369 assertThat(e.getMessage()).startsWith("Failed to recover key with alias 'nick'");
Robert Berryb9a220b2017-12-21 12:41:01 +0000370 }
371 }
372
373 @Test
Robert Berrybd4c43c2017-12-22 11:35:14 +0000374 public void recoverKeys_returnsDecryptedKeys() throws Exception {
Robert Berryb9a220b2017-12-21 12:41:01 +0000375 mRecoverableKeyStoreManager.startRecoverySession(
376 TEST_SESSION_ID,
377 TEST_PUBLIC_KEY,
378 TEST_VAULT_PARAMS,
379 TEST_VAULT_CHALLENGE,
380 ImmutableList.of(new KeyStoreRecoveryMetadata(
381 TYPE_LOCKSCREEN,
382 TYPE_PASSWORD,
383 KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
Dmitry Dementyev14298312018-01-04 15:19:19 -0800384 TEST_SECRET)));
385 byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
Robert Berryb9a220b2017-12-21 12:41:01 +0000386 .getKeyClaimant();
387 SecretKey recoveryKey = randomRecoveryKey();
388 byte[] encryptedClaimResponse = encryptClaimResponse(
389 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
Robert Berrybd4c43c2017-12-22 11:35:14 +0000390 byte[] applicationKeyBytes = randomBytes(32);
Robert Berryb9a220b2017-12-21 12:41:01 +0000391 KeyEntryRecoveryData applicationKey = new KeyEntryRecoveryData(
392 TEST_ALIAS.getBytes(StandardCharsets.UTF_8),
Robert Berrybd4c43c2017-12-22 11:35:14 +0000393 encryptedApplicationKey(recoveryKey, applicationKeyBytes));
Robert Berryb9a220b2017-12-21 12:41:01 +0000394
Robert Berrybd4c43c2017-12-22 11:35:14 +0000395 Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys(
Robert Berryb9a220b2017-12-21 12:41:01 +0000396 TEST_SESSION_ID,
397 encryptedClaimResponse,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800398 ImmutableList.of(applicationKey));
Robert Berrybd4c43c2017-12-22 11:35:14 +0000399
400 assertThat(recoveredKeys).hasSize(1);
401 assertThat(recoveredKeys.get(TEST_ALIAS)).isEqualTo(applicationKeyBytes);
Robert Berryb9a220b2017-12-21 12:41:01 +0000402 }
403
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800404 @Test
405 public void setSnapshotCreatedPendingIntent() throws Exception {
406 int uid = Binder.getCallingUid();
407 PendingIntent intent = PendingIntent.getBroadcast(
408 InstrumentationRegistry.getTargetContext(), /*requestCode=*/1,
409 new Intent(), /*flags=*/ 0);
Dmitry Dementyev14298312018-01-04 15:19:19 -0800410 mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent);
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800411 verify(mMockListenersStorage).setSnapshotListener(eq(uid), any(PendingIntent.class));
412 }
413
Dmitry Dementyevad884712017-12-20 12:38:36 -0800414 @Test
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800415 public void setRecoverySecretTypes() throws Exception {
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800416 int[] types1 = new int[]{11, 2000};
417 int[] types2 = new int[]{1, 2, 3};
418 int[] types3 = new int[]{};
419
Dmitry Dementyev14298312018-01-04 15:19:19 -0800420 mRecoverableKeyStoreManager.setRecoverySecretTypes(types1);
421 assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800422 types1);
423
Dmitry Dementyev14298312018-01-04 15:19:19 -0800424 mRecoverableKeyStoreManager.setRecoverySecretTypes(types2);
425 assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800426 types2);
427
Dmitry Dementyev14298312018-01-04 15:19:19 -0800428 mRecoverableKeyStoreManager.setRecoverySecretTypes(types3);
429 assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800430 types3);
431 }
432
433 @Test
Dmitry Dementyevad884712017-12-20 12:38:36 -0800434 public void setRecoveryStatus_forOneAlias() throws Exception {
435 int userId = UserHandle.getCallingUserId();
436 int uid = Binder.getCallingUid();
437 int status = 100;
438 int status2 = 200;
439 String alias = "key1";
440 WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status);
441 mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
442 Map<String, Integer> statuses =
Dmitry Dementyev14298312018-01-04 15:19:19 -0800443 mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
Dmitry Dementyevad884712017-12-20 12:38:36 -0800444 assertThat(statuses).hasSize(1);
445 assertThat(statuses).containsEntry(alias, status);
446
447 mRecoverableKeyStoreManager.setRecoveryStatus(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800448 /*packageName=*/ null, new String[] {alias}, status2);
449 statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
Dmitry Dementyevad884712017-12-20 12:38:36 -0800450 assertThat(statuses).hasSize(1);
451 assertThat(statuses).containsEntry(alias, status2); // updated
452 }
453
454 @Test
455 public void setRecoveryStatus_for2Aliases() throws Exception {
456 int userId = UserHandle.getCallingUserId();
457 int uid = Binder.getCallingUid();
458 int status = 100;
459 int status2 = 200;
460 int status3 = 300;
461 String alias = "key1";
462 String alias2 = "key2";
463 WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status);
464 mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
465 mRecoverableKeyStoreDb.insertKey(userId, uid, alias2, wrappedKey);
466 Map<String, Integer> statuses =
Dmitry Dementyev14298312018-01-04 15:19:19 -0800467 mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
Dmitry Dementyevad884712017-12-20 12:38:36 -0800468 assertThat(statuses).hasSize(2);
469 assertThat(statuses).containsEntry(alias, status);
470 assertThat(statuses).containsEntry(alias2, status);
471
472 mRecoverableKeyStoreManager.setRecoveryStatus(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800473 /*packageName=*/ null, /*aliases=*/ null, status2);
474 statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
Dmitry Dementyevad884712017-12-20 12:38:36 -0800475 assertThat(statuses).hasSize(2);
476 assertThat(statuses).containsEntry(alias, status2); // updated
477 assertThat(statuses).containsEntry(alias2, status2); // updated
478
479 mRecoverableKeyStoreManager.setRecoveryStatus(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800480 /*packageName=*/ null, new String[] {alias2}, status3);
Dmitry Dementyevad884712017-12-20 12:38:36 -0800481
Dmitry Dementyev14298312018-01-04 15:19:19 -0800482 statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
Dmitry Dementyevad884712017-12-20 12:38:36 -0800483 assertThat(statuses).hasSize(2);
484 assertThat(statuses).containsEntry(alias, status2);
485 assertThat(statuses).containsEntry(alias2, status3); // updated
486
487 mRecoverableKeyStoreManager.setRecoveryStatus(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800488 /*packageName=*/ null, new String[] {alias, alias2}, status);
Dmitry Dementyevad884712017-12-20 12:38:36 -0800489
Dmitry Dementyev14298312018-01-04 15:19:19 -0800490 statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
Dmitry Dementyevad884712017-12-20 12:38:36 -0800491 assertThat(statuses).hasSize(2);
492 assertThat(statuses).containsEntry(alias, status); // updated
493 assertThat(statuses).containsEntry(alias2, status); // updated
494 }
495
Robert Berrybd4c43c2017-12-22 11:35:14 +0000496 private static byte[] encryptedApplicationKey(
497 SecretKey recoveryKey, byte[] applicationKey) throws Exception {
Robert Berryb9a220b2017-12-21 12:41:01 +0000498 return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
Robert Berrybd4c43c2017-12-22 11:35:14 +0000499 "alias", new SecretKeySpec(applicationKey, "AES")
Robert Berryb9a220b2017-12-21 12:41:01 +0000500 )).get("alias");
501 }
502
503 private static byte[] encryptClaimResponse(
504 byte[] keyClaimant,
505 byte[] lskfHash,
506 byte[] vaultParams,
507 SecretKey recoveryKey) throws Exception {
508 byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey(
509 lskfHash, recoveryKey);
510 return SecureBox.encrypt(
511 /*theirPublicKey=*/ null,
512 /*sharedSecret=*/ keyClaimant,
513 /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
514 /*payload=*/ locallyEncryptedRecoveryKey);
515 }
516
517 private static SecretKey randomRecoveryKey() {
518 return new SecretKeySpec(randomBytes(32), "AES");
519 }
520
Robert Berrye16fa982017-12-20 15:59:37 +0000521 private static byte[] getUtf8Bytes(String s) {
522 return s.getBytes(StandardCharsets.UTF_8);
523 }
Robert Berryb9a220b2017-12-21 12:41:01 +0000524
525 private static byte[] randomBytes(int n) {
526 byte[] bytes = new byte[n];
527 new Random().nextBytes(bytes);
528 return bytes;
529 }
Bo Zhu3462c832018-01-04 22:42:36 -0800530
531 private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
532 KeyGenerator keyGenerator = KeyGenerator.getInstance(
533 KEY_ALGORITHM,
534 ANDROID_KEY_STORE_PROVIDER);
535 keyGenerator.init(new KeyGenParameterSpec.Builder(
536 WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
537 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
538 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
539 .build());
540 return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
541 }
Robert Berrye16fa982017-12-20 15:59:37 +0000542}