1 /* 2 * Copyright (C) 2015 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 android.keystore.cts; 18 19 import static android.keystore.cts.util.TestUtils.KmType; 20 import static android.keystore.cts.util.TestUtils.assumeKmSupport; 21 import static android.keystore.cts.util.TestUtils.isStrongboxKeyMint; 22 import static android.keystore.cts.util.TestUtils.assumeLockScreenSupport; 23 24 import static org.junit.Assert.assertEquals; 25 import static org.junit.Assert.assertFalse; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.fail; 28 29 import android.content.Context; 30 import android.keystore.cts.util.StrictModeDetector; 31 import android.keystore.cts.util.TestUtils; 32 import android.os.Build; 33 import android.security.keystore.KeyGenParameterSpec; 34 import android.security.keystore.KeyInfo; 35 import android.security.keystore.KeyProperties; 36 import android.test.MoreAsserts; 37 import android.text.TextUtils; 38 import android.util.Log; 39 40 import androidx.test.platform.app.InstrumentationRegistry; 41 42 import com.google.common.collect.ObjectArrays; 43 44 import junitparams.JUnitParamsRunner; 45 import junitparams.Parameters; 46 import junitparams.naming.TestCaseName; 47 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 51 import java.nio.charset.StandardCharsets; 52 import java.security.InvalidAlgorithmParameterException; 53 import java.security.NoSuchAlgorithmException; 54 import java.security.NoSuchProviderException; 55 import java.security.Provider; 56 import java.security.Provider.Service; 57 import java.security.SecureRandom; 58 import java.security.Security; 59 import java.security.spec.AlgorithmParameterSpec; 60 import java.security.spec.ECGenParameterSpec; 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.Date; 64 import java.util.HashSet; 65 import java.util.List; 66 import java.util.Locale; 67 import java.util.Map; 68 import java.util.Set; 69 import java.util.TreeMap; 70 71 import javax.crypto.Cipher; 72 import javax.crypto.KeyGenerator; 73 import javax.crypto.Mac; 74 import javax.crypto.SecretKey; 75 import javax.crypto.spec.IvParameterSpec; 76 77 @RunWith(JUnitParamsRunner.class) 78 public class KeyGeneratorTest { 79 private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_PROVIDER_NAME; 80 private static final String TAG = KeyGeneratorTest.class.getSimpleName(); 81 82 static String[] EXPECTED_ALGORITHMS = { 83 "AES", 84 "HmacSHA1", 85 "HmacSHA224", 86 "HmacSHA256", 87 "HmacSHA384", 88 "HmacSHA512", 89 }; 90 91 { 92 if (TestUtils.supports3DES()) { 93 EXPECTED_ALGORITHMS = ObjectArrays.concat(EXPECTED_ALGORITHMS, "DESede"); 94 } 95 } 96 97 private static final Map<String, Integer> DEFAULT_KEY_SIZES = 98 new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 99 static { 100 DEFAULT_KEY_SIZES.put("AES", 128); 101 DEFAULT_KEY_SIZES.put("DESede", 168); 102 DEFAULT_KEY_SIZES.put("HmacSHA1", 160); 103 DEFAULT_KEY_SIZES.put("HmacSHA224", 224); 104 DEFAULT_KEY_SIZES.put("HmacSHA256", 256); 105 DEFAULT_KEY_SIZES.put("HmacSHA384", 384); 106 DEFAULT_KEY_SIZES.put("HmacSHA512", 512); 107 } 108 109 static final int[] AES_SUPPORTED_KEY_SIZES = new int[] {128, 192, 256}; 110 static final int[] AES_STRONGBOX_SUPPORTED_KEY_SIZES = new int[] {128, 256}; 111 static final int[] DES_SUPPORTED_KEY_SIZES = new int[] {168}; 112 kmTypes()113 private static KmType[] kmTypes() { 114 return new KmType[] {KmType.SB, KmType.TEE}; 115 } 116 kmTypes_x_algorithms()117 private static Object[] kmTypes_x_algorithms() { 118 var permutations = new ArrayList<>(List.of(new Object[][] { 119 {KmType.SB, "AES"}, 120 {KmType.SB, "HmacSHA256"}, 121 122 {KmType.TEE, "AES"}, 123 {KmType.TEE, "HmacSHA1"}, 124 {KmType.TEE, "HmacSHA224"}, 125 {KmType.TEE, "HmacSHA256"}, 126 {KmType.TEE, "HmacSHA384"}, 127 {KmType.TEE, "HmacSHA512"} 128 })); 129 if (TestUtils.supports3DES()) { 130 permutations.add(new Object[] {KmType.TEE, "DESede"}); 131 } 132 return permutations.toArray(); 133 } 134 kmTypes_x_hmacAlgorithms()135 private static Object[] kmTypes_x_hmacAlgorithms() { 136 var permutations = new ArrayList<>(List.of(new Object[][] { 137 {KmType.SB, "HmacSHA256"}, 138 139 {KmType.TEE, "HmacSHA1"}, 140 {KmType.TEE, "HmacSHA224"}, 141 {KmType.TEE, "HmacSHA256"}, 142 {KmType.TEE, "HmacSHA384"}, 143 {KmType.TEE, "HmacSHA512"} 144 })); 145 return permutations.toArray(); 146 } 147 kmTypes_x_signingAlgorithms()148 private static Object[] kmTypes_x_signingAlgorithms() { 149 var permutations = new ArrayList<>(Arrays.asList(kmTypes_x_hmacAlgorithms())); 150 if (TestUtils.supports3DES()) { 151 permutations.add(new Object[] {KmType.TEE, "DESede"}); 152 } 153 return permutations.toArray(); 154 } 155 getContext()156 private Context getContext() { 157 return InstrumentationRegistry.getInstrumentation().getTargetContext(); 158 } 159 160 @Test testAlgorithmList()161 public void testAlgorithmList() { 162 // Assert that Android Keystore Provider exposes exactly the expected KeyGenerator 163 // algorithms. We don't care whether the algorithms are exposed via aliases, as long as 164 // canonical names of algorithms are accepted. If the Provider exposes extraneous 165 // algorithms, it'll be caught because it'll have to expose at least one Service for such an 166 // algorithm, and this Service's algorithm will not be in the expected set. 167 168 Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME); 169 Set<Service> services = provider.getServices(); 170 Set<String> actualAlgsLowerCase = new HashSet<String>(); 171 Set<String> expectedAlgsLowerCase = new HashSet<String>( 172 Arrays.asList(TestUtils.toLowerCase(EXPECTED_ALGORITHMS))); 173 for (Service service : services) { 174 if ("KeyGenerator".equalsIgnoreCase(service.getType())) { 175 String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US); 176 actualAlgsLowerCase.add(algLowerCase); 177 } 178 } 179 180 TestUtils.assertContentsInAnyOrder(actualAlgsLowerCase, 181 expectedAlgsLowerCase.toArray(new String[0])); 182 } 183 184 @Test testGenerateWithoutInitThrowsIllegalStateException()185 public void testGenerateWithoutInitThrowsIllegalStateException() throws Exception { 186 for (String algorithm : EXPECTED_ALGORITHMS) { 187 try { 188 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 189 try { 190 keyGenerator.generateKey(); 191 fail(); 192 } catch (IllegalStateException expected) {} 193 } catch (Throwable e) { 194 throw new RuntimeException("Failed for " + algorithm, e); 195 } 196 } 197 } 198 199 @Test testInitWithKeySizeThrowsUnsupportedOperationException()200 public void testInitWithKeySizeThrowsUnsupportedOperationException() throws Exception { 201 for (String algorithm : EXPECTED_ALGORITHMS) { 202 try { 203 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 204 int keySizeBits = DEFAULT_KEY_SIZES.get(algorithm); 205 try { 206 keyGenerator.init(keySizeBits); 207 fail(); 208 } catch (UnsupportedOperationException expected) {} 209 } catch (Throwable e) { 210 throw new RuntimeException("Failed for " + algorithm, e); 211 } 212 } 213 } 214 215 @Test testInitWithKeySizeAndSecureRandomThrowsUnsupportedOperationException()216 public void testInitWithKeySizeAndSecureRandomThrowsUnsupportedOperationException() 217 throws Exception { 218 SecureRandom rng = new SecureRandom(); 219 for (String algorithm : EXPECTED_ALGORITHMS) { 220 try { 221 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 222 int keySizeBits = DEFAULT_KEY_SIZES.get(algorithm); 223 try { 224 keyGenerator.init(keySizeBits, rng); 225 fail(); 226 } catch (UnsupportedOperationException expected) {} 227 } catch (Throwable e) { 228 throw new RuntimeException("Failed for " + algorithm, e); 229 } 230 } 231 } 232 233 @Test testInitWithNullAlgParamsThrowsInvalidAlgorithmParameterException()234 public void testInitWithNullAlgParamsThrowsInvalidAlgorithmParameterException() 235 throws Exception { 236 for (String algorithm : EXPECTED_ALGORITHMS) { 237 try { 238 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 239 try { 240 keyGenerator.init((AlgorithmParameterSpec) null); 241 fail(); 242 } catch (InvalidAlgorithmParameterException expected) {} 243 } catch (Throwable e) { 244 throw new RuntimeException("Failed for " + algorithm, e); 245 } 246 } 247 } 248 249 @Test testInitWithNullAlgParamsAndSecureRandomThrowsInvalidAlgorithmParameterException()250 public void testInitWithNullAlgParamsAndSecureRandomThrowsInvalidAlgorithmParameterException() 251 throws Exception { 252 SecureRandom rng = new SecureRandom(); 253 for (String algorithm : EXPECTED_ALGORITHMS) { 254 try { 255 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 256 try { 257 keyGenerator.init((AlgorithmParameterSpec) null, rng); 258 fail(); 259 } catch (InvalidAlgorithmParameterException expected) {} 260 } catch (Throwable e) { 261 throw new RuntimeException("Failed for " + algorithm, e); 262 } 263 } 264 } 265 266 @Test 267 @Parameters(method = "kmTypes_x_algorithms") 268 @TestCaseName(value = "{method}_{0}_{1}") testInitWithAlgParamsAndNullSecureRandom(KmType kmType, String algorithm)269 public void testInitWithAlgParamsAndNullSecureRandom(KmType kmType, String algorithm) 270 throws Exception { 271 assumeKmSupport(kmType); 272 try { 273 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 274 keyGenerator.init(getWorkingSpec() 275 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 276 .build(), 277 (SecureRandom) null); 278 // Check that generateKey doesn't fail either, just in case null SecureRandom 279 // causes trouble there. 280 keyGenerator.generateKey(); 281 } catch (Throwable e) { 282 throw new RuntimeException("Failed for " + algorithm, e); 283 } 284 } 285 286 @Test testInitWithUnsupportedAlgParamsTypeThrowsInvalidAlgorithmParameterException()287 public void testInitWithUnsupportedAlgParamsTypeThrowsInvalidAlgorithmParameterException() 288 throws Exception { 289 for (String algorithm : EXPECTED_ALGORITHMS) { 290 try { 291 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 292 try { 293 keyGenerator.init(new ECGenParameterSpec("secp256r1")); 294 fail(); 295 } catch (InvalidAlgorithmParameterException expected) {} 296 } catch (Throwable e) { 297 throw new RuntimeException("Failed for " + algorithm, e); 298 } 299 } 300 } 301 302 @Test 303 @Parameters(method = "kmTypes_x_algorithms") 304 @TestCaseName(value = "{method}_{0}_{1}") testDefaultKeySize(KmType kmType, String algorithm)305 public void testDefaultKeySize(KmType kmType, String algorithm) throws Exception { 306 assumeKmSupport(kmType); 307 StrictModeDetector strict = new StrictModeDetector(getContext()); 308 try { 309 int expectedSizeBits = DEFAULT_KEY_SIZES.get(algorithm); 310 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 311 keyGenerator.init(getWorkingSpec().build()); 312 SecretKey key = keyGenerator.generateKey(); 313 assertEquals(expectedSizeBits, TestUtils.getKeyInfo(key).getKeySize()); 314 } catch (Throwable e) { 315 throw new RuntimeException("Failed for " + algorithm, e); 316 } 317 strict.check(algorithm + " key generation on " + kmType); 318 } 319 320 @Test 321 @Parameters(method = "kmTypes") 322 @TestCaseName(value = "{method}_{0}") testAesKeySupportedSizes(KmType kmType)323 public void testAesKeySupportedSizes(KmType kmType) throws Exception { 324 assumeKmSupport(kmType); 325 boolean useStrongbox = isStrongboxKeyMint(kmType); 326 KeyGenerator keyGenerator = getKeyGenerator("AES"); 327 KeyGenParameterSpec.Builder goodSpec = getWorkingSpec(); 328 CountingSecureRandom rng = new CountingSecureRandom(); 329 for (int i = -16; i <= 512; i++) { 330 try { 331 rng.resetCounters(); 332 KeyGenParameterSpec spec; 333 if (i >= 0) { 334 spec = TestUtils.buildUpon( 335 goodSpec.setKeySize(i)).setIsStrongBoxBacked(isStrongboxKeyMint(kmType)).build(); 336 } else { 337 try { 338 spec = TestUtils.buildUpon( 339 goodSpec.setKeySize(i)).setIsStrongBoxBacked(isStrongboxKeyMint(kmType)).build(); 340 fail(); 341 } catch (IllegalArgumentException expected) { 342 continue; 343 } 344 } 345 rng.resetCounters(); 346 if (TestUtils.contains(useStrongbox ? 347 AES_STRONGBOX_SUPPORTED_KEY_SIZES : AES_SUPPORTED_KEY_SIZES, i)) { 348 keyGenerator.init(spec, rng); 349 SecretKey key = keyGenerator.generateKey(); 350 assertEquals(i, TestUtils.getKeyInfo(key).getKeySize()); 351 assertEquals((i + 7) / 8, rng.getOutputSizeBytes()); 352 } else { 353 try { 354 if (useStrongbox && (i == 192)) 355 throw new InvalidAlgorithmParameterException("Strongbox does not" 356 + " support key size 192."); 357 keyGenerator.init(spec, rng); 358 fail(); 359 } catch (InvalidAlgorithmParameterException expected) {} 360 assertEquals(0, rng.getOutputSizeBytes()); 361 } 362 } catch (Throwable e) { 363 throw new RuntimeException("Failed for key size " + i, e); 364 } 365 } 366 } 367 368 @Test testDESKeySupportedSizes()369 public void testDESKeySupportedSizes() throws Exception { 370 if (!TestUtils.supports3DES()) { 371 return; 372 } 373 KeyGenerator keyGenerator = getKeyGenerator("DESede"); 374 KeyGenParameterSpec.Builder goodSpec = getWorkingSpec(); 375 CountingSecureRandom rng = new CountingSecureRandom(); 376 for (int i = -16; i <= 168; i++) { 377 try { 378 rng.resetCounters(); 379 KeyGenParameterSpec spec; 380 if (i >= 0) { 381 spec = TestUtils.buildUpon(goodSpec.setKeySize(i)).build(); 382 } else { 383 try { 384 spec = TestUtils.buildUpon(goodSpec.setKeySize(i)).build(); 385 fail(); 386 } catch (IllegalArgumentException expected) { 387 continue; 388 } 389 } 390 rng.resetCounters(); 391 if (TestUtils.contains(DES_SUPPORTED_KEY_SIZES, i)) { 392 keyGenerator.init(spec, rng); 393 SecretKey key = keyGenerator.generateKey(); 394 assertEquals(i, TestUtils.getKeyInfo(key).getKeySize()); 395 } else { 396 try { 397 keyGenerator.init(spec, rng); 398 fail(); 399 } catch (InvalidAlgorithmParameterException expected) {} 400 assertEquals(0, rng.getOutputSizeBytes()); 401 } 402 } catch (Throwable e) { 403 throw new RuntimeException("Failed for key size " + i, e); 404 } 405 } 406 } 407 408 @Test 409 @Parameters(method = "kmTypes_x_hmacAlgorithms") 410 @TestCaseName(value = "{method}_{0}_{1}") testHmacKeySupportedSizes(KmType kmType, String algorithm)411 public void testHmacKeySupportedSizes(KmType kmType, String algorithm) throws Exception { 412 assumeKmSupport(kmType); 413 CountingSecureRandom rng = new CountingSecureRandom(); 414 415 for (int i = -16; i <= 1024; i++) { 416 try { 417 rng.resetCounters(); 418 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 419 KeyGenParameterSpec spec; 420 if (i >= 0) { 421 spec = getWorkingSpec() 422 .setKeySize(i) 423 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 424 .build(); 425 } else { 426 try { 427 spec = getWorkingSpec() 428 .setKeySize(i) 429 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 430 .build(); 431 fail(); 432 } catch (IllegalArgumentException expected) { 433 continue; 434 } 435 } 436 if (i > 512) { 437 try { 438 keyGenerator.init(spec, rng); 439 fail(); 440 } catch (InvalidAlgorithmParameterException expected) { 441 assertEquals(0, rng.getOutputSizeBytes()); 442 } 443 } else if ((i >= 64) && ((i % 8) == 0)) { 444 keyGenerator.init(spec, rng); 445 SecretKey key = keyGenerator.generateKey(); 446 assertEquals(i, TestUtils.getKeyInfo(key).getKeySize()); 447 assertEquals((i + 7) / 8, rng.getOutputSizeBytes()); 448 } else if (i >= 64) { 449 try { 450 keyGenerator.init(spec, rng); 451 fail(); 452 } catch (InvalidAlgorithmParameterException expected) {} 453 assertEquals(0, rng.getOutputSizeBytes()); 454 } 455 } catch (Throwable e) { 456 throw new RuntimeException( 457 "Failed for " + algorithm + " with key size " + i, e); 458 } 459 } 460 } 461 462 @Test 463 @Parameters(method = "kmTypes_x_hmacAlgorithms") 464 @TestCaseName(value = "{method}_{0}_{1}") testHmacKeyOnlyOneDigestCanBeAuthorized(KmType kmType, String algorithm)465 public void testHmacKeyOnlyOneDigestCanBeAuthorized(KmType kmType, String algorithm) 466 throws Exception { 467 assumeKmSupport(kmType); 468 469 try { 470 String digest = TestUtils.getHmacAlgorithmDigest(algorithm); 471 assertNotNull(digest); 472 473 KeyGenParameterSpec.Builder goodSpec = 474 new KeyGenParameterSpec.Builder("test1", KeyProperties.PURPOSE_SIGN); 475 476 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 477 478 // Digests authorization not specified in algorithm parameters 479 assertFalse(goodSpec.setIsStrongBoxBacked(isStrongboxKeyMint(kmType)).build().isDigestsSpecified()); 480 keyGenerator.init(goodSpec.setIsStrongBoxBacked(isStrongboxKeyMint(kmType)).build()); 481 SecretKey key = keyGenerator.generateKey(); 482 TestUtils.assertContentsInAnyOrder( 483 Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest); 484 485 // The same digest is specified in algorithm parameters 486 keyGenerator.init(TestUtils.buildUpon(goodSpec) 487 .setDigests(digest) 488 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 489 .build()); 490 key = keyGenerator.generateKey(); 491 TestUtils.assertContentsInAnyOrder( 492 Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest); 493 494 // No digests specified in algorithm parameters 495 try { 496 keyGenerator.init(TestUtils.buildUpon(goodSpec) 497 .setDigests() 498 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 499 .build()); 500 fail(); 501 } catch (InvalidAlgorithmParameterException expected) {} 502 503 // A different digest specified in algorithm parameters 504 String anotherDigest = "SHA-256".equalsIgnoreCase(digest) ? "SHA-384" : "SHA-256"; 505 try { 506 keyGenerator.init(TestUtils.buildUpon(goodSpec) 507 .setDigests(anotherDigest) 508 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 509 .build()); 510 fail(); 511 } catch (InvalidAlgorithmParameterException expected) {} 512 try { 513 keyGenerator.init(TestUtils.buildUpon(goodSpec) 514 .setDigests(digest, anotherDigest) 515 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 516 .build()); 517 fail(); 518 } catch (InvalidAlgorithmParameterException expected) {} 519 } catch (Throwable e) { 520 throw new RuntimeException("Failed for " + algorithm, e); 521 } 522 } 523 524 @Test 525 @Parameters(method = "kmTypes_x_algorithms") 526 @TestCaseName(value = "{method}_{0}_{1}") testInitWithUnknownBlockModeFails(KmType kmType, String algorithm)527 public void testInitWithUnknownBlockModeFails(KmType kmType, String algorithm) { 528 assumeKmSupport(kmType); 529 try { 530 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 531 try { 532 keyGenerator.init( 533 getWorkingSpec() 534 .setBlockModes("weird") 535 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 536 .build()); 537 fail(); 538 } catch (InvalidAlgorithmParameterException expected) {} 539 } catch (Throwable e) { 540 throw new RuntimeException("Failed for " + algorithm, e); 541 } 542 } 543 544 @Test 545 @Parameters(method = "kmTypes_x_algorithms") 546 @TestCaseName(value = "{method}_{0}_{1}") testInitWithUnknownEncryptionPaddingFails(KmType kmType, String algorithm)547 public void testInitWithUnknownEncryptionPaddingFails(KmType kmType, String algorithm) { 548 assumeKmSupport(kmType); 549 try { 550 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 551 try { 552 keyGenerator.init( 553 getWorkingSpec() 554 .setEncryptionPaddings("weird") 555 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 556 .build()); 557 fail(); 558 } catch (InvalidAlgorithmParameterException expected) {} 559 } catch (Throwable e) { 560 throw new RuntimeException("Failed for " + algorithm, e); 561 } 562 } 563 564 @Test 565 @Parameters(method = "kmTypes_x_algorithms") 566 @TestCaseName(value = "{method}_{0}_{1}") testInitWithSignaturePaddingFails(KmType kmType, String algorithm)567 public void testInitWithSignaturePaddingFails(KmType kmType, String algorithm) { 568 assumeKmSupport(kmType); 569 try { 570 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 571 try { 572 keyGenerator.init(getWorkingSpec() 573 .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) 574 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 575 .build()); 576 fail(); 577 } catch (InvalidAlgorithmParameterException expected) {} 578 } catch (Throwable e) { 579 throw new RuntimeException("Failed for " + algorithm, e); 580 } 581 } 582 583 @Test 584 @Parameters(method = "kmTypes_x_algorithms") 585 @TestCaseName(value = "{method}_{0}_{1}") testInitWithUnknownDigestFails(KmType kmType, String algorithm)586 public void testInitWithUnknownDigestFails(KmType kmType, String algorithm) { 587 assumeKmSupport(kmType); 588 try { 589 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 590 try { 591 String[] digests; 592 if (TestUtils.isHmacAlgorithm(algorithm)) { 593 // The digest from HMAC key algorithm must be specified in the list of 594 // authorized digests (if the list if provided). 595 digests = new String[] {algorithm, "weird"}; 596 } else { 597 digests = new String[] {"weird"}; 598 } 599 keyGenerator.init( 600 getWorkingSpec() 601 .setDigests(digests) 602 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 603 .build()); 604 fail(); 605 } catch (InvalidAlgorithmParameterException expected) {} 606 } catch (Throwable e) { 607 throw new RuntimeException("Failed for " + algorithm, e); 608 } 609 } 610 611 @Test 612 @Parameters(method = "kmTypes_x_hmacAlgorithms") 613 @TestCaseName(value = "{method}_{0}_{1}") testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFails( KmType kmType, String algorithm)614 public void testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFails( 615 KmType kmType, String algorithm) { 616 assumeKmSupport(kmType); 617 try { 618 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 619 620 // Authorized for digest(s) none of which is the one implied by key algorithm. 621 try { 622 String digest = TestUtils.getHmacAlgorithmDigest(algorithm); 623 String anotherDigest = KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest) 624 ? KeyProperties.DIGEST_SHA512 : KeyProperties.DIGEST_SHA256; 625 keyGenerator.init( 626 getWorkingSpec() 627 .setDigests(anotherDigest) 628 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 629 .build()); 630 fail(); 631 } catch (InvalidAlgorithmParameterException expected) {} 632 633 // Authorized for empty set of digests 634 try { 635 keyGenerator.init( 636 getWorkingSpec() 637 .setDigests() 638 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 639 .build()); 640 fail(); 641 } catch (InvalidAlgorithmParameterException expected) {} 642 } catch (Throwable e) { 643 throw new RuntimeException("Failed for " + algorithm, e); 644 } 645 } 646 647 @Test 648 @Parameters(method = "kmTypes_x_algorithms") 649 @TestCaseName(value = "{method}_{0}_{1}") testInitRandomizedEncryptionRequiredButViolatedFails( KmType kmType, String algorithm)650 public void testInitRandomizedEncryptionRequiredButViolatedFails( 651 KmType kmType, String algorithm) throws Exception { 652 assumeKmSupport(kmType); 653 try { 654 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 655 try { 656 keyGenerator.init(getWorkingSpec(KeyProperties.PURPOSE_ENCRYPT) 657 .setBlockModes(KeyProperties.BLOCK_MODE_ECB) 658 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 659 .build()); 660 fail(); 661 } catch (InvalidAlgorithmParameterException expected) {} 662 } catch (Throwable e) { 663 throw new RuntimeException("Failed for " + algorithm, e); 664 } 665 } 666 667 @Test 668 @Parameters(method = "kmTypes_x_algorithms") 669 @TestCaseName(value = "{method}_{0}_{1}") testGenerateHonorsRequestedAuthorizations(KmType kmType, String algorithm)670 public void testGenerateHonorsRequestedAuthorizations(KmType kmType, String algorithm) 671 throws Exception { 672 assumeKmSupport(kmType); 673 Date keyValidityStart = new Date(System.currentTimeMillis() - TestUtils.DAY_IN_MILLIS); 674 Date keyValidityForOriginationEnd = 675 new Date(System.currentTimeMillis() + TestUtils.DAY_IN_MILLIS); 676 Date keyValidityForConsumptionEnd = 677 new Date(System.currentTimeMillis() + 3 * TestUtils.DAY_IN_MILLIS); 678 try { 679 String[] blockModes = 680 new String[] {KeyProperties.BLOCK_MODE_GCM, KeyProperties.BLOCK_MODE_CBC}; 681 String[] encryptionPaddings = 682 new String[] {KeyProperties.ENCRYPTION_PADDING_PKCS7, 683 KeyProperties.ENCRYPTION_PADDING_NONE}; 684 String[] digests; 685 @KeyProperties.PurposeEnum int purposes; 686 if (TestUtils.isHmacAlgorithm(algorithm)) { 687 // HMAC key can only be authorized for one digest, the one implied by the key's 688 // JCA algorithm name. 689 digests = new String[] {TestUtils.getHmacAlgorithmDigest(algorithm)}; 690 purposes = KeyProperties.PURPOSE_SIGN; 691 } else { 692 digests = new String[] {KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA1}; 693 purposes = KeyProperties.PURPOSE_DECRYPT; 694 } 695 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 696 keyGenerator.init(getWorkingSpec(purposes) 697 .setBlockModes(blockModes) 698 .setEncryptionPaddings(encryptionPaddings) 699 .setDigests(digests) 700 .setKeyValidityStart(keyValidityStart) 701 .setKeyValidityForOriginationEnd(keyValidityForOriginationEnd) 702 .setKeyValidityForConsumptionEnd(keyValidityForConsumptionEnd) 703 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 704 .build()); 705 SecretKey key = keyGenerator.generateKey(); 706 assertEquals(algorithm, key.getAlgorithm()); 707 708 KeyInfo keyInfo = TestUtils.getKeyInfo(key); 709 assertEquals(purposes, keyInfo.getPurposes()); 710 TestUtils.assertContentsInAnyOrder( 711 Arrays.asList(blockModes), keyInfo.getBlockModes()); 712 TestUtils.assertContentsInAnyOrder( 713 Arrays.asList(encryptionPaddings), keyInfo.getEncryptionPaddings()); 714 TestUtils.assertContentsInAnyOrder(Arrays.asList(digests), keyInfo.getDigests()); 715 MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getSignaturePaddings())); 716 assertEquals(keyValidityStart, keyInfo.getKeyValidityStart()); 717 assertEquals(keyValidityForOriginationEnd, 718 keyInfo.getKeyValidityForOriginationEnd()); 719 assertEquals(keyValidityForConsumptionEnd, 720 keyInfo.getKeyValidityForConsumptionEnd()); 721 assertFalse(keyInfo.isUserAuthenticationRequired()); 722 assertFalse(keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware()); 723 } catch (Throwable e) { 724 throw new RuntimeException("Failed for " + algorithm, e); 725 } 726 } 727 728 @Test 729 @Parameters(method = "kmTypes_x_algorithms") 730 @TestCaseName(value = "{method}_{0}_{1}") testLimitedUseKey(KmType kmType, String algorithm)731 public void testLimitedUseKey(KmType kmType, String algorithm) throws Exception { 732 assumeKmSupport(kmType); 733 int maxUsageCount = 1; 734 try { 735 int expectedSizeBits = DEFAULT_KEY_SIZES.get(algorithm); 736 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 737 keyGenerator.init(getWorkingSpec().setMaxUsageCount(maxUsageCount).build()); 738 SecretKey key = keyGenerator.generateKey(); 739 assertEquals(expectedSizeBits, TestUtils.getKeyInfo(key).getKeySize()); 740 assertEquals(maxUsageCount, TestUtils.getKeyInfo(key).getRemainingUsageCount()); 741 } catch (Throwable e) { 742 throw new RuntimeException("Failed for " + algorithm, e); 743 } 744 } 745 746 @Test 747 @Parameters(method = "kmTypes_x_signingAlgorithms") 748 @TestCaseName(value = "{method}_{0}_{1}") testGenerateAuthBoundKey_Lskf(KmType kmType, String algorithm)749 public void testGenerateAuthBoundKey_Lskf(KmType kmType, String algorithm) 750 throws Exception { 751 assumeLockScreenSupport(); 752 assumeKmSupport(kmType); 753 try (var dl = new DeviceLockSession(InstrumentationRegistry.getInstrumentation())) { 754 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 755 keyGenerator.init(getWorkingSpec( 756 KeyProperties.PURPOSE_SIGN) 757 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 758 .setUserAuthenticationRequired(true) 759 .setUserAuthenticationParameters(0 /* seconds */, 760 KeyProperties.AUTH_DEVICE_CREDENTIAL) 761 .build()); 762 keyGenerator.generateKey(); 763 } 764 } 765 766 @Test 767 @Parameters(method = "kmTypes_x_signingAlgorithms") 768 @TestCaseName(value = "{method}_{0}_{1}") testGenerateAuthBoundKey_LskfOrStrongBiometric(KmType kmType, String algorithm)769 public void testGenerateAuthBoundKey_LskfOrStrongBiometric(KmType kmType, String algorithm) 770 throws Exception { 771 assumeLockScreenSupport(); 772 assumeKmSupport(kmType); 773 try (var dl = new DeviceLockSession(InstrumentationRegistry.getInstrumentation())) { 774 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 775 keyGenerator.init(getWorkingSpec( 776 KeyProperties.PURPOSE_SIGN) 777 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 778 .setUserAuthenticationRequired(true) 779 .setUserAuthenticationParameters(0 /* seconds */, 780 KeyProperties.AUTH_BIOMETRIC_STRONG 781 | KeyProperties.AUTH_DEVICE_CREDENTIAL) 782 .build()); 783 keyGenerator.generateKey(); 784 } 785 } 786 787 @Test 788 @Parameters(method = "kmTypes") 789 @TestCaseName(value = "{method}_{0}") testUniquenessOfAesKeys(KmType kmType)790 public void testUniquenessOfAesKeys(KmType kmType) throws Exception { 791 assumeKmSupport(kmType); 792 assertUniqueAesEncryptionForNKeys("AES/ECB/NoPadding", isStrongboxKeyMint(kmType)); 793 assertUniqueAesEncryptionForNKeys("AES/CBC/NoPadding", isStrongboxKeyMint(kmType)); 794 } 795 assertUniqueAesEncryptionForNKeys(String algoTransform, boolean isStrongboxKeyMint)796 private void assertUniqueAesEncryptionForNKeys(String algoTransform, boolean isStrongboxKeyMint) 797 throws Exception { 798 byte[] randomMsg = new byte[16]; 799 SecureRandom.getInstance("SHA1PRNG").nextBytes(randomMsg); 800 byte[][] msgArr = new byte[][]{ 801 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 802 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 803 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 804 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 805 "16 char message.".getBytes(StandardCharsets.UTF_8), 806 randomMsg 807 }; 808 for (byte[] msg : msgArr) { 809 int numberOfKeysToTest = 10; 810 Set results = new HashSet(); 811 boolean isCbcMode = algoTransform.contains("CBC"); 812 byte[] iv = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 813 for (int i = 0; i < numberOfKeysToTest; i++) { 814 KeyGenerator keyGenerator = getKeyGenerator("AES"); 815 keyGenerator.init(getWorkingSpec(KeyProperties.PURPOSE_ENCRYPT) 816 .setBlockModes(isCbcMode 817 ? KeyProperties.BLOCK_MODE_CBC : KeyProperties.BLOCK_MODE_ECB) 818 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 819 .setRandomizedEncryptionRequired(false) 820 .setIsStrongBoxBacked(isStrongboxKeyMint) 821 .build()); 822 SecretKey key = keyGenerator.generateKey(); 823 Cipher cipher = Cipher.getInstance(algoTransform, 824 TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME); 825 if (isCbcMode) { 826 cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); 827 } else { 828 cipher.init(Cipher.ENCRYPT_MODE, key); 829 } 830 byte[] cipherText = msg == null ? cipher.doFinal() : cipher.doFinal(msg); 831 // Add generated cipher text to HashSet so that only unique cipher text will be 832 // counted. 833 results.add(new String(cipherText)); 834 } 835 // Verify unique cipher text is generated for all different keys 836 assertEquals( 837 TextUtils.formatSimple("%d different cipher text should have been" 838 + " generated for %d different keys. Failed for message |%s|.", 839 numberOfKeysToTest, numberOfKeysToTest, HexEncoding.encode(msg)), 840 numberOfKeysToTest, results.size()); 841 } 842 } 843 844 @Test 845 @Parameters(method = "kmTypes") 846 @TestCaseName(value = "{method}_{0}") testUniquenessOfHmacKeys(KmType kmType)847 public void testUniquenessOfHmacKeys(KmType kmType) throws Exception { 848 assumeKmSupport(kmType); 849 int numberOfKeysToTest = 10; 850 byte[] randomMsg = new byte[16]; 851 SecureRandom.getInstance("SHA1PRNG").nextBytes(randomMsg); 852 byte[][] msgArr = new byte[][]{ 853 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 854 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 855 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 856 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 857 "dummymessage1234".getBytes(StandardCharsets.UTF_8), 858 randomMsg, 859 {}, 860 null 861 }; 862 for (byte[] msg : msgArr) { 863 Set results = new HashSet(); 864 for (int i = 0; i < numberOfKeysToTest; i++) { 865 KeyGenerator keyGenerator = getKeyGenerator("HmacSHA256"); 866 keyGenerator.init(getWorkingSpec(KeyProperties.PURPOSE_SIGN) 867 .setIsStrongBoxBacked(isStrongboxKeyMint(kmType)) 868 .build()); 869 SecretKey key = keyGenerator.generateKey(); 870 Mac mac = Mac.getInstance("HMACSHA256", 871 TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME); 872 mac.init(key); 873 byte[] macSign = mac.doFinal(msg); 874 // Add generated mac signature to HashSet so that unique signatures will be counted 875 results.add(new String(macSign)); 876 } 877 878 if ((msg == null || msg.length == 0) 879 && TestUtils.getVendorApiLevel() <= Build.VERSION_CODES.P) { 880 // Skip empty and null inputs on older devices as HAL is unable to handle them. 881 Log.d(TAG, "Skipping test for unsupported input on pre-Q launch device."); 882 continue; 883 } 884 885 // Verify unique MAC is generated for all different keys 886 assertEquals(TextUtils.formatSimple("%d different MACs should have been generated for " 887 + "%d different keys over message |%s|", 888 numberOfKeysToTest, numberOfKeysToTest, HexEncoding.encode(msg)), 889 numberOfKeysToTest, results.size()); 890 } 891 } 892 getWorkingSpec()893 private static KeyGenParameterSpec.Builder getWorkingSpec() { 894 return getWorkingSpec(0); 895 } 896 getWorkingSpec( @eyProperties.PurposeEnum int purposes)897 private static KeyGenParameterSpec.Builder getWorkingSpec( 898 @KeyProperties.PurposeEnum int purposes) { 899 return new KeyGenParameterSpec.Builder("test1", purposes); 900 } 901 getKeyGenerator(String algorithm)902 private static KeyGenerator getKeyGenerator(String algorithm) throws NoSuchAlgorithmException, 903 NoSuchProviderException { 904 return KeyGenerator.getInstance(algorithm, EXPECTED_PROVIDER_NAME); 905 } 906 } 907