blob: 18a3885aea6ad4a7b6193389b5be607b39c6d4ff [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
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080019import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;
20import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PASSWORD;
Robert Berrye16fa982017-12-20 15:59:37 +000021
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;
Dmitry Dementyeved89ea02018-01-11 13:53:52 -080038import android.Manifest;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -080039import android.os.Binder;
Dmitry Dementyev14298312018-01-04 15:19:19 -080040import android.os.ServiceSpecificException;
Dmitry Dementyevad884712017-12-20 12:38:36 -080041import android.os.UserHandle;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080042import android.security.KeyStore;
Robert Berry4a5c87d2018-03-19 18:00:46 +000043import android.security.keystore.AndroidKeyStoreProvider;
Bo Zhu3462c832018-01-04 22:42:36 -080044import android.security.keystore.AndroidKeyStoreSecretKey;
45import android.security.keystore.KeyGenParameterSpec;
46import android.security.keystore.KeyProperties;
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080047import android.security.keystore.recovery.KeyChainProtectionParams;
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -070048import android.security.keystore.recovery.KeyDerivationParams;
Bo Zhu7c1972f2018-02-22 21:43:52 -080049import android.security.keystore.recovery.RecoveryCertPath;
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -070050import android.security.keystore.recovery.TrustedRootCertificates;
Robert Berry81ee34b2018-01-23 11:59:59 +000051import android.security.keystore.recovery.WrappedApplicationKey;
Robert Berrye16fa982017-12-20 15:59:37 +000052import android.support.test.filters.SmallTest;
Dmitry Dementyeved89ea02018-01-11 13:53:52 -080053import android.support.test.InstrumentationRegistry;
Robert Berrye16fa982017-12-20 15:59:37 +000054import android.support.test.runner.AndroidJUnit4;
55
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080056import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
Robert Berrye16fa982017-12-20 15:59:37 +000057import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
58import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
Robert Berrybd086f12017-12-27 13:29:39 +000059import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Robert Berrye16fa982017-12-20 15:59:37 +000060
61import com.google.common.collect.ImmutableList;
Robert Berryb9a220b2017-12-21 12:41:01 +000062import com.google.common.collect.ImmutableMap;
Robert Berrye16fa982017-12-20 15:59:37 +000063
64import org.junit.After;
65import org.junit.Before;
66import org.junit.Test;
67import org.junit.runner.RunWith;
68import org.mockito.Mock;
69import org.mockito.MockitoAnnotations;
70
71import java.io.File;
72import java.nio.charset.StandardCharsets;
Robert Berry4a5c87d2018-03-19 18:00:46 +000073import java.security.UnrecoverableKeyException;
Bo Zhu7c1972f2018-02-22 21:43:52 -080074import java.security.cert.CertPath;
75import java.security.cert.CertificateFactory;
76import java.security.cert.X509Certificate;
77import java.util.ArrayList;
Robert Berry4a534ec2017-12-21 15:44:02 +000078import java.util.concurrent.Executors;
Dmitry Dementyevad884712017-12-20 12:38:36 -080079import java.util.Map;
Robert Berryb9a220b2017-12-21 12:41:01 +000080import java.util.Random;
81
Robert Berry4a5c87d2018-03-19 18:00:46 +000082import javax.crypto.Cipher;
Bo Zhu3462c832018-01-04 22:42:36 -080083import javax.crypto.KeyGenerator;
Robert Berryb9a220b2017-12-21 12:41:01 +000084import javax.crypto.SecretKey;
Robert Berry4a5c87d2018-03-19 18:00:46 +000085import javax.crypto.spec.GCMParameterSpec;
Robert Berryb9a220b2017-12-21 12:41:01 +000086import javax.crypto.spec.SecretKeySpec;
Robert Berrye16fa982017-12-20 15:59:37 +000087
88@SmallTest
89@RunWith(AndroidJUnit4.class)
90public class RecoverableKeyStoreManagerTest {
91 private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
92
Bo Zhub31ab672018-03-20 22:44:18 -070093 private static final String ROOT_CERTIFICATE_ALIAS = "";
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -070094 private static final String DEFAULT_ROOT_CERT_ALIAS =
95 TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
Robert Berrye16fa982017-12-20 15:59:37 +000096 private static final String TEST_SESSION_ID = "karlin";
97 private static final byte[] TEST_PUBLIC_KEY = new byte[] {
98 (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
99 (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
100 (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03,
101 (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xb8,
102 (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e,
103 (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07,
104 (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc,
105 (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
106 (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f, (byte) 0xd2,
107 (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55,
108 (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32, (byte) 0x7d,
109 (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1,
110 (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa};
111 private static final byte[] TEST_SALT = getUtf8Bytes("salt");
112 private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
113 private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
Bo Zhudef7ffd2018-01-05 14:50:52 -0800114 private static final byte[] TEST_VAULT_PARAMS = new byte[] {
115 // backend_key
116 (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d,
117 (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda,
118 (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6,
119 (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95,
120 (byte) 0x0f, (byte) 0x10, (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0,
121 (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d,
122 (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b,
123 (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5,
124 (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78,
125 (byte) 0x52, (byte) 0xfa,
126 // counter_id
127 (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00,
128 (byte) 0x00,
129 // device_parameter
130 (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00, (byte) 0x00, (byte) 0x00,
131 (byte) 0x0,
132 // max_attempts
133 (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00};
Bo Zhu3462c832018-01-04 22:42:36 -0800134 private static final int TEST_GENERATION_ID = 2;
135 private static final int TEST_USER_ID = 10009;
Robert Berrye16fa982017-12-20 15:59:37 +0000136 private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
Robert Berryb9a220b2017-12-21 12:41:01 +0000137 private static final byte[] RECOVERY_RESPONSE_HEADER =
138 "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
139 private static final String TEST_ALIAS = "nick";
Bo Zhu4857cb52018-02-06 14:34:48 -0800140 private static final String TEST_ALIAS2 = "bob";
Robert Berrycfc990a2017-12-22 15:54:30 +0000141 private static final int RECOVERABLE_KEY_SIZE_BYTES = 32;
Bo Zhu2c8e5382018-02-26 15:54:25 -0800142 private static final int APPLICATION_KEY_SIZE_BYTES = 32;
Dmitry Dementyevad884712017-12-20 12:38:36 -0800143 private static final int GENERATION_ID = 1;
144 private static final byte[] NONCE = getUtf8Bytes("nonce");
145 private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial");
Bo Zhu3462c832018-01-04 22:42:36 -0800146 private static final String KEY_ALGORITHM = "AES";
147 private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
148 private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey";
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700149 private static final String TEST_DEFAULT_ROOT_CERT_ALIAS = "";
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700150 private static final KeyChainProtectionParams TEST_PROTECTION_PARAMS =
151 new KeyChainProtectionParams.Builder()
152 .setUserSecretType(TYPE_LOCKSCREEN)
153 .setLockScreenUiFormat(UI_FORMAT_PASSWORD)
154 .setKeyDerivationParams(KeyDerivationParams.createSha256Params(TEST_SALT))
155 .setSecret(TEST_SECRET)
156 .build();
Dmitry Dementyevad884712017-12-20 12:38:36 -0800157
Robert Berrye16fa982017-12-20 15:59:37 +0000158 @Mock private Context mMockContext;
Robert Berry91044042017-12-27 12:05:58 +0000159 @Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
Robert Berrycfc990a2017-12-22 15:54:30 +0000160 @Mock private KeyguardManager mKeyguardManager;
Bo Zhu3462c832018-01-04 22:42:36 -0800161 @Mock private PlatformKeyManager mPlatformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800162 @Mock private ApplicationKeyStorage mApplicationKeyStorage;
Robert Berrye16fa982017-12-20 15:59:37 +0000163
164 private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
165 private File mDatabaseFile;
166 private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
167 private RecoverySessionStorage mRecoverySessionStorage;
Robert Berrybd086f12017-12-27 13:29:39 +0000168 private RecoverySnapshotStorage mRecoverySnapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800169 private PlatformEncryptionKey mPlatformEncryptionKey;
Robert Berrye16fa982017-12-20 15:59:37 +0000170
171 @Before
Bo Zhu3462c832018-01-04 22:42:36 -0800172 public void setUp() throws Exception {
Robert Berrye16fa982017-12-20 15:59:37 +0000173 MockitoAnnotations.initMocks(this);
174
175 Context context = InstrumentationRegistry.getTargetContext();
176 mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
177 mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
Robert Berrycfc990a2017-12-22 15:54:30 +0000178
Robert Berrye16fa982017-12-20 15:59:37 +0000179 mRecoverySessionStorage = new RecoverySessionStorage();
Robert Berrycfc990a2017-12-22 15:54:30 +0000180
181 when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
182 when(mMockContext.getSystemServiceName(any())).thenReturn("test");
Robert Berry3ae5bea2017-12-27 10:58:03 +0000183 when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
Bo Zhu3462c832018-01-04 22:42:36 -0800184 when(mKeyguardManager.isDeviceSecure(TEST_USER_ID)).thenReturn(true);
185
186 mPlatformEncryptionKey =
187 new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey());
188 when(mPlatformKeyManager.getEncryptKey(anyInt())).thenReturn(mPlatformEncryptionKey);
Robert Berrycfc990a2017-12-22 15:54:30 +0000189
Robert Berrye16fa982017-12-20 15:59:37 +0000190 mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
191 mMockContext,
192 mRecoverableKeyStoreDb,
Robert Berry4a534ec2017-12-21 15:44:02 +0000193 mRecoverySessionStorage,
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800194 Executors.newSingleThreadExecutor(),
Robert Berry91044042017-12-27 12:05:58 +0000195 mRecoverySnapshotStorage,
Bo Zhu3462c832018-01-04 22:42:36 -0800196 mMockListenersStorage,
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800197 mPlatformKeyManager,
198 mApplicationKeyStorage);
Robert Berrye16fa982017-12-20 15:59:37 +0000199 }
200
201 @After
202 public void tearDown() {
203 mRecoverableKeyStoreDb.close();
204 mDatabaseFile.delete();
205 }
206
207 @Test
Robert Berrycfc990a2017-12-22 15:54:30 +0000208 public void generateAndStoreKey_storesTheKey() throws Exception {
209 int uid = Binder.getCallingUid();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800210 int userId = UserHandle.getCallingUserId();
Robert Berrycfc990a2017-12-22 15:54:30 +0000211
212 mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
213
214 assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800215
216 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
Robert Berrycfc990a2017-12-22 15:54:30 +0000217 }
218
219 @Test
220 public void generateAndStoreKey_returnsAKeyOfAppropriateSize() throws Exception {
221 assertThat(mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS))
222 .hasLength(RECOVERABLE_KEY_SIZE_BYTES);
223 }
224
225 @Test
Bo Zhu2c8e5382018-02-26 15:54:25 -0800226 public void importKey_storesTheKey() throws Exception {
227 int uid = Binder.getCallingUid();
228 int userId = UserHandle.getCallingUserId();
229 byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES);
230
231 mRecoverableKeyStoreManager.importKey(TEST_ALIAS, keyMaterial);
232
233 assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
234 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
Robert Berry4a5c87d2018-03-19 18:00:46 +0000235 // TODO(76083050) Test the grant mechanism for the keys.
Bo Zhu2c8e5382018-02-26 15:54:25 -0800236 }
237
238 @Test
239 public void importKey_throwsIfInvalidLength() throws Exception {
240 byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES - 1);
241 try {
242 mRecoverableKeyStoreManager.importKey(TEST_ALIAS, keyMaterial);
243 fail("should have thrown");
244 } catch (ServiceSpecificException e) {
245 assertThat(e.getMessage()).contains("not contain 256 bits");
246 }
247 }
248
249 @Test
250 public void importKey_throwsIfNullKey() throws Exception {
251 try {
252 mRecoverableKeyStoreManager.importKey(TEST_ALIAS, /*keyBytes=*/ null);
253 fail("should have thrown");
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700254 } catch (NullPointerException e) {
255 assertThat(e.getMessage()).contains("is null");
Bo Zhu2c8e5382018-02-26 15:54:25 -0800256 }
257 }
258
259 @Test
Robert Berry5daccec2018-01-06 19:16:25 +0000260 public void removeKey_removesAKey() throws Exception {
261 int uid = Binder.getCallingUid();
262 mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
263
264 mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
265
266 assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNull();
267 }
268
269 @Test
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800270 public void removeKey_updatesShouldCreateSnapshot() throws Exception {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800271 int uid = Binder.getCallingUid();
272 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800273 mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
274 // Pretend that key was synced
275 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
276
277 mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
278
279 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
280 }
281
282 @Test
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800283 public void removeKey_failureDoesNotUpdateShouldCreateSnapshot() throws Exception {
284 int uid = Binder.getCallingUid();
285 int userId = UserHandle.getCallingUserId();
286 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
287 // Key did not exist
288 mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
289
290 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
291 }
292
293 @Test
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800294 public void initRecoveryService_succeedsWithCertFile() throws Exception {
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800295 int uid = Binder.getCallingUid();
296 int userId = UserHandle.getCallingUserId();
Bo Zhu14d993d2018-02-03 21:38:48 -0800297 long certSerial = 1000L;
298 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
299
300 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
301 TestData.getCertXmlWithSerial(certSerial));
302
Robert Berrye8edf972018-03-27 11:45:11 +0100303 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700304 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
305 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1);
306 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
307 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial);
Bo Zhu14d993d2018-02-03 21:38:48 -0800308 assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
309 }
310
311 @Test
Bo Zhu8d6861e2018-03-21 20:45:09 -0700312 public void initRecoveryService_regeneratesCounterId() throws Exception {
313 int uid = Binder.getCallingUid();
314 int userId = UserHandle.getCallingUserId();
315 long certSerial = 1000L;
316
317 Long counterId0 = mRecoverableKeyStoreDb.getCounterId(userId, uid);
318 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
319 TestData.getCertXmlWithSerial(certSerial));
320 Long counterId1 = mRecoverableKeyStoreDb.getCounterId(userId, uid);
321 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
322 TestData.getCertXmlWithSerial(certSerial + 1));
323 Long counterId2 = mRecoverableKeyStoreDb.getCounterId(userId, uid);
324
325 assertThat(!counterId1.equals(counterId0) || !counterId2.equals(counterId1)).isTrue();
326 }
327
328 @Test
Bo Zhu7f414d92018-02-28 09:28:19 -0800329 public void initRecoveryService_throwsIfInvalidCert() throws Exception {
330 byte[] modifiedCertXml = TestData.getCertXml();
331 modifiedCertXml[modifiedCertXml.length - 50] ^= 1; // Flip a bit in the certificate
332 try {
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700333 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
334 modifiedCertXml);
Bo Zhu7f414d92018-02-28 09:28:19 -0800335 fail("should have thrown");
336 } catch (ServiceSpecificException e) {
337 assertThat(e.getMessage()).contains("validate cert");
338 }
339 }
340
341 @Test
Bo Zhu14d993d2018-02-03 21:38:48 -0800342 public void initRecoveryService_updatesWithLargerSerial() throws Exception {
343 int uid = Binder.getCallingUid();
344 int userId = UserHandle.getCallingUserId();
345 long certSerial = 1000L;
346
347 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
348 TestData.getCertXmlWithSerial(certSerial));
349 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
350 TestData.getCertXmlWithSerial(certSerial + 1));
351
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700352 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
353 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial + 1);
Robert Berrye8edf972018-03-27 11:45:11 +0100354 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
Bo Zhu14d993d2018-02-03 21:38:48 -0800355 }
356
357 @Test
358 public void initRecoveryService_ignoresSmallerSerial() throws Exception {
359 int uid = Binder.getCallingUid();
360 int userId = UserHandle.getCallingUserId();
361 long certSerial = 1000L;
362
363 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
364 TestData.getCertXmlWithSerial(certSerial));
365 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
366 TestData.getCertXmlWithSerial(certSerial - 1));
367
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700368 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
369 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial);
Robert Berrye8edf972018-03-27 11:45:11 +0100370 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
Bo Zhu14d993d2018-02-03 21:38:48 -0800371 }
372
373 @Test
374 public void initRecoveryService_ignoresTheSameSerial() throws Exception {
375 int uid = Binder.getCallingUid();
376 int userId = UserHandle.getCallingUserId();
377 long certSerial = 1000L;
378
379 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
380 TestData.getCertXmlWithSerial(certSerial));
Bo Zhu14d993d2018-02-03 21:38:48 -0800381 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
382 TestData.getCertXmlWithSerial(certSerial));
383
384 // If the second update succeeds, getShouldCreateSnapshot() will return true.
385 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
386 }
387
388 @Test
389 public void initRecoveryService_succeedsWithRawPublicKey() throws Exception {
390 int uid = Binder.getCallingUid();
391 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800392 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
393
394 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, TEST_PUBLIC_KEY);
395
396 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700397 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
398 DEFAULT_ROOT_CERT_ALIAS)).isNull();
399 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
400 DEFAULT_ROOT_CERT_ALIAS)).isNull();
Bo Zhu14d993d2018-02-03 21:38:48 -0800401 assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNotNull();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800402 }
403
404 @Test
Bo Zhu7f414d92018-02-28 09:28:19 -0800405 public void initRecoveryServiceWithSigFile_succeeds() throws Exception {
406 int uid = Binder.getCallingUid();
407 int userId = UserHandle.getCallingUserId();
408 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
409
410 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
411 ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(), TestData.getSigXml());
412
Robert Berrye8edf972018-03-27 11:45:11 +0100413 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700414 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
415 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1);
Bo Zhu7f414d92018-02-28 09:28:19 -0800416 assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
417 }
418
419 @Test
420 public void initRecoveryServiceWithSigFile_throwsIfNullCertFile() throws Exception {
421 try {
422 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
423 ROOT_CERTIFICATE_ALIAS, /*recoveryServiceCertFile=*/ null,
424 TestData.getSigXml());
425 fail("should have thrown");
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700426 } catch (NullPointerException e) {
Bo Zhu7f414d92018-02-28 09:28:19 -0800427 assertThat(e.getMessage()).contains("is null");
428 }
429 }
430
431 @Test
432 public void initRecoveryServiceWithSigFile_throwsIfNullSigFile() throws Exception {
433 try {
434 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
435 ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(),
436 /*recoveryServiceSigFile=*/ null);
437 fail("should have thrown");
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700438 } catch (NullPointerException e) {
Bo Zhu7f414d92018-02-28 09:28:19 -0800439 assertThat(e.getMessage()).contains("is null");
440 }
441 }
442
443 @Test
444 public void initRecoveryServiceWithSigFile_throwsIfWrongSigFileFormat() throws Exception {
445 try {
446 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
447 ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(),
448 getUtf8Bytes("wrong-sig-file-format"));
449 fail("should have thrown");
450 } catch (ServiceSpecificException e) {
451 assertThat(e.getMessage()).contains("parse the sig file");
452 }
453 }
454
455 @Test
456 public void initRecoveryServiceWithSigFile_throwsIfInvalidFileSignature() throws Exception {
457 byte[] modifiedCertXml = TestData.getCertXml();
458 modifiedCertXml[modifiedCertXml.length - 1] = 0; // Change the last new line char to a zero
459 try {
460 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
461 ROOT_CERTIFICATE_ALIAS, modifiedCertXml, TestData.getSigXml());
462 fail("should have thrown");
463 } catch (ServiceSpecificException e) {
464 assertThat(e.getMessage()).contains("is invalid");
465 }
466 }
467
468 @Test
Robert Berrye16fa982017-12-20 15:59:37 +0000469 public void startRecoverySession_checksPermissionFirst() throws Exception {
470 mRecoverableKeyStoreManager.startRecoverySession(
471 TEST_SESSION_ID,
472 TEST_PUBLIC_KEY,
473 TEST_VAULT_PARAMS,
474 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700475 ImmutableList.of(TEST_PROTECTION_PARAMS));
Robert Berrye16fa982017-12-20 15:59:37 +0000476
Dmitry Dementyevad884712017-12-20 12:38:36 -0800477 verify(mMockContext, times(1))
478 .enforceCallingOrSelfPermission(
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800479 eq(Manifest.permission.RECOVER_KEYSTORE), any());
Robert Berrye16fa982017-12-20 15:59:37 +0000480 }
481
Bo Zhub31ab672018-03-20 22:44:18 -0700482 // TODO: Add tests for non-existing cert alias
483
Robert Berrye16fa982017-12-20 15:59:37 +0000484 @Test
Bo Zhu7c1972f2018-02-22 21:43:52 -0800485 public void startRecoverySessionWithCertPath_storesTheSessionInfo() throws Exception {
486 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
487 TEST_SESSION_ID,
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700488 TEST_DEFAULT_ROOT_CERT_ALIAS,
Bo Zhu7c1972f2018-02-22 21:43:52 -0800489 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
490 TEST_VAULT_PARAMS,
491 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700492 ImmutableList.of(TEST_PROTECTION_PARAMS));
Bo Zhu7c1972f2018-02-22 21:43:52 -0800493
494 assertEquals(1, mRecoverySessionStorage.size());
495 RecoverySessionStorage.Entry entry =
496 mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID);
497 assertArrayEquals(TEST_SECRET, entry.getLskfHash());
498 assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
499 }
500
501 @Test
502 public void startRecoverySessionWithCertPath_checksPermissionFirst() throws Exception {
503 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
504 TEST_SESSION_ID,
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700505 TEST_DEFAULT_ROOT_CERT_ALIAS,
Bo Zhu7c1972f2018-02-22 21:43:52 -0800506 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
507 TEST_VAULT_PARAMS,
508 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700509 ImmutableList.of(TEST_PROTECTION_PARAMS));
Bo Zhu7c1972f2018-02-22 21:43:52 -0800510
511 verify(mMockContext, times(2))
512 .enforceCallingOrSelfPermission(
513 eq(Manifest.permission.RECOVER_KEYSTORE), any());
514 }
515
516 @Test
Robert Berrye16fa982017-12-20 15:59:37 +0000517 public void startRecoverySession_storesTheSessionInfo() throws Exception {
518 mRecoverableKeyStoreManager.startRecoverySession(
519 TEST_SESSION_ID,
520 TEST_PUBLIC_KEY,
521 TEST_VAULT_PARAMS,
522 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700523 ImmutableList.of(TEST_PROTECTION_PARAMS));
Robert Berrye16fa982017-12-20 15:59:37 +0000524
525 assertEquals(1, mRecoverySessionStorage.size());
Dmitry Dementyevad884712017-12-20 12:38:36 -0800526 RecoverySessionStorage.Entry entry =
Dmitry Dementyev14298312018-01-04 15:19:19 -0800527 mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID);
Robert Berrye16fa982017-12-20 15:59:37 +0000528 assertArrayEquals(TEST_SECRET, entry.getLskfHash());
529 assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
530 }
531
532 @Test
Robert Berry2bcdad92018-01-18 12:53:29 +0000533 public void closeSession_closesASession() throws Exception {
534 mRecoverableKeyStoreManager.startRecoverySession(
535 TEST_SESSION_ID,
536 TEST_PUBLIC_KEY,
537 TEST_VAULT_PARAMS,
538 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700539 ImmutableList.of(TEST_PROTECTION_PARAMS));
Robert Berry2bcdad92018-01-18 12:53:29 +0000540
541 mRecoverableKeyStoreManager.closeSession(TEST_SESSION_ID);
542
543 assertEquals(0, mRecoverySessionStorage.size());
544 }
545
546 @Test
547 public void closeSession_doesNotCloseUnrelatedSessions() throws Exception {
548 mRecoverableKeyStoreManager.startRecoverySession(
549 TEST_SESSION_ID,
550 TEST_PUBLIC_KEY,
551 TEST_VAULT_PARAMS,
552 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700553 ImmutableList.of(TEST_PROTECTION_PARAMS));
Robert Berry2bcdad92018-01-18 12:53:29 +0000554
555 mRecoverableKeyStoreManager.closeSession("some random session");
556
557 assertEquals(1, mRecoverySessionStorage.size());
558 }
559
560 @Test
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700561 public void closeSession_throwsIfNullSession() throws Exception {
562 try {
563 mRecoverableKeyStoreManager.closeSession(/*sessionId=*/ null);
564 fail("should have thrown");
565 } catch (NullPointerException e) {
566 assertThat(e.getMessage()).contains("invalid");
567 }
568 }
569
570 @Test
Robert Berrye16fa982017-12-20 15:59:37 +0000571 public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception {
572 try {
573 mRecoverableKeyStoreManager.startRecoverySession(
574 TEST_SESSION_ID,
575 TEST_PUBLIC_KEY,
576 TEST_VAULT_PARAMS,
577 TEST_VAULT_CHALLENGE,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800578 ImmutableList.of());
Robert Berryb9a220b2017-12-21 12:41:01 +0000579 fail("should have thrown");
Dmitry Dementyevae6ec6d2018-01-18 14:29:49 -0800580 } catch (UnsupportedOperationException e) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800581 assertThat(e.getMessage()).startsWith(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800582 "Only a single KeyChainProtectionParams is supported");
Robert Berrye16fa982017-12-20 15:59:37 +0000583 }
584 }
585
586 @Test
Bo Zhudef7ffd2018-01-05 14:50:52 -0800587 public void startRecoverySession_throwsIfPublicKeysMismatch() throws Exception {
588 byte[] vaultParams = TEST_VAULT_PARAMS.clone();
589 vaultParams[1] ^= (byte) 1; // Flip 1 bit
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700590
Bo Zhudef7ffd2018-01-05 14:50:52 -0800591 try {
592 mRecoverableKeyStoreManager.startRecoverySession(
593 TEST_SESSION_ID,
594 TEST_PUBLIC_KEY,
595 vaultParams,
596 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700597 ImmutableList.of(TEST_PROTECTION_PARAMS));
Bo Zhudef7ffd2018-01-05 14:50:52 -0800598 fail("should have thrown");
599 } catch (ServiceSpecificException e) {
600 assertThat(e.getMessage()).contains("do not match");
601 }
602 }
603
604 @Test
Bo Zhu7c1972f2018-02-22 21:43:52 -0800605 public void startRecoverySessionWithCertPath_throwsIfBadNumberOfSecrets() throws Exception {
606 try {
607 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
608 TEST_SESSION_ID,
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700609 TEST_DEFAULT_ROOT_CERT_ALIAS,
Bo Zhu7c1972f2018-02-22 21:43:52 -0800610 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
611 TEST_VAULT_PARAMS,
612 TEST_VAULT_CHALLENGE,
613 ImmutableList.of());
614 fail("should have thrown");
615 } catch (UnsupportedOperationException e) {
616 assertThat(e.getMessage()).startsWith(
617 "Only a single KeyChainProtectionParams is supported");
618 }
619 }
620
621 @Test
622 public void startRecoverySessionWithCertPath_throwsIfPublicKeysMismatch() throws Exception {
623 byte[] vaultParams = TEST_VAULT_PARAMS.clone();
624 vaultParams[1] ^= (byte) 1; // Flip 1 bit
625 try {
626 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
627 TEST_SESSION_ID,
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700628 TEST_DEFAULT_ROOT_CERT_ALIAS,
Bo Zhu7c1972f2018-02-22 21:43:52 -0800629 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
630 vaultParams,
631 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700632 ImmutableList.of(TEST_PROTECTION_PARAMS));
Bo Zhu7c1972f2018-02-22 21:43:52 -0800633 fail("should have thrown");
634 } catch (ServiceSpecificException e) {
635 assertThat(e.getMessage()).contains("do not match");
636 }
637 }
638
639 @Test
640 public void startRecoverySessionWithCertPath_throwsIfEmptyCertPath() throws Exception {
641 CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
642 CertPath emptyCertPath = certFactory.generateCertPath(new ArrayList<X509Certificate>());
643 try {
644 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
645 TEST_SESSION_ID,
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700646 TEST_DEFAULT_ROOT_CERT_ALIAS,
Bo Zhu7c1972f2018-02-22 21:43:52 -0800647 RecoveryCertPath.createRecoveryCertPath(emptyCertPath),
648 TEST_VAULT_PARAMS,
649 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700650 ImmutableList.of(TEST_PROTECTION_PARAMS));
Bo Zhu7c1972f2018-02-22 21:43:52 -0800651 fail("should have thrown");
652 } catch (ServiceSpecificException e) {
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800653 assertThat(e.getMessage()).contains("empty");
654 }
655 }
656
657 @Test
658 public void startRecoverySessionWithCertPath_throwsIfInvalidCertPath() throws Exception {
659 CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
660 CertPath shortCertPath = certFactory.generateCertPath(
661 TestData.CERT_PATH_1.getCertificates()
662 .subList(0, TestData.CERT_PATH_1.getCertificates().size() - 1));
663 try {
664 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
665 TEST_SESSION_ID,
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700666 TEST_DEFAULT_ROOT_CERT_ALIAS,
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800667 RecoveryCertPath.createRecoveryCertPath(shortCertPath),
668 TEST_VAULT_PARAMS,
669 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700670 ImmutableList.of(TEST_PROTECTION_PARAMS));
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800671 fail("should have thrown");
672 } catch (ServiceSpecificException e) {
673 // expected
Bo Zhu7c1972f2018-02-22 21:43:52 -0800674 }
675 }
676
677 @Test
Robert Berry4a5c87d2018-03-19 18:00:46 +0000678 public void recoverKeyChainSnapshot_throwsIfNoSessionIsPresent() throws Exception {
Robert Berryb9a220b2017-12-21 12:41:01 +0000679 try {
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700680 WrappedApplicationKey applicationKey = new WrappedApplicationKey.Builder()
681 .setAlias(TEST_ALIAS)
682 .setEncryptedKeyMaterial(randomBytes(32))
683 .build();
Robert Berry4a5c87d2018-03-19 18:00:46 +0000684 mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
Robert Berryb9a220b2017-12-21 12:41:01 +0000685 TEST_SESSION_ID,
686 /*recoveryKeyBlob=*/ randomBytes(32),
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700687 /*applicationKeys=*/ ImmutableList.of(applicationKey));
Robert Berryb9a220b2017-12-21 12:41:01 +0000688 fail("should have thrown");
Dmitry Dementyev14298312018-01-04 15:19:19 -0800689 } catch (ServiceSpecificException e) {
690 // expected
Robert Berryb9a220b2017-12-21 12:41:01 +0000691 }
692 }
693
694 @Test
Robert Berry4a5c87d2018-03-19 18:00:46 +0000695 public void recoverKeyChainSnapshot_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception {
Robert Berryb9a220b2017-12-21 12:41:01 +0000696 mRecoverableKeyStoreManager.startRecoverySession(
697 TEST_SESSION_ID,
698 TEST_PUBLIC_KEY,
699 TEST_VAULT_PARAMS,
700 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700701 ImmutableList.of(TEST_PROTECTION_PARAMS));
Robert Berryb9a220b2017-12-21 12:41:01 +0000702
703 try {
Robert Berry4a5c87d2018-03-19 18:00:46 +0000704 mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
Robert Berryb9a220b2017-12-21 12:41:01 +0000705 TEST_SESSION_ID,
706 /*encryptedRecoveryKey=*/ randomBytes(60),
Dmitry Dementyev14298312018-01-04 15:19:19 -0800707 /*applicationKeys=*/ ImmutableList.of());
Robert Berryb9a220b2017-12-21 12:41:01 +0000708 fail("should have thrown");
Dmitry Dementyev14298312018-01-04 15:19:19 -0800709 } catch (ServiceSpecificException e) {
710 assertThat(e.getMessage()).startsWith("Failed to decrypt recovery key");
Robert Berryb9a220b2017-12-21 12:41:01 +0000711 }
712 }
713
714 @Test
Robert Berry4a5c87d2018-03-19 18:00:46 +0000715 public void recoverKeyChainSnapshot_throwsIfFailedToDecryptAllApplicationKeys() throws Exception {
Robert Berryb9a220b2017-12-21 12:41:01 +0000716 mRecoverableKeyStoreManager.startRecoverySession(
717 TEST_SESSION_ID,
718 TEST_PUBLIC_KEY,
719 TEST_VAULT_PARAMS,
720 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700721 ImmutableList.of(TEST_PROTECTION_PARAMS));
Dmitry Dementyev14298312018-01-04 15:19:19 -0800722 byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
Robert Berryb9a220b2017-12-21 12:41:01 +0000723 .getKeyClaimant();
724 SecretKey recoveryKey = randomRecoveryKey();
725 byte[] encryptedClaimResponse = encryptClaimResponse(
726 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700727 WrappedApplicationKey badApplicationKey = new WrappedApplicationKey.Builder()
728 .setAlias(TEST_ALIAS)
729 .setEncryptedKeyMaterial(
730 encryptedApplicationKey(randomRecoveryKey(), randomBytes(32)))
731 .build();
Robert Berryb9a220b2017-12-21 12:41:01 +0000732 try {
Robert Berry4a5c87d2018-03-19 18:00:46 +0000733 mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
Robert Berryb9a220b2017-12-21 12:41:01 +0000734 TEST_SESSION_ID,
735 /*encryptedRecoveryKey=*/ encryptedClaimResponse,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800736 /*applicationKeys=*/ ImmutableList.of(badApplicationKey));
Robert Berryb9a220b2017-12-21 12:41:01 +0000737 fail("should have thrown");
Bo Zhu3462c832018-01-04 22:42:36 -0800738 } catch (ServiceSpecificException e) {
Bo Zhu4857cb52018-02-06 14:34:48 -0800739 assertThat(e.getMessage()).startsWith("Failed to recover any of the application keys");
Robert Berryb9a220b2017-12-21 12:41:01 +0000740 }
741 }
742
743 @Test
Robert Berry4a5c87d2018-03-19 18:00:46 +0000744 public void recoverKeyChainSnapshot_doesNotThrowIfNoApplicationKeysToBeDecrypted()
745 throws Exception {
Bo Zhuae0682d2018-02-13 10:23:39 -0800746 mRecoverableKeyStoreManager.startRecoverySession(
747 TEST_SESSION_ID,
748 TEST_PUBLIC_KEY,
749 TEST_VAULT_PARAMS,
750 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700751 ImmutableList.of(TEST_PROTECTION_PARAMS));
Bo Zhuae0682d2018-02-13 10:23:39 -0800752 byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
753 .getKeyClaimant();
754 SecretKey recoveryKey = randomRecoveryKey();
755 byte[] encryptedClaimResponse = encryptClaimResponse(
756 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
757
Robert Berry4a5c87d2018-03-19 18:00:46 +0000758 mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
Bo Zhuae0682d2018-02-13 10:23:39 -0800759 TEST_SESSION_ID,
760 /*encryptedRecoveryKey=*/ encryptedClaimResponse,
761 /*applicationKeys=*/ ImmutableList.of());
762 }
763
764 @Test
Robert Berry4a5c87d2018-03-19 18:00:46 +0000765 public void recoverKeyChainSnapshot_returnsDecryptedKeys() throws Exception {
Robert Berryb9a220b2017-12-21 12:41:01 +0000766 mRecoverableKeyStoreManager.startRecoverySession(
767 TEST_SESSION_ID,
768 TEST_PUBLIC_KEY,
769 TEST_VAULT_PARAMS,
770 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700771 ImmutableList.of(TEST_PROTECTION_PARAMS));
Dmitry Dementyev14298312018-01-04 15:19:19 -0800772 byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
Robert Berryb9a220b2017-12-21 12:41:01 +0000773 .getKeyClaimant();
774 SecretKey recoveryKey = randomRecoveryKey();
775 byte[] encryptedClaimResponse = encryptClaimResponse(
776 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
Robert Berrybd4c43c2017-12-22 11:35:14 +0000777 byte[] applicationKeyBytes = randomBytes(32);
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700778 WrappedApplicationKey applicationKey = new WrappedApplicationKey.Builder()
779 .setAlias(TEST_ALIAS)
780 .setEncryptedKeyMaterial(
781 encryptedApplicationKey(recoveryKey, applicationKeyBytes))
782 .build();
Robert Berryb9a220b2017-12-21 12:41:01 +0000783
Robert Berry4a5c87d2018-03-19 18:00:46 +0000784 Map<String, String> recoveredKeys = mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
Robert Berryb9a220b2017-12-21 12:41:01 +0000785 TEST_SESSION_ID,
786 encryptedClaimResponse,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800787 ImmutableList.of(applicationKey));
Robert Berrybd4c43c2017-12-22 11:35:14 +0000788
789 assertThat(recoveredKeys).hasSize(1);
Robert Berry4a5c87d2018-03-19 18:00:46 +0000790 assertThat(recoveredKeys).containsKey(TEST_ALIAS);
791 // TODO(76083050) Test the grant mechanism for the keys.
Robert Berryb9a220b2017-12-21 12:41:01 +0000792 }
793
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800794 @Test
Robert Berry4a5c87d2018-03-19 18:00:46 +0000795 public void recoverKeyChainSnapshot_worksOnOtherApplicationKeysIfOneDecryptionFails() throws Exception {
Bo Zhu4857cb52018-02-06 14:34:48 -0800796 mRecoverableKeyStoreManager.startRecoverySession(
797 TEST_SESSION_ID,
798 TEST_PUBLIC_KEY,
799 TEST_VAULT_PARAMS,
800 TEST_VAULT_CHALLENGE,
Dmitry Dementyev16d9db52018-03-26 11:31:46 -0700801 ImmutableList.of(TEST_PROTECTION_PARAMS));
Bo Zhu4857cb52018-02-06 14:34:48 -0800802 byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
803 .getKeyClaimant();
804 SecretKey recoveryKey = randomRecoveryKey();
805 byte[] encryptedClaimResponse = encryptClaimResponse(
806 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
807
808 byte[] applicationKeyBytes1 = randomBytes(32);
809 byte[] applicationKeyBytes2 = randomBytes(32);
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700810 WrappedApplicationKey applicationKey1 = new WrappedApplicationKey.Builder()
811 .setAlias(TEST_ALIAS)
812 // Use a different recovery key here, so the decryption will fail
813 .setEncryptedKeyMaterial(
814 encryptedApplicationKey(randomRecoveryKey(), applicationKeyBytes1))
815 .build();
816 WrappedApplicationKey applicationKey2 = new WrappedApplicationKey.Builder()
817 .setAlias(TEST_ALIAS2)
818 .setEncryptedKeyMaterial(
819 encryptedApplicationKey(recoveryKey, applicationKeyBytes2))
820 .build();
Bo Zhu4857cb52018-02-06 14:34:48 -0800821
Robert Berry4a5c87d2018-03-19 18:00:46 +0000822 Map<String, String> recoveredKeys = mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
Bo Zhu4857cb52018-02-06 14:34:48 -0800823 TEST_SESSION_ID,
824 encryptedClaimResponse,
825 ImmutableList.of(applicationKey1, applicationKey2));
826
827 assertThat(recoveredKeys).hasSize(1);
Robert Berry4a5c87d2018-03-19 18:00:46 +0000828 assertThat(recoveredKeys).containsKey(TEST_ALIAS2);
829 // TODO(76083050) Test the grant mechanism for the keys.
Bo Zhu4857cb52018-02-06 14:34:48 -0800830 }
831
832 @Test
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800833 public void setSnapshotCreatedPendingIntent() throws Exception {
834 int uid = Binder.getCallingUid();
835 PendingIntent intent = PendingIntent.getBroadcast(
836 InstrumentationRegistry.getTargetContext(), /*requestCode=*/1,
837 new Intent(), /*flags=*/ 0);
Dmitry Dementyev14298312018-01-04 15:19:19 -0800838 mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent);
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800839 verify(mMockListenersStorage).setSnapshotListener(eq(uid), any(PendingIntent.class));
840 }
841
Dmitry Dementyevad884712017-12-20 12:38:36 -0800842 @Test
Robert Berry8f9038c2018-03-26 11:36:40 +0100843 public void setServerParams_updatesServerParams() throws Exception {
844 int uid = Binder.getCallingUid();
845 int userId = UserHandle.getCallingUserId();
846 byte[] serverParams = new byte[] { 1 };
847
848 mRecoverableKeyStoreManager.setServerParams(serverParams);
849
850 assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(serverParams);
851 }
852
853 @Test
854 public void setServerParams_doesNotSetSnapshotPendingIfInitializing() throws Exception {
855 int uid = Binder.getCallingUid();
856 int userId = UserHandle.getCallingUserId();
857 byte[] serverParams = new byte[] { 1 };
858
859 mRecoverableKeyStoreManager.setServerParams(serverParams);
860
861 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
862 }
863
864 @Test
865 public void setServerParams_doesNotSetSnapshotPendingIfSettingSameValue() throws Exception {
866 int uid = Binder.getCallingUid();
867 int userId = UserHandle.getCallingUserId();
868 byte[] serverParams = new byte[] { 1 };
869
870 mRecoverableKeyStoreManager.setServerParams(serverParams);
871 mRecoverableKeyStoreManager.setServerParams(serverParams);
872
873 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
874 }
875
876 @Test
877 public void setServerParams_setsSnapshotPendingIfUpdatingValue() throws Exception {
878 int uid = Binder.getCallingUid();
879 int userId = UserHandle.getCallingUserId();
880
881 mRecoverableKeyStoreManager.setServerParams(new byte[] { 1 });
882 mRecoverableKeyStoreManager.setServerParams(new byte[] { 2 });
883
884 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
885 }
886
887 @Test
Robert Berry5a1acefb2018-03-26 14:41:30 +0100888 public void setRecoverySecretTypes_updatesSecretTypes() throws Exception {
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800889 int[] types1 = new int[]{11, 2000};
890 int[] types2 = new int[]{1, 2, 3};
891 int[] types3 = new int[]{};
892
Dmitry Dementyev14298312018-01-04 15:19:19 -0800893 mRecoverableKeyStoreManager.setRecoverySecretTypes(types1);
894 assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800895 types1);
896
Dmitry Dementyev14298312018-01-04 15:19:19 -0800897 mRecoverableKeyStoreManager.setRecoverySecretTypes(types2);
898 assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800899 types2);
900
Dmitry Dementyev14298312018-01-04 15:19:19 -0800901 mRecoverableKeyStoreManager.setRecoverySecretTypes(types3);
902 assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo(
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800903 types3);
904 }
905
906 @Test
Robert Berry5a1acefb2018-03-26 14:41:30 +0100907 public void setRecoverySecretTypes_doesNotSetSnapshotPendingIfIniting() throws Exception {
908 int uid = Binder.getCallingUid();
909 int userId = UserHandle.getCallingUserId();
910 int[] secretTypes = new int[] { 101 };
911
912 mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
913
914 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
915 }
916
917 @Test
918 public void setRecoverySecretTypes_doesNotSetSnapshotPendingIfSettingSameValue()
919 throws Exception {
920 int uid = Binder.getCallingUid();
921 int userId = UserHandle.getCallingUserId();
922 int[] secretTypes = new int[] { 101 };
923
924 mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
925 mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
926
927 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
928 }
929
930 @Test
931 public void setRecoverySecretTypes_setsSnapshotPendingIfUpdatingValue() throws Exception {
932 int uid = Binder.getCallingUid();
933 int userId = UserHandle.getCallingUserId();
934
935 mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 101 });
936 mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 102 });
937
938 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
939 }
940
941 @Test
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700942 public void setRecoverySecretTypes_throwsIfNullTypes() throws Exception {
943 try {
944 mRecoverableKeyStoreManager.setRecoverySecretTypes(/*types=*/ null);
945 fail("should have thrown");
946 } catch (NullPointerException e) {
947 assertThat(e.getMessage()).contains("is null");
948 }
949 }
950
951 @Test
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800952 public void setRecoverySecretTypes_updatesShouldCreateSnapshot() throws Exception {
953 int uid = Binder.getCallingUid();
954 int userId = UserHandle.getCallingUserId();
Robert Berrye8edf972018-03-27 11:45:11 +0100955 mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 1 });
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800956
957 mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
958 // Pretend that key was synced
959 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
Robert Berrye8edf972018-03-27 11:45:11 +0100960 mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 2 });
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800961
962 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
963 }
964
965 @Test
Robert Berrybbe02ae2018-02-20 19:47:43 +0000966 public void setRecoveryStatus() throws Exception {
Dmitry Dementyevad884712017-12-20 12:38:36 -0800967 int userId = UserHandle.getCallingUserId();
968 int uid = Binder.getCallingUid();
969 int status = 100;
970 int status2 = 200;
971 String alias = "key1";
972 WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status);
973 mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
974 Map<String, Integer> statuses =
Robert Berry56f06b42018-02-23 13:31:32 +0000975 mRecoverableKeyStoreManager.getRecoveryStatus();
Dmitry Dementyevad884712017-12-20 12:38:36 -0800976 assertThat(statuses).hasSize(1);
977 assertThat(statuses).containsEntry(alias, status);
978
Robert Berrybbe02ae2018-02-20 19:47:43 +0000979 mRecoverableKeyStoreManager.setRecoveryStatus(alias, status2);
Robert Berry56f06b42018-02-23 13:31:32 +0000980 statuses = mRecoverableKeyStoreManager.getRecoveryStatus();
Dmitry Dementyevad884712017-12-20 12:38:36 -0800981 assertThat(statuses).hasSize(1);
982 assertThat(statuses).containsEntry(alias, status2); // updated
983 }
984
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700985 @Test
986 public void setRecoveryStatus_throwsIfNullAlias() throws Exception {
987 try {
988 mRecoverableKeyStoreManager.setRecoveryStatus(/*alias=*/ null, /*status=*/ 100);
989 fail("should have thrown");
990 } catch (NullPointerException e) {
991 assertThat(e.getMessage()).contains("is null");
992 }
993 }
994
Robert Berrybd4c43c2017-12-22 11:35:14 +0000995 private static byte[] encryptedApplicationKey(
996 SecretKey recoveryKey, byte[] applicationKey) throws Exception {
Robert Berryb9a220b2017-12-21 12:41:01 +0000997 return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700998 TEST_ALIAS, new SecretKeySpec(applicationKey, "AES")
999 )).get(TEST_ALIAS);
Robert Berryb9a220b2017-12-21 12:41:01 +00001000 }
1001
1002 private static byte[] encryptClaimResponse(
1003 byte[] keyClaimant,
1004 byte[] lskfHash,
1005 byte[] vaultParams,
1006 SecretKey recoveryKey) throws Exception {
1007 byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey(
1008 lskfHash, recoveryKey);
1009 return SecureBox.encrypt(
1010 /*theirPublicKey=*/ null,
1011 /*sharedSecret=*/ keyClaimant,
1012 /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
1013 /*payload=*/ locallyEncryptedRecoveryKey);
1014 }
1015
1016 private static SecretKey randomRecoveryKey() {
1017 return new SecretKeySpec(randomBytes(32), "AES");
1018 }
1019
Robert Berrye16fa982017-12-20 15:59:37 +00001020 private static byte[] getUtf8Bytes(String s) {
1021 return s.getBytes(StandardCharsets.UTF_8);
1022 }
Robert Berryb9a220b2017-12-21 12:41:01 +00001023
1024 private static byte[] randomBytes(int n) {
1025 byte[] bytes = new byte[n];
1026 new Random().nextBytes(bytes);
1027 return bytes;
1028 }
Bo Zhu3462c832018-01-04 22:42:36 -08001029
1030 private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
1031 KeyGenerator keyGenerator = KeyGenerator.getInstance(
1032 KEY_ALGORITHM,
1033 ANDROID_KEY_STORE_PROVIDER);
1034 keyGenerator.init(new KeyGenParameterSpec.Builder(
1035 WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
1036 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
1037 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
1038 .build());
1039 return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
1040 }
Robert Berrye16fa982017-12-20 15:59:37 +00001041}