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 junit.framework.Assert.fail; 20 21 import static org.junit.Assert.assertArrayEquals; 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertFalse; 24 25 import android.util.Pair; 26 27 import androidx.test.filters.SmallTest; 28 import androidx.test.runner.AndroidJUnit4; 29 30 import com.android.internal.util.ArrayUtils; 31 import com.android.security.SecureBox; 32 33 import com.google.common.collect.ImmutableMap; 34 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 38 import java.nio.ByteBuffer; 39 import java.nio.ByteOrder; 40 import java.nio.charset.StandardCharsets; 41 import java.security.KeyPair; 42 import java.security.MessageDigest; 43 import java.security.PublicKey; 44 import java.util.Arrays; 45 import java.util.Map; 46 import java.util.Random; 47 48 import javax.crypto.AEADBadTagException; 49 import javax.crypto.KeyGenerator; 50 import javax.crypto.SecretKey; 51 52 @SmallTest 53 @RunWith(AndroidJUnit4.class) 54 public class KeySyncUtilsTest { 55 private static final int RECOVERY_KEY_LENGTH_BITS = 256; 56 private static final int THM_KF_HASH_SIZE = 256; 57 private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; 58 private static final byte[] TEST_VAULT_HANDLE = 59 new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}; 60 private static final int VAULT_PARAMS_LENGTH_BYTES = 94; 61 private static final int VAULT_HANDLE_LENGTH_BYTES = 17; 62 private static final String SHA_256_ALGORITHM = "SHA-256"; 63 private static final String APPLICATION_KEY_ALGORITHM = "AES"; 64 private static final byte[] LOCK_SCREEN_HASH_1 = 65 utf8Bytes("g09TEvo6XqVdNaYdRggzn5w2C5rCeE1F"); 66 private static final byte[] LOCK_SCREEN_HASH_2 = 67 utf8Bytes("snQzsbvclkSsG6PwasAp1oFLzbq3KtFe"); 68 private static final byte[] RECOVERY_CLAIM_HEADER = 69 "V1 KF_claim".getBytes(StandardCharsets.UTF_8); 70 private static final byte[] RECOVERY_RESPONSE_HEADER = 71 "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); 72 private static final int PUBLIC_KEY_LENGTH_BYTES = 65; 73 private static final byte[] NULL_METADATA = null; 74 private static final byte[] NON_NULL_METADATA = "somemetadata".getBytes(StandardCharsets.UTF_8); 75 76 @Test calculateThmKfHash_isShaOfLockScreenHashWithPrefix()77 public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception { 78 byte[] lockScreenHash = utf8Bytes("012345678910"); 79 80 byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(lockScreenHash); 81 82 assertArrayEquals(calculateSha256(utf8Bytes("THM_KF_hash012345678910")), thmKfHash); 83 } 84 85 @Test calculateThmKfHash_is256BitsLong()86 public void calculateThmKfHash_is256BitsLong() throws Exception { 87 byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(utf8Bytes("1234")); 88 89 assertEquals(THM_KF_HASH_SIZE / Byte.SIZE, thmKfHash.length); 90 } 91 92 @Test generateRecoveryKey_returnsA256BitKey()93 public void generateRecoveryKey_returnsA256BitKey() throws Exception { 94 SecretKey key = KeySyncUtils.generateRecoveryKey(); 95 96 assertEquals(RECOVERY_KEY_LENGTH_BITS / Byte.SIZE, key.getEncoded().length); 97 } 98 99 @Test generateRecoveryKey_generatesANewKeyEachTime()100 public void generateRecoveryKey_generatesANewKeyEachTime() throws Exception { 101 SecretKey a = KeySyncUtils.generateRecoveryKey(); 102 SecretKey b = KeySyncUtils.generateRecoveryKey(); 103 104 assertFalse(Arrays.equals(a.getEncoded(), b.getEncoded())); 105 } 106 107 @Test generateKeyClaimant_returns16Bytes()108 public void generateKeyClaimant_returns16Bytes() throws Exception { 109 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); 110 111 assertEquals(KEY_CLAIMANT_LENGTH_BYTES, keyClaimant.length); 112 } 113 114 @Test generateKeyClaimant_generatesANewClaimantEachTime()115 public void generateKeyClaimant_generatesANewClaimantEachTime() { 116 byte[] a = KeySyncUtils.generateKeyClaimant(); 117 byte[] b = KeySyncUtils.generateKeyClaimant(); 118 119 assertFalse(Arrays.equals(a, b)); 120 } 121 122 @Test decryptApplicationKey_decryptsAnApplicationKey_nullMetadata()123 public void decryptApplicationKey_decryptsAnApplicationKey_nullMetadata() throws Exception { 124 String alias = "phoebe"; 125 SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey(); 126 SecretKey applicationKey = generateApplicationKey(); 127 Map<String, byte[]> encryptedKeys = 128 KeySyncUtils.encryptKeysWithRecoveryKey( 129 recoveryKey, 130 ImmutableMap.of(alias, Pair.create(applicationKey, NULL_METADATA))); 131 byte[] encryptedKey = encryptedKeys.get(alias); 132 133 byte[] keyMaterial = KeySyncUtils.decryptApplicationKey(recoveryKey.getEncoded(), 134 encryptedKey, NULL_METADATA); 135 136 assertArrayEquals(applicationKey.getEncoded(), keyMaterial); 137 } 138 139 @Test decryptApplicationKey_decryptsAnApplicationKey_nonNullMetadata()140 public void decryptApplicationKey_decryptsAnApplicationKey_nonNullMetadata() throws Exception { 141 String alias = "phoebe"; 142 SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey(); 143 SecretKey applicationKey = generateApplicationKey(); 144 Map<String, byte[]> encryptedKeys = 145 KeySyncUtils.encryptKeysWithRecoveryKey( 146 recoveryKey, 147 ImmutableMap.of(alias, Pair.create(applicationKey, NON_NULL_METADATA))); 148 byte[] encryptedKey = encryptedKeys.get(alias); 149 150 byte[] keyMaterial = KeySyncUtils.decryptApplicationKey(recoveryKey.getEncoded(), 151 encryptedKey, NON_NULL_METADATA); 152 153 assertArrayEquals(applicationKey.getEncoded(), keyMaterial); 154 } 155 156 @Test decryptApplicationKey_throwsIfUnableToDecrypt()157 public void decryptApplicationKey_throwsIfUnableToDecrypt() throws Exception { 158 String alias = "casper"; 159 Map<String, byte[]> encryptedKeys = 160 KeySyncUtils.encryptKeysWithRecoveryKey( 161 KeySyncUtils.generateRecoveryKey(), 162 ImmutableMap.of("casper", 163 Pair.create(generateApplicationKey(), NULL_METADATA))); 164 byte[] encryptedKey = encryptedKeys.get(alias); 165 166 try { 167 KeySyncUtils.decryptApplicationKey(KeySyncUtils.generateRecoveryKey().getEncoded(), 168 encryptedKey, NULL_METADATA); 169 fail("Did not throw decrypting with bad key."); 170 } catch (AEADBadTagException error) { 171 // expected 172 } 173 } 174 175 @Test decryptApplicationKey_throwsIfWrongMetadata()176 public void decryptApplicationKey_throwsIfWrongMetadata() throws Exception { 177 String alias1 = "casper1"; 178 String alias2 = "casper2"; 179 String alias3 = "casper3"; 180 SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey(); 181 182 Map<String, byte[]> encryptedKeys = 183 KeySyncUtils.encryptKeysWithRecoveryKey( 184 recoveryKey, 185 ImmutableMap.of( 186 alias1, 187 Pair.create(generateApplicationKey(), NULL_METADATA), 188 alias2, 189 Pair.create(generateApplicationKey(), NON_NULL_METADATA), 190 alias3, 191 Pair.create(generateApplicationKey(), NON_NULL_METADATA))); 192 193 try { 194 KeySyncUtils.decryptApplicationKey(recoveryKey.getEncoded(), 195 encryptedKeys.get(alias1), NON_NULL_METADATA); 196 fail("Did not throw decrypting with wrong metadata."); 197 } catch (AEADBadTagException error) { 198 // expected 199 } 200 try { 201 KeySyncUtils.decryptApplicationKey(recoveryKey.getEncoded(), 202 encryptedKeys.get(alias2), NULL_METADATA); 203 fail("Did not throw decrypting with wrong metadata."); 204 } catch (AEADBadTagException error) { 205 // expected 206 } 207 try { 208 KeySyncUtils.decryptApplicationKey(recoveryKey.getEncoded(), 209 encryptedKeys.get(alias3), "different".getBytes(StandardCharsets.UTF_8)); 210 fail("Did not throw decrypting with wrong metadata."); 211 } catch (AEADBadTagException error) { 212 // expected 213 } 214 } 215 216 @Test decryptRecoveryKey_decryptsALocallyEncryptedKey()217 public void decryptRecoveryKey_decryptsALocallyEncryptedKey() throws Exception { 218 SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey(); 219 byte[] encrypted = KeySyncUtils.locallyEncryptRecoveryKey( 220 LOCK_SCREEN_HASH_1, recoveryKey); 221 222 byte[] keyMaterial = KeySyncUtils.decryptRecoveryKey(LOCK_SCREEN_HASH_1, encrypted); 223 224 assertArrayEquals(recoveryKey.getEncoded(), keyMaterial); 225 } 226 227 @Test decryptRecoveryKey_throwsIfCannotDecrypt()228 public void decryptRecoveryKey_throwsIfCannotDecrypt() throws Exception { 229 SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey(); 230 byte[] encrypted = KeySyncUtils.locallyEncryptRecoveryKey(LOCK_SCREEN_HASH_1, recoveryKey); 231 232 try { 233 KeySyncUtils.decryptRecoveryKey(LOCK_SCREEN_HASH_2, encrypted); 234 fail("Did not throw decrypting with bad key."); 235 } catch (AEADBadTagException error) { 236 // expected 237 } 238 } 239 240 @Test decryptRecoveryClaimResponse_decryptsAValidResponse()241 public void decryptRecoveryClaimResponse_decryptsAValidResponse() throws Exception { 242 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); 243 byte[] vaultParams = randomBytes(100); 244 byte[] recoveryKey = randomBytes(32); 245 byte[] encryptedPayload = SecureBox.encrypt( 246 /*theirPublicKey=*/ null, 247 /*sharedSecret=*/ keyClaimant, 248 /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), 249 /*payload=*/ recoveryKey); 250 251 byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse( 252 keyClaimant, vaultParams, encryptedPayload); 253 254 assertArrayEquals(recoveryKey, decrypted); 255 } 256 257 @Test decryptRecoveryClaimResponse_throwsIfCannotDecrypt()258 public void decryptRecoveryClaimResponse_throwsIfCannotDecrypt() throws Exception { 259 byte[] vaultParams = randomBytes(100); 260 byte[] recoveryKey = randomBytes(32); 261 byte[] encryptedPayload = SecureBox.encrypt( 262 /*theirPublicKey=*/ null, 263 /*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(), 264 /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), 265 /*payload=*/ recoveryKey); 266 267 try { 268 KeySyncUtils.decryptRecoveryClaimResponse( 269 KeySyncUtils.generateKeyClaimant(), vaultParams, encryptedPayload); 270 fail("Did not throw decrypting with bad keyClaimant"); 271 } catch (AEADBadTagException error) { 272 // expected 273 } 274 } 275 276 @Test encryptRecoveryClaim_encryptsLockScreenAndKeyClaimant()277 public void encryptRecoveryClaim_encryptsLockScreenAndKeyClaimant() throws Exception { 278 KeyPair keyPair = SecureBox.genKeyPair(); 279 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); 280 byte[] challenge = randomBytes(32); 281 byte[] vaultParams = randomBytes(100); 282 283 byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim( 284 keyPair.getPublic(), 285 vaultParams, 286 challenge, 287 LOCK_SCREEN_HASH_1, 288 keyClaimant); 289 290 byte[] decrypted = SecureBox.decrypt( 291 keyPair.getPrivate(), 292 /*sharedSecret=*/ null, 293 /*header=*/ ArrayUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge), 294 encryptedRecoveryClaim); 295 assertArrayEquals(ArrayUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted); 296 } 297 298 @Test encryptRecoveryClaim_cannotBeDecryptedWithoutChallenge()299 public void encryptRecoveryClaim_cannotBeDecryptedWithoutChallenge() throws Exception { 300 KeyPair keyPair = SecureBox.genKeyPair(); 301 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); 302 byte[] vaultParams = randomBytes(100); 303 304 byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim( 305 keyPair.getPublic(), 306 vaultParams, 307 /*challenge=*/ randomBytes(32), 308 LOCK_SCREEN_HASH_1, 309 keyClaimant); 310 311 try { 312 SecureBox.decrypt( 313 keyPair.getPrivate(), 314 /*sharedSecret=*/ null, 315 /*header=*/ ArrayUtils.concat( 316 RECOVERY_CLAIM_HEADER, vaultParams, randomBytes(32)), 317 encryptedRecoveryClaim); 318 fail("Should throw if challenge is incorrect."); 319 } catch (AEADBadTagException e) { 320 // expected 321 } 322 } 323 324 @Test encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectSecretKey()325 public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectSecretKey() throws Exception { 326 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); 327 byte[] challenge = randomBytes(32); 328 byte[] vaultParams = randomBytes(100); 329 330 byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim( 331 SecureBox.genKeyPair().getPublic(), 332 vaultParams, 333 challenge, 334 LOCK_SCREEN_HASH_1, 335 keyClaimant); 336 337 try { 338 SecureBox.decrypt( 339 SecureBox.genKeyPair().getPrivate(), 340 /*sharedSecret=*/ null, 341 /*header=*/ ArrayUtils.concat( 342 RECOVERY_CLAIM_HEADER, vaultParams, challenge), 343 encryptedRecoveryClaim); 344 fail("Should throw if secret key is incorrect."); 345 } catch (AEADBadTagException e) { 346 // expected 347 } 348 } 349 350 @Test encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectVaultParams()351 public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectVaultParams() throws Exception { 352 KeyPair keyPair = SecureBox.genKeyPair(); 353 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); 354 byte[] challenge = randomBytes(32); 355 356 byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim( 357 keyPair.getPublic(), 358 /*vaultParams=*/ randomBytes(100), 359 challenge, 360 LOCK_SCREEN_HASH_1, 361 keyClaimant); 362 363 try { 364 SecureBox.decrypt( 365 keyPair.getPrivate(), 366 /*sharedSecret=*/ null, 367 /*header=*/ ArrayUtils.concat( 368 RECOVERY_CLAIM_HEADER, randomBytes(100), challenge), 369 encryptedRecoveryClaim); 370 fail("Should throw if vault params is incorrect."); 371 } catch (AEADBadTagException e) { 372 // expected 373 } 374 } 375 376 @Test encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectHeader()377 public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectHeader() throws Exception { 378 KeyPair keyPair = SecureBox.genKeyPair(); 379 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); 380 byte[] challenge = randomBytes(32); 381 byte[] vaultParams = randomBytes(100); 382 383 byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim( 384 keyPair.getPublic(), 385 vaultParams, 386 challenge, 387 LOCK_SCREEN_HASH_1, 388 keyClaimant); 389 390 try { 391 SecureBox.decrypt( 392 keyPair.getPrivate(), 393 /*sharedSecret=*/ null, 394 /*header=*/ ArrayUtils.concat(randomBytes(10), vaultParams, challenge), 395 encryptedRecoveryClaim); 396 fail("Should throw if header is incorrect."); 397 } catch (AEADBadTagException e) { 398 // expected 399 } 400 } 401 402 @Test packVaultParams_returnsCorrectSize()403 public void packVaultParams_returnsCorrectSize() throws Exception { 404 PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic(); 405 406 byte[] packedForm = KeySyncUtils.packVaultParams( 407 thmPublicKey, 408 /*counterId=*/ 1001L, 409 /*maxAttempts=*/ 10, 410 TEST_VAULT_HANDLE); 411 412 assertEquals(VAULT_PARAMS_LENGTH_BYTES, packedForm.length); 413 } 414 415 @Test packVaultParams_encodesPublicKeyInFirst65Bytes()416 public void packVaultParams_encodesPublicKeyInFirst65Bytes() throws Exception { 417 PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic(); 418 419 byte[] packedForm = KeySyncUtils.packVaultParams( 420 thmPublicKey, 421 /*counterId=*/ 1001L, 422 /*maxAttempts=*/ 10, 423 TEST_VAULT_HANDLE); 424 425 assertArrayEquals( 426 SecureBox.encodePublicKey(thmPublicKey), 427 Arrays.copyOf(packedForm, PUBLIC_KEY_LENGTH_BYTES)); 428 } 429 430 @Test packVaultParams_encodesCounterIdAsSecondParam()431 public void packVaultParams_encodesCounterIdAsSecondParam() throws Exception { 432 long counterId = 103502L; 433 434 byte[] packedForm = KeySyncUtils.packVaultParams( 435 SecureBox.genKeyPair().getPublic(), 436 counterId, 437 /*maxAttempts=*/ 10, 438 TEST_VAULT_HANDLE); 439 440 ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) 441 .order(ByteOrder.LITTLE_ENDIAN); 442 byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES); 443 assertEquals(counterId, byteBuffer.getLong()); 444 } 445 446 @Test packVaultParams_encodesMaxAttemptsAsThirdParam()447 public void packVaultParams_encodesMaxAttemptsAsThirdParam() throws Exception { 448 int maxAttempts = 10; 449 450 byte[] packedForm = KeySyncUtils.packVaultParams( 451 SecureBox.genKeyPair().getPublic(), 452 /*counterId=*/ 1001L, 453 maxAttempts, 454 TEST_VAULT_HANDLE); 455 456 ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) 457 .order(ByteOrder.LITTLE_ENDIAN); 458 byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES); 459 assertEquals(maxAttempts, byteBuffer.getInt()); 460 } 461 462 @Test packVaultParams_encodesVaultHandleAsLastParam()463 public void packVaultParams_encodesVaultHandleAsLastParam() throws Exception { 464 byte[] packedForm = KeySyncUtils.packVaultParams( 465 SecureBox.genKeyPair().getPublic(), 466 /*counterId=*/ 10021L, 467 /*maxAttempts=*/ 10, 468 TEST_VAULT_HANDLE); 469 470 ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) 471 .order(ByteOrder.LITTLE_ENDIAN); 472 byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES + Integer.BYTES); 473 byte[] vaultHandle = new byte[VAULT_HANDLE_LENGTH_BYTES]; 474 byteBuffer.get(vaultHandle); 475 assertArrayEquals(TEST_VAULT_HANDLE, vaultHandle); 476 } 477 478 @Test packVaultParams_encodesVaultHandleWithLength8AsLastParam()479 public void packVaultParams_encodesVaultHandleWithLength8AsLastParam() throws Exception { 480 byte[] vaultHandleWithLenght8 = new byte[] {1, 2, 3, 4, 1, 2, 3, 4}; 481 byte[] packedForm = KeySyncUtils.packVaultParams( 482 SecureBox.genKeyPair().getPublic(), 483 /*counterId=*/ 10021L, 484 /*maxAttempts=*/ 10, 485 vaultHandleWithLenght8); 486 487 ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) 488 .order(ByteOrder.LITTLE_ENDIAN); 489 assertEquals(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES + Integer.BYTES + 8, packedForm.length); 490 byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES + Integer.BYTES); 491 byte[] vaultHandle = new byte[8]; 492 byteBuffer.get(vaultHandle); 493 assertArrayEquals(vaultHandleWithLenght8, vaultHandle); 494 } 495 randomBytes(int n)496 private static byte[] randomBytes(int n) { 497 byte[] bytes = new byte[n]; 498 new Random().nextBytes(bytes); 499 return bytes; 500 } 501 utf8Bytes(String s)502 private static byte[] utf8Bytes(String s) { 503 return s.getBytes(StandardCharsets.UTF_8); 504 } 505 calculateSha256(byte[] bytes)506 private static byte[] calculateSha256(byte[] bytes) throws Exception { 507 MessageDigest messageDigest = MessageDigest.getInstance(SHA_256_ALGORITHM); 508 messageDigest.update(bytes); 509 return messageDigest.digest(); 510 } 511 generateApplicationKey()512 private static SecretKey generateApplicationKey() throws Exception { 513 KeyGenerator keyGenerator = KeyGenerator.getInstance(APPLICATION_KEY_ALGORITHM); 514 keyGenerator.init(/*keySize=*/ 256); 515 return keyGenerator.generateKey(); 516 } 517 } 518