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