1 /* 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 17 package com.android.server.locksettings.recoverablekeystore; 18 19 import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN; 20 import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PASSWORD; 21 import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT; 22 import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE; 23 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE; 24 25 import static com.google.common.truth.Truth.assertThat; 26 import static org.junit.Assert.assertArrayEquals; 27 import static org.junit.Assert.assertEquals; 28 import static org.junit.Assert.fail; 29 import static org.mockito.ArgumentMatchers.any; 30 import static org.mockito.ArgumentMatchers.anyInt; 31 import static org.mockito.ArgumentMatchers.anyString; 32 import static org.mockito.ArgumentMatchers.eq; 33 import static org.mockito.Mockito.atLeast; 34 import static org.mockito.Mockito.times; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.when; 37 38 import android.app.KeyguardManager; 39 import android.app.PendingIntent; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.Manifest; 43 import android.os.Binder; 44 import android.os.ServiceSpecificException; 45 import android.os.UserHandle; 46 import android.security.KeyStore; 47 import android.security.keystore.AndroidKeyStoreProvider; 48 import android.security.keystore.AndroidKeyStoreSecretKey; 49 import android.security.keystore.KeyGenParameterSpec; 50 import android.security.keystore.KeyProperties; 51 import android.security.keystore.recovery.KeyChainProtectionParams; 52 import android.security.keystore.recovery.KeyDerivationParams; 53 import android.security.keystore.recovery.RecoveryCertPath; 54 import android.security.keystore.recovery.TrustedRootCertificates; 55 import android.security.keystore.recovery.WrappedApplicationKey; 56 import android.support.test.filters.SmallTest; 57 import android.support.test.InstrumentationRegistry; 58 import android.support.test.runner.AndroidJUnit4; 59 60 import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; 61 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; 62 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; 63 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; 64 65 import com.google.common.collect.ImmutableList; 66 import com.google.common.collect.ImmutableMap; 67 68 import org.junit.After; 69 import org.junit.Before; 70 import org.junit.Test; 71 import org.junit.runner.RunWith; 72 import org.mockito.Mock; 73 import org.mockito.MockitoAnnotations; 74 import org.mockito.Spy; 75 76 import java.io.File; 77 import java.nio.charset.StandardCharsets; 78 import java.security.UnrecoverableKeyException; 79 import java.security.cert.CertPath; 80 import java.security.cert.CertificateFactory; 81 import java.security.cert.X509Certificate; 82 import java.util.ArrayList; 83 import java.util.concurrent.Executors; 84 import java.util.Map; 85 import java.util.Random; 86 87 import javax.crypto.Cipher; 88 import javax.crypto.KeyGenerator; 89 import javax.crypto.SecretKey; 90 import javax.crypto.spec.GCMParameterSpec; 91 import javax.crypto.spec.SecretKeySpec; 92 93 @SmallTest 94 @RunWith(AndroidJUnit4.class) 95 public class RecoverableKeyStoreManagerTest { 96 private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; 97 98 private static final String ROOT_CERTIFICATE_ALIAS = ""; 99 private static final String DEFAULT_ROOT_CERT_ALIAS = 100 TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; 101 private static final String INSECURE_CERTIFICATE_ALIAS = 102 TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS; 103 private static final String TEST_SESSION_ID = "karlin"; 104 private static final byte[] TEST_PUBLIC_KEY = TestData.CERT_1_PUBLIC_KEY.getEncoded(); 105 private static final byte[] TEST_SALT = getUtf8Bytes("salt"); 106 private static final byte[] TEST_SECRET = getUtf8Bytes("password1234"); 107 private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge"); 108 private static final byte[] TEST_VAULT_PARAMS = new byte[] { 109 // backend_key 110 (byte) 0x04, (byte) 0x8e, (byte) 0x0c, (byte) 0x11, (byte) 0x4a, (byte) 0x79, (byte) 0x20, 111 (byte) 0x7c, (byte) 0x00, (byte) 0x4c, (byte) 0xd7, (byte) 0xe9, (byte) 0x06, (byte) 0xe2, 112 (byte) 0x58, (byte) 0x21, (byte) 0x45, (byte) 0xfa, (byte) 0x24, (byte) 0xcb, (byte) 0x07, 113 (byte) 0x66, (byte) 0xde, (byte) 0xfd, (byte) 0xf1, (byte) 0x83, (byte) 0xb4, (byte) 0x26, 114 (byte) 0x55, (byte) 0x98, (byte) 0xcb, (byte) 0xa9, (byte) 0xd5, (byte) 0x55, (byte) 0xad, 115 (byte) 0x65, (byte) 0xc5, (byte) 0xff, (byte) 0x5c, (byte) 0xfb, (byte) 0x1c, (byte) 0x4e, 116 (byte) 0x34, (byte) 0x98, (byte) 0x7e, (byte) 0x4f, (byte) 0x96, (byte) 0xa2, (byte) 0xa3, 117 (byte) 0x7e, (byte) 0xf4, (byte) 0x46, (byte) 0x52, (byte) 0x04, (byte) 0xba, (byte) 0x2a, 118 (byte) 0xb9, (byte) 0x47, (byte) 0xbb, (byte) 0xc2, (byte) 0x1e, (byte) 0xdd, (byte) 0x15, 119 (byte) 0x1a, (byte) 0xc0, 120 // counter_id 121 (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00, 122 (byte) 0x00, 123 // device_parameter 124 (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00, (byte) 0x00, (byte) 0x00, 125 (byte) 0x0, 126 // max_attempts 127 (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00}; 128 private static final int TEST_GENERATION_ID = 2; 129 private static final int TEST_USER_ID = 10009; 130 private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; 131 private static final byte[] RECOVERY_RESPONSE_HEADER = 132 "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); 133 private static final String TEST_ALIAS = "nick"; 134 private static final String TEST_ALIAS2 = "bob"; 135 private static final int RECOVERABLE_KEY_SIZE_BYTES = 32; 136 private static final int APPLICATION_KEY_SIZE_BYTES = 32; 137 private static final int GENERATION_ID = 1; 138 private static final byte[] NONCE = getUtf8Bytes("nonce"); 139 private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial"); 140 private static final String KEY_ALGORITHM = "AES"; 141 private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; 142 private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey"; 143 private static final String TEST_DEFAULT_ROOT_CERT_ALIAS = ""; 144 private static final KeyChainProtectionParams TEST_PROTECTION_PARAMS = 145 new KeyChainProtectionParams.Builder() 146 .setUserSecretType(TYPE_LOCKSCREEN) 147 .setLockScreenUiFormat(UI_FORMAT_PASSWORD) 148 .setKeyDerivationParams(KeyDerivationParams.createSha256Params(TEST_SALT)) 149 .setSecret(TEST_SECRET) 150 .build(); 151 152 @Mock private Context mMockContext; 153 @Mock private RecoverySnapshotListenersStorage mMockListenersStorage; 154 @Mock private KeyguardManager mKeyguardManager; 155 @Mock private PlatformKeyManager mPlatformKeyManager; 156 @Mock private ApplicationKeyStorage mApplicationKeyStorage; 157 @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; 158 159 private RecoverableKeyStoreDb mRecoverableKeyStoreDb; 160 private File mDatabaseFile; 161 private RecoverableKeyStoreManager mRecoverableKeyStoreManager; 162 private RecoverySessionStorage mRecoverySessionStorage; 163 private RecoverySnapshotStorage mRecoverySnapshotStorage; 164 private PlatformEncryptionKey mPlatformEncryptionKey; 165 166 @Before 167 public void setUp() throws Exception { 168 MockitoAnnotations.initMocks(this); 169 170 Context context = InstrumentationRegistry.getTargetContext(); 171 mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); 172 mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); 173 174 mRecoverySessionStorage = new RecoverySessionStorage(); 175 176 when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager); 177 when(mMockContext.getSystemServiceName(any())).thenReturn("test"); 178 when(mMockContext.getApplicationContext()).thenReturn(mMockContext); 179 when(mKeyguardManager.isDeviceSecure(TEST_USER_ID)).thenReturn(true); 180 181 mPlatformEncryptionKey = 182 new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey()); 183 when(mPlatformKeyManager.getEncryptKey(anyInt())).thenReturn(mPlatformEncryptionKey); 184 185 mRecoverableKeyStoreManager = new RecoverableKeyStoreManager( 186 mMockContext, 187 mRecoverableKeyStoreDb, 188 mRecoverySessionStorage, 189 Executors.newSingleThreadExecutor(), 190 mRecoverySnapshotStorage, 191 mMockListenersStorage, 192 mPlatformKeyManager, 193 mApplicationKeyStorage, 194 mTestOnlyInsecureCertificateHelper); 195 } 196 197 @After 198 public void tearDown() { 199 mRecoverableKeyStoreDb.close(); 200 mDatabaseFile.delete(); 201 } 202 203 @Test 204 public void importKey_storesTheKey() throws Exception { 205 int uid = Binder.getCallingUid(); 206 int userId = UserHandle.getCallingUserId(); 207 byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES); 208 209 mRecoverableKeyStoreManager.importKey(TEST_ALIAS, keyMaterial); 210 211 assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull(); 212 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); 213 } 214 215 @Test 216 public void importKey_throwsIfInvalidLength() throws Exception { 217 byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES - 1); 218 try { 219 mRecoverableKeyStoreManager.importKey(TEST_ALIAS, keyMaterial); 220 fail("should have thrown"); 221 } catch (ServiceSpecificException e) { 222 assertThat(e.getMessage()).contains("not contain 256 bits"); 223 } 224 } 225 226 @Test 227 public void importKey_throwsIfNullKey() throws Exception { 228 try { 229 mRecoverableKeyStoreManager.importKey(TEST_ALIAS, /*keyBytes=*/ null); 230 fail("should have thrown"); 231 } catch (NullPointerException e) { 232 assertThat(e.getMessage()).contains("is null"); 233 } 234 } 235 236 @Test 237 public void removeKey_removesAKey() throws Exception { 238 int uid = Binder.getCallingUid(); 239 mRecoverableKeyStoreManager.generateKey(TEST_ALIAS); 240 241 mRecoverableKeyStoreManager.removeKey(TEST_ALIAS); 242 243 assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNull(); 244 } 245 246 @Test 247 public void removeKey_updatesShouldCreateSnapshot() throws Exception { 248 int uid = Binder.getCallingUid(); 249 int userId = UserHandle.getCallingUserId(); 250 mRecoverableKeyStoreManager.generateKey(TEST_ALIAS); 251 // Pretend that key was synced 252 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 253 254 mRecoverableKeyStoreManager.removeKey(TEST_ALIAS); 255 256 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); 257 } 258 259 @Test 260 public void removeKey_failureDoesNotUpdateShouldCreateSnapshot() throws Exception { 261 int uid = Binder.getCallingUid(); 262 int userId = UserHandle.getCallingUserId(); 263 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 264 // Key did not exist 265 mRecoverableKeyStoreManager.removeKey(TEST_ALIAS); 266 267 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 268 } 269 270 @Test 271 public void initRecoveryService_succeedsWithCertFile() throws Exception { 272 int uid = Binder.getCallingUid(); 273 int userId = UserHandle.getCallingUserId(); 274 long certSerial = 1000L; 275 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 276 277 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 278 TestData.getCertXmlWithSerial(certSerial)); 279 280 verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) 281 .getDefaultCertificateAliasIfEmpty(ROOT_CERTIFICATE_ALIAS); 282 283 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 284 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, 285 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1); 286 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, 287 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial); 288 assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); 289 } 290 291 @Test 292 public void initRecoveryService_updatesShouldCreatesnapshotOnCertUpdate() throws Exception { 293 int uid = Binder.getCallingUid(); 294 int userId = UserHandle.getCallingUserId(); 295 long certSerial = 1000L; 296 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 297 298 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 299 TestData.getCertXmlWithSerial(certSerial)); 300 301 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 302 303 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 304 TestData.getCertXmlWithSerial(certSerial + 1)); 305 306 // Since there were no recoverable keys, new snapshot will not be created. 307 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 308 309 generateKeyAndSimulateSync(userId, uid, 10); 310 311 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 312 TestData.getCertXmlWithSerial(certSerial + 2)); 313 314 // Since there were a recoverable key, new serial number triggers snapshot creation 315 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); 316 } 317 318 @Test 319 public void initRecoveryService_triesToFilterRootAlias() throws Exception { 320 int uid = Binder.getCallingUid(); 321 int userId = UserHandle.getCallingUserId(); 322 long certSerial = 1000L; 323 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 324 325 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 326 TestData.getCertXmlWithSerial(certSerial)); 327 328 verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) 329 .getDefaultCertificateAliasIfEmpty(eq(ROOT_CERTIFICATE_ALIAS)); 330 331 verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) 332 .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); 333 334 String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid); 335 assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); 336 337 } 338 339 @Test 340 public void initRecoveryService_usesProdCertificateForEmptyRootAlias() throws Exception { 341 int uid = Binder.getCallingUid(); 342 int userId = UserHandle.getCallingUserId(); 343 long certSerial = 1000L; 344 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 345 346 mRecoverableKeyStoreManager.initRecoveryService(/*rootCertificateAlias=*/ "", 347 TestData.getCertXmlWithSerial(certSerial)); 348 349 verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) 350 .getDefaultCertificateAliasIfEmpty(eq("")); 351 352 verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) 353 .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); 354 355 String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid); 356 assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); 357 } 358 359 @Test 360 public void initRecoveryService_usesProdCertificateForNullRootAlias() throws Exception { 361 int uid = Binder.getCallingUid(); 362 int userId = UserHandle.getCallingUserId(); 363 long certSerial = 1000L; 364 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 365 366 mRecoverableKeyStoreManager.initRecoveryService(/*rootCertificateAlias=*/ null, 367 TestData.getCertXmlWithSerial(certSerial)); 368 369 verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) 370 .getDefaultCertificateAliasIfEmpty(null); 371 372 verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) 373 .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); 374 375 String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid); 376 assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); 377 } 378 379 @Test 380 public void initRecoveryService_regeneratesCounterId() throws Exception { 381 int uid = Binder.getCallingUid(); 382 int userId = UserHandle.getCallingUserId(); 383 long certSerial = 1000L; 384 385 Long counterId0 = mRecoverableKeyStoreDb.getCounterId(userId, uid); 386 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 387 TestData.getCertXmlWithSerial(certSerial)); 388 Long counterId1 = mRecoverableKeyStoreDb.getCounterId(userId, uid); 389 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 390 TestData.getCertXmlWithSerial(certSerial + 1)); 391 Long counterId2 = mRecoverableKeyStoreDb.getCounterId(userId, uid); 392 393 assertThat(!counterId1.equals(counterId0) || !counterId2.equals(counterId1)).isTrue(); 394 } 395 396 @Test 397 public void initRecoveryService_throwsIfInvalidCert() throws Exception { 398 byte[] modifiedCertXml = TestData.getCertXml(); 399 modifiedCertXml[modifiedCertXml.length - 50] ^= 1; // Flip a bit in the certificate 400 try { 401 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 402 modifiedCertXml); 403 fail("should have thrown"); 404 } catch (ServiceSpecificException e) { 405 assertThat(e.errorCode).isEqualTo(ERROR_INVALID_CERTIFICATE); 406 } 407 } 408 409 @Test 410 public void initRecoveryService_updatesWithLargerSerial() throws Exception { 411 int uid = Binder.getCallingUid(); 412 int userId = UserHandle.getCallingUserId(); 413 long certSerial = 1000L; 414 415 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 416 TestData.getCertXmlWithSerial(certSerial)); 417 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 418 TestData.getCertXmlWithSerial(certSerial + 1)); 419 420 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, 421 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial + 1); 422 // There were no keys. 423 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 424 } 425 426 @Test 427 public void initRecoveryService_throwsExceptionOnSmallerSerial() throws Exception { 428 int uid = Binder.getCallingUid(); 429 int userId = UserHandle.getCallingUserId(); 430 long certSerial = 1000L; 431 432 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 433 TestData.getCertXmlWithSerial(certSerial)); 434 try { 435 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 436 TestData.getCertXmlWithSerial(certSerial - 1)); 437 fail(); 438 } catch (ServiceSpecificException e) { 439 assertThat(e.errorCode).isEqualTo(ERROR_DOWNGRADE_CERTIFICATE); 440 } 441 } 442 443 @Test 444 public void initRecoveryService_alwaysUpdatesCertsWhenTestRootCertIsUsed() throws Exception { 445 int uid = Binder.getCallingUid(); 446 int userId = UserHandle.getCallingUserId(); 447 int certSerial = 3333; 448 449 String testRootCertAlias = TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS; 450 451 mRecoverableKeyStoreManager.initRecoveryService(testRootCertAlias, 452 TestData.getInsecureCertXmlBytesWithEndpoint1(certSerial)); 453 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, 454 testRootCertAlias)).isEqualTo(certSerial); 455 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, 456 testRootCertAlias)).isEqualTo(TestData.getInsecureCertPathForEndpoint1()); 457 458 mRecoverableKeyStoreManager.initRecoveryService(testRootCertAlias, 459 TestData.getInsecureCertXmlBytesWithEndpoint2(certSerial - 1)); 460 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, 461 testRootCertAlias)).isEqualTo(certSerial - 1); 462 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, 463 testRootCertAlias)).isEqualTo(TestData.getInsecureCertPathForEndpoint2()); 464 } 465 466 @Test 467 public void initRecoveryService_updatesCertsIndependentlyForDifferentRoots() throws Exception { 468 int uid = Binder.getCallingUid(); 469 int userId = UserHandle.getCallingUserId(); 470 471 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 472 TestData.getCertXmlWithSerial(1111L)); 473 mRecoverableKeyStoreManager.initRecoveryService( 474 TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS, 475 TestData.getInsecureCertXmlBytesWithEndpoint1(2222)); 476 477 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, 478 ROOT_CERTIFICATE_ALIAS)).isEqualTo(1111L); 479 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, 480 TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS)).isEqualTo(2222L); 481 482 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, 483 ROOT_CERTIFICATE_ALIAS)).isEqualTo(TestData.CERT_PATH_1); 484 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, 485 TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS)).isEqualTo( 486 TestData.getInsecureCertPathForEndpoint1()); 487 } 488 489 @Test 490 public void initRecoveryService_ignoresTheSameSerial() throws Exception { 491 int uid = Binder.getCallingUid(); 492 int userId = UserHandle.getCallingUserId(); 493 long certSerial = 1000L; 494 495 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 496 TestData.getCertXmlWithSerial(certSerial)); 497 498 generateKeyAndSimulateSync(userId, uid, 10); 499 500 mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, 501 TestData.getCertXmlWithSerial(certSerial)); 502 503 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 504 } 505 506 @Test 507 public void initRecoveryService_throwsIfRawPublicKey() throws Exception { 508 int uid = Binder.getCallingUid(); 509 int userId = UserHandle.getCallingUserId(); 510 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 511 512 try { 513 mRecoverableKeyStoreManager 514 .initRecoveryService(ROOT_CERTIFICATE_ALIAS, TEST_PUBLIC_KEY); 515 fail("should have thrown"); 516 } catch (ServiceSpecificException e) { 517 assertThat(e.errorCode).isEqualTo(ERROR_BAD_CERTIFICATE_FORMAT); 518 } 519 520 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 521 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, 522 DEFAULT_ROOT_CERT_ALIAS)).isNull(); 523 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, 524 DEFAULT_ROOT_CERT_ALIAS)).isNull(); 525 assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); 526 } 527 528 @Test 529 public void initRecoveryService_throwsIfUnknownRootCertAlias() throws Exception { 530 try { 531 mRecoverableKeyStoreManager.initRecoveryService( 532 "unknown-root-cert-alias", TestData.getCertXml()); 533 fail("should have thrown"); 534 } catch (ServiceSpecificException e) { 535 assertThat(e.errorCode).isEqualTo(ERROR_INVALID_CERTIFICATE); 536 } 537 } 538 539 @Test 540 public void initRecoveryServiceWithSigFile_succeeds() throws Exception { 541 int uid = Binder.getCallingUid(); 542 int userId = UserHandle.getCallingUserId(); 543 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 544 545 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( 546 ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(), TestData.getSigXml()); 547 548 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 549 assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, 550 DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1); 551 assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); 552 } 553 554 @Test 555 public void initRecoveryServiceWithSigFile_usesProdCertificateForNullRootAlias() 556 throws Exception { 557 int uid = Binder.getCallingUid(); 558 int userId = UserHandle.getCallingUserId(); 559 long certSerial = 1000L; 560 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 561 562 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( 563 /*rootCertificateAlias=*/null, TestData.getCertXml(), TestData.getSigXml()); 564 565 verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) 566 .getDefaultCertificateAliasIfEmpty(null); 567 568 verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) 569 .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); 570 } 571 572 @Test 573 public void initRecoveryServiceWithSigFile_throwsIfNullCertFile() throws Exception { 574 try { 575 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( 576 ROOT_CERTIFICATE_ALIAS, /*recoveryServiceCertFile=*/ null, 577 TestData.getSigXml()); 578 fail("should have thrown"); 579 } catch (NullPointerException e) { 580 assertThat(e.getMessage()).contains("is null"); 581 } 582 } 583 584 @Test 585 public void initRecoveryServiceWithSigFile_throwsIfNullSigFile() throws Exception { 586 try { 587 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( 588 ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(), 589 /*recoveryServiceSigFile=*/ null); 590 fail("should have thrown"); 591 } catch (NullPointerException e) { 592 assertThat(e.getMessage()).contains("is null"); 593 } 594 } 595 596 @Test 597 public void initRecoveryServiceWithSigFile_throwsIfWrongSigFileFormat() throws Exception { 598 try { 599 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( 600 ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(), 601 getUtf8Bytes("wrong-sig-file-format")); 602 fail("should have thrown"); 603 } catch (ServiceSpecificException e) { 604 assertThat(e.errorCode).isEqualTo(ERROR_BAD_CERTIFICATE_FORMAT); 605 } 606 } 607 608 @Test 609 public void initRecoveryServiceWithSigFile_throwsIfTestAliasUsedWithProdCert() 610 throws Exception { 611 try { 612 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( 613 INSECURE_CERTIFICATE_ALIAS, TestData.getCertXml(), TestData.getSigXml()); 614 fail("should have thrown"); 615 } catch (ServiceSpecificException e) { 616 assertThat(e.errorCode).isEqualTo(ERROR_INVALID_CERTIFICATE); 617 } 618 } 619 620 @Test 621 public void initRecoveryServiceWithSigFile_throwsIfInvalidFileSignature() throws Exception { 622 byte[] modifiedCertXml = TestData.getCertXml(); 623 modifiedCertXml[modifiedCertXml.length - 1] = 0; // Change the last new line char to a zero 624 try { 625 mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( 626 ROOT_CERTIFICATE_ALIAS, modifiedCertXml, TestData.getSigXml()); 627 fail("should have thrown"); 628 } catch (ServiceSpecificException e) { 629 assertThat(e.getMessage()).contains("is invalid"); 630 } 631 } 632 633 @Test 634 public void startRecoverySession_checksPermissionFirst() throws Exception { 635 mRecoverableKeyStoreManager.startRecoverySession( 636 TEST_SESSION_ID, 637 TEST_PUBLIC_KEY, 638 TEST_VAULT_PARAMS, 639 TEST_VAULT_CHALLENGE, 640 ImmutableList.of(TEST_PROTECTION_PARAMS)); 641 642 verify(mMockContext, times(1)) 643 .enforceCallingOrSelfPermission( 644 eq(Manifest.permission.RECOVER_KEYSTORE), any()); 645 } 646 647 @Test 648 public void startRecoverySessionWithCertPath_storesTheSessionInfo() throws Exception { 649 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( 650 TEST_SESSION_ID, 651 TEST_DEFAULT_ROOT_CERT_ALIAS, 652 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1), 653 TEST_VAULT_PARAMS, 654 TEST_VAULT_CHALLENGE, 655 ImmutableList.of(TEST_PROTECTION_PARAMS)); 656 657 assertEquals(1, mRecoverySessionStorage.size()); 658 RecoverySessionStorage.Entry entry = 659 mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID); 660 assertArrayEquals(TEST_SECRET, entry.getLskfHash()); 661 assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length); 662 } 663 664 @Test 665 public void startRecoverySessionWithCertPath_checksPermissionFirst() throws Exception { 666 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( 667 TEST_SESSION_ID, 668 TEST_DEFAULT_ROOT_CERT_ALIAS, 669 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1), 670 TEST_VAULT_PARAMS, 671 TEST_VAULT_CHALLENGE, 672 ImmutableList.of(TEST_PROTECTION_PARAMS)); 673 674 verify(mMockContext, times(2)) 675 .enforceCallingOrSelfPermission( 676 eq(Manifest.permission.RECOVER_KEYSTORE), any()); 677 } 678 679 @Test 680 public void startRecoverySession_storesTheSessionInfo() throws Exception { 681 mRecoverableKeyStoreManager.startRecoverySession( 682 TEST_SESSION_ID, 683 TEST_PUBLIC_KEY, 684 TEST_VAULT_PARAMS, 685 TEST_VAULT_CHALLENGE, 686 ImmutableList.of(TEST_PROTECTION_PARAMS)); 687 688 assertEquals(1, mRecoverySessionStorage.size()); 689 RecoverySessionStorage.Entry entry = 690 mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID); 691 assertArrayEquals(TEST_SECRET, entry.getLskfHash()); 692 assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length); 693 } 694 695 @Test 696 public void closeSession_closesASession() throws Exception { 697 mRecoverableKeyStoreManager.startRecoverySession( 698 TEST_SESSION_ID, 699 TEST_PUBLIC_KEY, 700 TEST_VAULT_PARAMS, 701 TEST_VAULT_CHALLENGE, 702 ImmutableList.of(TEST_PROTECTION_PARAMS)); 703 704 mRecoverableKeyStoreManager.closeSession(TEST_SESSION_ID); 705 706 assertEquals(0, mRecoverySessionStorage.size()); 707 } 708 709 @Test 710 public void closeSession_doesNotCloseUnrelatedSessions() throws Exception { 711 mRecoverableKeyStoreManager.startRecoverySession( 712 TEST_SESSION_ID, 713 TEST_PUBLIC_KEY, 714 TEST_VAULT_PARAMS, 715 TEST_VAULT_CHALLENGE, 716 ImmutableList.of(TEST_PROTECTION_PARAMS)); 717 718 mRecoverableKeyStoreManager.closeSession("some random session"); 719 720 assertEquals(1, mRecoverySessionStorage.size()); 721 } 722 723 @Test 724 public void closeSession_throwsIfNullSession() throws Exception { 725 try { 726 mRecoverableKeyStoreManager.closeSession(/*sessionId=*/ null); 727 fail("should have thrown"); 728 } catch (NullPointerException e) { 729 assertThat(e.getMessage()).contains("invalid"); 730 } 731 } 732 733 @Test 734 public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception { 735 try { 736 mRecoverableKeyStoreManager.startRecoverySession( 737 TEST_SESSION_ID, 738 TEST_PUBLIC_KEY, 739 TEST_VAULT_PARAMS, 740 TEST_VAULT_CHALLENGE, 741 ImmutableList.of()); 742 fail("should have thrown"); 743 } catch (UnsupportedOperationException e) { 744 assertThat(e.getMessage()).startsWith( 745 "Only a single KeyChainProtectionParams is supported"); 746 } 747 } 748 749 @Test 750 public void startRecoverySession_throwsIfPublicKeysMismatch() throws Exception { 751 byte[] vaultParams = TEST_VAULT_PARAMS.clone(); 752 vaultParams[1] ^= (byte) 1; // Flip 1 bit 753 754 try { 755 mRecoverableKeyStoreManager.startRecoverySession( 756 TEST_SESSION_ID, 757 TEST_PUBLIC_KEY, 758 vaultParams, 759 TEST_VAULT_CHALLENGE, 760 ImmutableList.of(TEST_PROTECTION_PARAMS)); 761 fail("should have thrown"); 762 } catch (ServiceSpecificException e) { 763 assertThat(e.getMessage()).contains("do not match"); 764 } 765 } 766 767 @Test 768 public void startRecoverySessionWithCertPath_throwsIfBadNumberOfSecrets() throws Exception { 769 try { 770 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( 771 TEST_SESSION_ID, 772 TEST_DEFAULT_ROOT_CERT_ALIAS, 773 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1), 774 TEST_VAULT_PARAMS, 775 TEST_VAULT_CHALLENGE, 776 ImmutableList.of()); 777 fail("should have thrown"); 778 } catch (UnsupportedOperationException e) { 779 assertThat(e.getMessage()).startsWith( 780 "Only a single KeyChainProtectionParams is supported"); 781 } 782 } 783 784 @Test 785 public void startRecoverySessionWithCertPath_throwsIfPublicKeysMismatch() throws Exception { 786 byte[] vaultParams = TEST_VAULT_PARAMS.clone(); 787 vaultParams[1] ^= (byte) 1; // Flip 1 bit 788 try { 789 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( 790 TEST_SESSION_ID, 791 TEST_DEFAULT_ROOT_CERT_ALIAS, 792 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1), 793 vaultParams, 794 TEST_VAULT_CHALLENGE, 795 ImmutableList.of(TEST_PROTECTION_PARAMS)); 796 fail("should have thrown"); 797 } catch (ServiceSpecificException e) { 798 assertThat(e.getMessage()).contains("do not match"); 799 } 800 } 801 802 @Test 803 public void startRecoverySessionWithCertPath_throwsIfEmptyCertPath() throws Exception { 804 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 805 CertPath emptyCertPath = certFactory.generateCertPath(new ArrayList<X509Certificate>()); 806 try { 807 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( 808 TEST_SESSION_ID, 809 TEST_DEFAULT_ROOT_CERT_ALIAS, 810 RecoveryCertPath.createRecoveryCertPath(emptyCertPath), 811 TEST_VAULT_PARAMS, 812 TEST_VAULT_CHALLENGE, 813 ImmutableList.of(TEST_PROTECTION_PARAMS)); 814 fail("should have thrown"); 815 } catch (ServiceSpecificException e) { 816 assertThat(e.getMessage()).contains("empty"); 817 } 818 } 819 820 @Test 821 public void startRecoverySessionWithCertPath_throwsIfInvalidCertPath() throws Exception { 822 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 823 CertPath shortCertPath = certFactory.generateCertPath( 824 TestData.CERT_PATH_1.getCertificates() 825 .subList(0, TestData.CERT_PATH_1.getCertificates().size() - 1)); 826 try { 827 mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( 828 TEST_SESSION_ID, 829 TEST_DEFAULT_ROOT_CERT_ALIAS, 830 RecoveryCertPath.createRecoveryCertPath(shortCertPath), 831 TEST_VAULT_PARAMS, 832 TEST_VAULT_CHALLENGE, 833 ImmutableList.of(TEST_PROTECTION_PARAMS)); 834 fail("should have thrown"); 835 } catch (ServiceSpecificException e) { 836 // expected 837 } 838 } 839 840 @Test 841 public void recoverKeyChainSnapshot_throwsIfNoSessionIsPresent() throws Exception { 842 try { 843 WrappedApplicationKey applicationKey = new WrappedApplicationKey.Builder() 844 .setAlias(TEST_ALIAS) 845 .setEncryptedKeyMaterial(randomBytes(32)) 846 .build(); 847 mRecoverableKeyStoreManager.recoverKeyChainSnapshot( 848 TEST_SESSION_ID, 849 /*recoveryKeyBlob=*/ randomBytes(32), 850 /*applicationKeys=*/ ImmutableList.of(applicationKey)); 851 fail("should have thrown"); 852 } catch (ServiceSpecificException e) { 853 // expected 854 } 855 } 856 857 @Test 858 public void recoverKeyChainSnapshot_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception { 859 mRecoverableKeyStoreManager.startRecoverySession( 860 TEST_SESSION_ID, 861 TEST_PUBLIC_KEY, 862 TEST_VAULT_PARAMS, 863 TEST_VAULT_CHALLENGE, 864 ImmutableList.of(TEST_PROTECTION_PARAMS)); 865 866 try { 867 mRecoverableKeyStoreManager.recoverKeyChainSnapshot( 868 TEST_SESSION_ID, 869 /*encryptedRecoveryKey=*/ randomBytes(60), 870 /*applicationKeys=*/ ImmutableList.of()); 871 fail("should have thrown"); 872 } catch (ServiceSpecificException e) { 873 assertThat(e.getMessage()).startsWith("Failed to decrypt recovery key"); 874 } 875 } 876 877 @Test 878 public void recoverKeyChainSnapshot_throwsIfFailedToDecryptAllApplicationKeys() 879 throws Exception { 880 mRecoverableKeyStoreManager.startRecoverySession( 881 TEST_SESSION_ID, 882 TEST_PUBLIC_KEY, 883 TEST_VAULT_PARAMS, 884 TEST_VAULT_CHALLENGE, 885 ImmutableList.of(TEST_PROTECTION_PARAMS)); 886 byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) 887 .getKeyClaimant(); 888 SecretKey recoveryKey = randomRecoveryKey(); 889 byte[] encryptedClaimResponse = encryptClaimResponse( 890 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); 891 WrappedApplicationKey badApplicationKey = new WrappedApplicationKey.Builder() 892 .setAlias(TEST_ALIAS) 893 .setEncryptedKeyMaterial( 894 encryptedApplicationKey(randomRecoveryKey(), randomBytes(32))) 895 .build(); 896 try { 897 mRecoverableKeyStoreManager.recoverKeyChainSnapshot( 898 TEST_SESSION_ID, 899 /*encryptedRecoveryKey=*/ encryptedClaimResponse, 900 /*applicationKeys=*/ ImmutableList.of(badApplicationKey)); 901 fail("should have thrown"); 902 } catch (ServiceSpecificException e) { 903 assertThat(e.getMessage()).startsWith("Failed to recover any of the application keys"); 904 } 905 } 906 907 @Test 908 public void recoverKeyChainSnapshot_doesNotThrowIfNoApplicationKeysToBeDecrypted() 909 throws Exception { 910 mRecoverableKeyStoreManager.startRecoverySession( 911 TEST_SESSION_ID, 912 TEST_PUBLIC_KEY, 913 TEST_VAULT_PARAMS, 914 TEST_VAULT_CHALLENGE, 915 ImmutableList.of(TEST_PROTECTION_PARAMS)); 916 byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) 917 .getKeyClaimant(); 918 SecretKey recoveryKey = randomRecoveryKey(); 919 byte[] encryptedClaimResponse = encryptClaimResponse( 920 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); 921 922 mRecoverableKeyStoreManager.recoverKeyChainSnapshot( 923 TEST_SESSION_ID, 924 /*encryptedRecoveryKey=*/ encryptedClaimResponse, 925 /*applicationKeys=*/ ImmutableList.of()); 926 } 927 928 @Test 929 public void recoverKeyChainSnapshot_returnsDecryptedKeys() throws Exception { 930 mRecoverableKeyStoreManager.startRecoverySession( 931 TEST_SESSION_ID, 932 TEST_PUBLIC_KEY, 933 TEST_VAULT_PARAMS, 934 TEST_VAULT_CHALLENGE, 935 ImmutableList.of(TEST_PROTECTION_PARAMS)); 936 byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) 937 .getKeyClaimant(); 938 SecretKey recoveryKey = randomRecoveryKey(); 939 byte[] encryptedClaimResponse = encryptClaimResponse( 940 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); 941 byte[] applicationKeyBytes = randomBytes(32); 942 WrappedApplicationKey applicationKey = new WrappedApplicationKey.Builder() 943 .setAlias(TEST_ALIAS) 944 .setEncryptedKeyMaterial( 945 encryptedApplicationKey(recoveryKey, applicationKeyBytes)) 946 .build(); 947 948 Map<String, String> recoveredKeys = mRecoverableKeyStoreManager.recoverKeyChainSnapshot( 949 TEST_SESSION_ID, 950 encryptedClaimResponse, 951 ImmutableList.of(applicationKey)); 952 953 assertThat(recoveredKeys).hasSize(1); 954 assertThat(recoveredKeys).containsKey(TEST_ALIAS); 955 } 956 957 @Test 958 public void recoverKeyChainSnapshot_worksOnOtherApplicationKeysIfOneDecryptionFails() 959 throws Exception { 960 mRecoverableKeyStoreManager.startRecoverySession( 961 TEST_SESSION_ID, 962 TEST_PUBLIC_KEY, 963 TEST_VAULT_PARAMS, 964 TEST_VAULT_CHALLENGE, 965 ImmutableList.of(TEST_PROTECTION_PARAMS)); 966 byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) 967 .getKeyClaimant(); 968 SecretKey recoveryKey = randomRecoveryKey(); 969 byte[] encryptedClaimResponse = encryptClaimResponse( 970 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); 971 972 byte[] applicationKeyBytes1 = randomBytes(32); 973 byte[] applicationKeyBytes2 = randomBytes(32); 974 WrappedApplicationKey applicationKey1 = new WrappedApplicationKey.Builder() 975 .setAlias(TEST_ALIAS) 976 // Use a different recovery key here, so the decryption will fail 977 .setEncryptedKeyMaterial( 978 encryptedApplicationKey(randomRecoveryKey(), applicationKeyBytes1)) 979 .build(); 980 WrappedApplicationKey applicationKey2 = new WrappedApplicationKey.Builder() 981 .setAlias(TEST_ALIAS2) 982 .setEncryptedKeyMaterial( 983 encryptedApplicationKey(recoveryKey, applicationKeyBytes2)) 984 .build(); 985 986 Map<String, String> recoveredKeys = mRecoverableKeyStoreManager.recoverKeyChainSnapshot( 987 TEST_SESSION_ID, 988 encryptedClaimResponse, 989 ImmutableList.of(applicationKey1, applicationKey2)); 990 991 assertThat(recoveredKeys).hasSize(1); 992 assertThat(recoveredKeys).containsKey(TEST_ALIAS2); 993 } 994 995 @Test 996 public void setSnapshotCreatedPendingIntent() throws Exception { 997 int uid = Binder.getCallingUid(); 998 PendingIntent intent = PendingIntent.getBroadcast( 999 InstrumentationRegistry.getTargetContext(), /*requestCode=*/1, 1000 new Intent(), /*flags=*/ 0); 1001 mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent); 1002 verify(mMockListenersStorage).setSnapshotListener(eq(uid), any(PendingIntent.class)); 1003 } 1004 1005 @Test 1006 public void setServerParams_updatesServerParams() throws Exception { 1007 int uid = Binder.getCallingUid(); 1008 int userId = UserHandle.getCallingUserId(); 1009 byte[] serverParams = new byte[] { 1 }; 1010 1011 mRecoverableKeyStoreManager.setServerParams(serverParams); 1012 1013 assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(serverParams); 1014 } 1015 1016 @Test 1017 public void setServerParams_doesNotSetSnapshotPendingIfInitializing() throws Exception { 1018 int uid = Binder.getCallingUid(); 1019 int userId = UserHandle.getCallingUserId(); 1020 byte[] serverParams = new byte[] { 1 }; 1021 1022 mRecoverableKeyStoreManager.setServerParams(serverParams); 1023 1024 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 1025 } 1026 1027 @Test 1028 public void setServerParams_doesNotSetSnapshotPendingIfSettingSameValue() throws Exception { 1029 int uid = Binder.getCallingUid(); 1030 int userId = UserHandle.getCallingUserId(); 1031 byte[] serverParams = new byte[] { 1 }; 1032 1033 mRecoverableKeyStoreManager.setServerParams(serverParams); 1034 1035 generateKeyAndSimulateSync(userId, uid, 10); 1036 1037 mRecoverableKeyStoreManager.setServerParams(serverParams); 1038 1039 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 1040 } 1041 1042 @Test 1043 public void setServerParams_setsSnapshotPendingIfUpdatingValue() throws Exception { 1044 int uid = Binder.getCallingUid(); 1045 int userId = UserHandle.getCallingUserId(); 1046 1047 mRecoverableKeyStoreManager.setServerParams(new byte[] { 1 }); 1048 1049 generateKeyAndSimulateSync(userId, uid, 10); 1050 1051 mRecoverableKeyStoreManager.setServerParams(new byte[] { 2 }); 1052 1053 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); 1054 } 1055 1056 @Test 1057 public void setRecoverySecretTypes_updatesSecretTypes() throws Exception { 1058 int[] types1 = new int[]{11, 2000}; 1059 int[] types2 = new int[]{1, 2, 3}; 1060 int[] types3 = new int[]{}; 1061 1062 mRecoverableKeyStoreManager.setRecoverySecretTypes(types1); 1063 assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo( 1064 types1); 1065 1066 mRecoverableKeyStoreManager.setRecoverySecretTypes(types2); 1067 assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo( 1068 types2); 1069 1070 mRecoverableKeyStoreManager.setRecoverySecretTypes(types3); 1071 assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo( 1072 types3); 1073 } 1074 1075 @Test 1076 public void setRecoverySecretTypes_doesNotSetSnapshotPendingIfIniting() throws Exception { 1077 int uid = Binder.getCallingUid(); 1078 int userId = UserHandle.getCallingUserId(); 1079 int[] secretTypes = new int[] { 101 }; 1080 1081 mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes); 1082 1083 // There were no keys. 1084 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 1085 } 1086 1087 @Test 1088 public void setRecoverySecretTypes_doesNotSetSnapshotPendingIfSettingSameValue() 1089 throws Exception { 1090 int uid = Binder.getCallingUid(); 1091 int userId = UserHandle.getCallingUserId(); 1092 int[] secretTypes = new int[] { 101 }; 1093 1094 mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes); 1095 1096 generateKeyAndSimulateSync(userId, uid, 10); 1097 1098 mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes); 1099 1100 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 1101 } 1102 1103 @Test 1104 public void setRecoverySecretTypes_setsSnapshotPendingIfUpdatingValue() throws Exception { 1105 int uid = Binder.getCallingUid(); 1106 int userId = UserHandle.getCallingUserId(); 1107 1108 mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 101 }); 1109 1110 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); 1111 1112 generateKeyAndSimulateSync(userId, uid, 10); 1113 1114 mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 102 }); 1115 1116 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); 1117 } 1118 1119 @Test 1120 public void setRecoverySecretTypes_throwsIfNullTypes() throws Exception { 1121 try { 1122 mRecoverableKeyStoreManager.setRecoverySecretTypes(/*types=*/ null); 1123 fail("should have thrown"); 1124 } catch (NullPointerException e) { 1125 assertThat(e.getMessage()).contains("is null"); 1126 } 1127 } 1128 1129 @Test 1130 public void setRecoverySecretTypes_updatesShouldCreateSnapshot() throws Exception { 1131 int uid = Binder.getCallingUid(); 1132 int userId = UserHandle.getCallingUserId(); 1133 mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 1 }); 1134 1135 generateKeyAndSimulateSync(userId, uid, 10); 1136 1137 mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 2 }); 1138 1139 assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); 1140 } 1141 1142 @Test 1143 public void setRecoveryStatus() throws Exception { 1144 int userId = UserHandle.getCallingUserId(); 1145 int uid = Binder.getCallingUid(); 1146 int status = 100; 1147 int status2 = 200; 1148 String alias = "key1"; 1149 WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status); 1150 mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey); 1151 Map<String, Integer> statuses = 1152 mRecoverableKeyStoreManager.getRecoveryStatus(); 1153 assertThat(statuses).hasSize(1); 1154 assertThat(statuses).containsEntry(alias, status); 1155 1156 mRecoverableKeyStoreManager.setRecoveryStatus(alias, status2); 1157 statuses = mRecoverableKeyStoreManager.getRecoveryStatus(); 1158 assertThat(statuses).hasSize(1); 1159 assertThat(statuses).containsEntry(alias, status2); // updated 1160 } 1161 1162 @Test 1163 public void setRecoveryStatus_throwsIfNullAlias() throws Exception { 1164 try { 1165 mRecoverableKeyStoreManager.setRecoveryStatus(/*alias=*/ null, /*status=*/ 100); 1166 fail("should have thrown"); 1167 } catch (NullPointerException e) { 1168 assertThat(e.getMessage()).contains("is null"); 1169 } 1170 } 1171 1172 private static byte[] encryptedApplicationKey( 1173 SecretKey recoveryKey, byte[] applicationKey) throws Exception { 1174 return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of( 1175 TEST_ALIAS, new SecretKeySpec(applicationKey, "AES") 1176 )).get(TEST_ALIAS); 1177 } 1178 1179 private static byte[] encryptClaimResponse( 1180 byte[] keyClaimant, 1181 byte[] lskfHash, 1182 byte[] vaultParams, 1183 SecretKey recoveryKey) throws Exception { 1184 byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey( 1185 lskfHash, recoveryKey); 1186 return SecureBox.encrypt( 1187 /*theirPublicKey=*/ null, 1188 /*sharedSecret=*/ keyClaimant, 1189 /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), 1190 /*payload=*/ locallyEncryptedRecoveryKey); 1191 } 1192 1193 private static SecretKey randomRecoveryKey() { 1194 return new SecretKeySpec(randomBytes(32), "AES"); 1195 } 1196 1197 private static byte[] getUtf8Bytes(String s) { 1198 return s.getBytes(StandardCharsets.UTF_8); 1199 } 1200 1201 private static byte[] randomBytes(int n) { 1202 byte[] bytes = new byte[n]; 1203 new Random().nextBytes(bytes); 1204 return bytes; 1205 } 1206 1207 private void generateKeyAndSimulateSync(int userId, int uid, int snapshotVersion) 1208 throws Exception{ 1209 mRecoverableKeyStoreManager.generateKey(TEST_ALIAS); 1210 // Simulate key sync. 1211 mRecoverableKeyStoreDb.setSnapshotVersion(userId, uid, snapshotVersion); 1212 mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); 1213 } 1214 1215 private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { 1216 KeyGenerator keyGenerator = KeyGenerator.getInstance( 1217 KEY_ALGORITHM, 1218 ANDROID_KEY_STORE_PROVIDER); 1219 keyGenerator.init(new KeyGenParameterSpec.Builder( 1220 WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 1221 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 1222 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 1223 .build()); 1224 return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); 1225 } 1226 } 1227