blob: a0dcfdc5f808dac4f24342410529203217223283 [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
19import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
20import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PATTERN;
21import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PIN;
22
23import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
24import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
25
26import static org.junit.Assert.assertArrayEquals;
27import static org.junit.Assert.assertEquals;
28import static org.junit.Assert.assertFalse;
Robert Berrybd086f12017-12-27 13:29:39 +000029import static org.junit.Assert.assertNull;
Robert Berry4a534ec2017-12-21 15:44:02 +000030import static org.junit.Assert.assertTrue;
Robert Berry91044042017-12-27 12:05:58 +000031import static org.mockito.Mockito.verify;
Robert Berryf0a4bea2017-12-22 13:17:32 +000032import static org.mockito.Mockito.when;
Robert Berry4a534ec2017-12-21 15:44:02 +000033
Robert Berryf0a4bea2017-12-22 13:17:32 +000034import android.content.Context;
35import android.security.keystore.AndroidKeyStoreSecretKey;
36import android.security.keystore.KeyGenParameterSpec;
37import android.security.keystore.KeyProperties;
Robert Berrybd086f12017-12-27 13:29:39 +000038import android.security.recoverablekeystore.KeyDerivationParameters;
39import android.security.recoverablekeystore.KeyEntryRecoveryData;
40import android.security.recoverablekeystore.KeyStoreRecoveryData;
Robert Berryf0a4bea2017-12-22 13:17:32 +000041import android.support.test.InstrumentationRegistry;
Robert Berry4a534ec2017-12-21 15:44:02 +000042import android.support.test.filters.SmallTest;
43import android.support.test.runner.AndroidJUnit4;
44
Robert Berryf0a4bea2017-12-22 13:17:32 +000045import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
Robert Berrybd086f12017-12-27 13:29:39 +000046import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Robert Berryf0a4bea2017-12-22 13:17:32 +000047
48import org.junit.After;
49import org.junit.Before;
Robert Berry4a534ec2017-12-21 15:44:02 +000050import org.junit.Test;
51import org.junit.runner.RunWith;
Robert Berryf0a4bea2017-12-22 13:17:32 +000052import org.mockito.Mock;
53import org.mockito.MockitoAnnotations;
Robert Berry4a534ec2017-12-21 15:44:02 +000054
Robert Berryf0a4bea2017-12-22 13:17:32 +000055import java.io.File;
Robert Berry4a534ec2017-12-21 15:44:02 +000056import java.nio.charset.StandardCharsets;
Robert Berryf0a4bea2017-12-22 13:17:32 +000057import java.security.KeyPair;
Robert Berry4a534ec2017-12-21 15:44:02 +000058import java.util.Arrays;
Robert Berrybd086f12017-12-27 13:29:39 +000059import java.util.List;
Robert Berry4a534ec2017-12-21 15:44:02 +000060import java.util.Random;
61
Robert Berryf0a4bea2017-12-22 13:17:32 +000062import javax.crypto.KeyGenerator;
63import javax.crypto.SecretKey;
64
Robert Berry4a534ec2017-12-21 15:44:02 +000065@SmallTest
66@RunWith(AndroidJUnit4.class)
67public class KeySyncTaskTest {
Robert Berryf0a4bea2017-12-22 13:17:32 +000068 private static final String KEY_ALGORITHM = "AES";
69 private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
70 private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey";
71 private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
72 private static final int TEST_USER_ID = 1000;
73 private static final int TEST_APP_UID = 10009;
Robert Berry91044042017-12-27 12:05:58 +000074 private static final int TEST_RECOVERY_AGENT_UID = 90873;
Robert Berry94ea4e42017-12-28 12:08:30 +000075 private static final long TEST_DEVICE_ID = 13295035643L;
Robert Berryf0a4bea2017-12-22 13:17:32 +000076 private static final String TEST_APP_KEY_ALIAS = "rcleaver";
77 private static final int TEST_GENERATION_ID = 2;
78 private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD;
79 private static final String TEST_CREDENTIAL = "password1234";
80 private static final byte[] THM_ENCRYPTED_RECOVERY_KEY_HEADER =
81 "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
82
Robert Berryf0a4bea2017-12-22 13:17:32 +000083 @Mock private PlatformKeyManager mPlatformKeyManager;
Robert Berry91044042017-12-27 12:05:58 +000084 @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage;
Robert Berryf0a4bea2017-12-22 13:17:32 +000085
Robert Berrybd086f12017-12-27 13:29:39 +000086 private RecoverySnapshotStorage mRecoverySnapshotStorage;
Robert Berryf0a4bea2017-12-22 13:17:32 +000087 private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
88 private File mDatabaseFile;
89 private KeyPair mKeyPair;
90 private AndroidKeyStoreSecretKey mWrappingKey;
91 private PlatformEncryptionKey mEncryptKey;
92
93 private KeySyncTask mKeySyncTask;
94
95 @Before
96 public void setUp() throws Exception {
97 MockitoAnnotations.initMocks(this);
98
99 Context context = InstrumentationRegistry.getTargetContext();
100 mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
101 mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
102 mKeyPair = SecureBox.genKeyPair();
103
Robert Berrybd086f12017-12-27 13:29:39 +0000104 mRecoverySnapshotStorage = new RecoverySnapshotStorage();
105
Robert Berryf0a4bea2017-12-22 13:17:32 +0000106 mKeySyncTask = new KeySyncTask(
107 mRecoverableKeyStoreDb,
Robert Berrybd086f12017-12-27 13:29:39 +0000108 mRecoverySnapshotStorage,
Robert Berry91044042017-12-27 12:05:58 +0000109 mSnapshotListenersStorage,
Robert Berryf0a4bea2017-12-22 13:17:32 +0000110 TEST_USER_ID,
111 TEST_CREDENTIAL_TYPE,
112 TEST_CREDENTIAL,
Robert Berryaa3f4ca2017-12-27 10:53:58 +0000113 () -> mPlatformKeyManager);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000114
115 mWrappingKey = generateAndroidKeyStoreKey();
116 mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey);
Bo Zhu3462c832018-01-04 22:42:36 -0800117 when(mPlatformKeyManager.getDecryptKey(TEST_USER_ID)).thenReturn(
Robert Berryf0a4bea2017-12-22 13:17:32 +0000118 new PlatformDecryptionKey(TEST_GENERATION_ID, mWrappingKey));
119 }
120
121 @After
122 public void tearDown() {
123 mRecoverableKeyStoreDb.close();
124 mDatabaseFile.delete();
125 }
Robert Berry4a534ec2017-12-21 15:44:02 +0000126
127 @Test
128 public void isPin_isTrueForNumericString() {
129 assertTrue(KeySyncTask.isPin("3298432574398654376547"));
130 }
131
132 @Test
133 public void isPin_isFalseForStringContainingLetters() {
134 assertFalse(KeySyncTask.isPin("398i54369548654"));
135 }
136
137 @Test
138 public void isPin_isFalseForStringContainingSymbols() {
139 assertFalse(KeySyncTask.isPin("-3987543643"));
140 }
141
142 @Test
143 public void hashCredentials_returnsSameHashForSameCredentialsAndSalt() {
144 String credentials = "password1234";
145 byte[] salt = randomBytes(16);
146
147 assertArrayEquals(
148 KeySyncTask.hashCredentials(salt, credentials),
149 KeySyncTask.hashCredentials(salt, credentials));
150 }
151
152 @Test
153 public void hashCredentials_returnsDifferentHashForDifferentCredentials() {
154 byte[] salt = randomBytes(16);
155
156 assertFalse(
157 Arrays.equals(
158 KeySyncTask.hashCredentials(salt, "password1234"),
159 KeySyncTask.hashCredentials(salt, "password12345")));
160 }
161
162 @Test
163 public void hashCredentials_returnsDifferentHashForDifferentSalt() {
164 String credentials = "wowmuch";
165
166 assertFalse(
167 Arrays.equals(
168 KeySyncTask.hashCredentials(randomBytes(64), credentials),
169 KeySyncTask.hashCredentials(randomBytes(64), credentials)));
170 }
171
172 @Test
173 public void hashCredentials_returnsDifferentHashEvenIfConcatIsSame() {
174 assertFalse(
175 Arrays.equals(
176 KeySyncTask.hashCredentials(utf8Bytes("123"), "4567"),
177 KeySyncTask.hashCredentials(utf8Bytes("1234"), "567")));
178 }
179
180 @Test
181 public void getUiFormat_returnsPinIfPin() {
182 assertEquals(TYPE_PIN,
183 KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234"));
184 }
185
186 @Test
187 public void getUiFormat_returnsPasswordIfPassword() {
188 assertEquals(TYPE_PASSWORD,
189 KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a"));
190 }
191
192 @Test
193 public void getUiFormat_returnsPatternIfPattern() {
194 assertEquals(TYPE_PATTERN,
195 KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234"));
196
197 }
198
Robert Berryf0a4bea2017-12-22 13:17:32 +0000199 @Test
200 public void run_doesNotSendAnythingIfNoKeysToSync() throws Exception {
201 // TODO: proper test here, once we have proper implementation for checking that keys need
202 // to be synced.
203 mKeySyncTask.run();
204
Robert Berrybd086f12017-12-27 13:29:39 +0000205 assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
Robert Berryf0a4bea2017-12-22 13:17:32 +0000206 }
207
208 @Test
Robert Berry91044042017-12-27 12:05:58 +0000209 public void run_doesNotSendAnythingIfNoRecoveryAgentSet() throws Exception {
210 SecretKey applicationKey = generateKey();
211 mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
212 mRecoverableKeyStoreDb.insertKey(
213 TEST_USER_ID,
214 TEST_APP_UID,
215 TEST_APP_KEY_ALIAS,
216 WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
217 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
218
219 mKeySyncTask.run();
220
221 assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
222 }
223
224 @Test
225 public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception {
226 SecretKey applicationKey = generateKey();
Robert Berry94ea4e42017-12-28 12:08:30 +0000227 mRecoverableKeyStoreDb.setServerParameters(
228 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID);
Robert Berry91044042017-12-27 12:05:58 +0000229 mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
230 mRecoverableKeyStoreDb.insertKey(
231 TEST_USER_ID,
232 TEST_APP_UID,
233 TEST_APP_KEY_ALIAS,
234 WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
235 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
236 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
237
238 mKeySyncTask.run();
239
240 assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
241 }
242
243 @Test
Robert Berry94ea4e42017-12-28 12:08:30 +0000244 public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception {
245 SecretKey applicationKey = generateKey();
246 mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
247 mRecoverableKeyStoreDb.insertKey(
248 TEST_USER_ID,
249 TEST_APP_UID,
250 TEST_APP_KEY_ALIAS,
251 WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
252 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
253 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
254 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
255
256 mKeySyncTask.run();
257
258 assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
259 }
260
261 @Test
Robert Berryf0a4bea2017-12-22 13:17:32 +0000262 public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception {
263 SecretKey applicationKey = generateKey();
Robert Berry94ea4e42017-12-28 12:08:30 +0000264 mRecoverableKeyStoreDb.setServerParameters(
265 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000266 mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
267 mRecoverableKeyStoreDb.insertKey(
268 TEST_USER_ID,
269 TEST_APP_UID,
270 TEST_APP_KEY_ALIAS,
271 WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
Robert Berry91044042017-12-27 12:05:58 +0000272 mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
273 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
274 when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000275
276 mKeySyncTask.run();
277
Robert Berrybd086f12017-12-27 13:29:39 +0000278 KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_USER_ID);
279 KeyDerivationParameters keyDerivationParameters =
280 recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters();
281 assertEquals(KeyDerivationParameters.ALGORITHM_SHA256,
282 keyDerivationParameters.getAlgorithm());
Robert Berry91044042017-12-27 12:05:58 +0000283 verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
Robert Berryf0a4bea2017-12-22 13:17:32 +0000284 byte[] lockScreenHash = KeySyncTask.hashCredentials(
Robert Berrybd086f12017-12-27 13:29:39 +0000285 keyDerivationParameters.getSalt(),
286 TEST_CREDENTIAL);
Robert Berry94ea4e42017-12-28 12:08:30 +0000287 // TODO: what should counter_id be here?
Robert Berryf0a4bea2017-12-22 13:17:32 +0000288 byte[] recoveryKey = decryptThmEncryptedKey(
289 lockScreenHash,
Robert Berrybd086f12017-12-27 13:29:39 +0000290 recoveryData.getEncryptedRecoveryKeyBlob(),
Robert Berry94ea4e42017-12-28 12:08:30 +0000291 /*vaultParams=*/ KeySyncUtils.packVaultParams(
292 mKeyPair.getPublic(),
293 /*counterId=*/ 1,
294 /*maxAttempts=*/ 10,
295 TEST_DEVICE_ID));
Robert Berrybd086f12017-12-27 13:29:39 +0000296 List<KeyEntryRecoveryData> applicationKeys = recoveryData.getApplicationKeyBlobs();
Robert Berryf0a4bea2017-12-22 13:17:32 +0000297 assertEquals(1, applicationKeys.size());
Robert Berrybd086f12017-12-27 13:29:39 +0000298 KeyEntryRecoveryData keyData = applicationKeys.get(0);
299 assertArrayEquals(TEST_APP_KEY_ALIAS.getBytes(StandardCharsets.UTF_8), keyData.getAlias());
Robert Berryf0a4bea2017-12-22 13:17:32 +0000300 byte[] appKey = KeySyncUtils.decryptApplicationKey(
Robert Berrybd086f12017-12-27 13:29:39 +0000301 recoveryKey, keyData.getEncryptedKeyMaterial());
Robert Berryf0a4bea2017-12-22 13:17:32 +0000302 assertArrayEquals(applicationKey.getEncoded(), appKey);
303 }
304
305 private byte[] decryptThmEncryptedKey(
306 byte[] lockScreenHash, byte[] encryptedKey, byte[] vaultParams) throws Exception {
307 byte[] locallyEncryptedKey = SecureBox.decrypt(
308 mKeyPair.getPrivate(),
309 /*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash),
310 /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
311 encryptedKey
312 );
313 return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey);
314 }
315
316 private SecretKey generateKey() throws Exception {
317 KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
318 keyGenerator.init(/*keySize=*/ 256);
319 return keyGenerator.generateKey();
320 }
321
322 private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
323 KeyGenerator keyGenerator = KeyGenerator.getInstance(
324 KEY_ALGORITHM,
325 ANDROID_KEY_STORE_PROVIDER);
326 keyGenerator.init(new KeyGenParameterSpec.Builder(
327 WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
328 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
329 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
330 .build());
331 return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
332 }
333
Robert Berry4a534ec2017-12-21 15:44:02 +0000334 private static byte[] utf8Bytes(String s) {
335 return s.getBytes(StandardCharsets.UTF_8);
336 }
337
338 private static byte[] randomBytes(int n) {
339 byte[] bytes = new byte[n];
340 new Random().nextBytes(bytes);
341 return bytes;
342 }
343}