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