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 android.security.keystore.KeyGenParameterSpec; 20 import android.security.keystore.KeyInfo; 21 import android.security.keystore.KeyProperties; 22 import android.test.MoreAsserts; 23 24 import junit.framework.TestCase; 25 26 import java.security.InvalidAlgorithmParameterException; 27 import java.security.NoSuchAlgorithmException; 28 import java.security.NoSuchProviderException; 29 import java.security.Provider; 30 import java.security.Security; 31 import java.security.spec.AlgorithmParameterSpec; 32 import java.security.spec.ECGenParameterSpec; 33 import java.security.Provider.Service; 34 import java.security.SecureRandom; 35 import java.util.Arrays; 36 import java.util.Date; 37 import java.util.HashSet; 38 import java.util.Locale; 39 import java.util.Map; 40 import java.util.Set; 41 import java.util.TreeMap; 42 43 import javax.crypto.KeyGenerator; 44 import javax.crypto.SecretKey; 45 46 public class KeyGeneratorTest extends TestCase { 47 private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_PROVIDER_NAME; 48 49 static final String[] EXPECTED_ALGORITHMS = { 50 "AES", 51 "HmacSHA1", 52 "HmacSHA224", 53 "HmacSHA256", 54 "HmacSHA384", 55 "HmacSHA512", 56 }; 57 58 private static final Map<String, Integer> DEFAULT_KEY_SIZES = 59 new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 60 static { 61 DEFAULT_KEY_SIZES.put("AES", 128); 62 DEFAULT_KEY_SIZES.put("HmacSHA1", 160); 63 DEFAULT_KEY_SIZES.put("HmacSHA224", 224); 64 DEFAULT_KEY_SIZES.put("HmacSHA256", 256); 65 DEFAULT_KEY_SIZES.put("HmacSHA384", 384); 66 DEFAULT_KEY_SIZES.put("HmacSHA512", 512); 67 } 68 69 static final int[] AES_SUPPORTED_KEY_SIZES = new int[] {128, 192, 256}; 70 testAlgorithmList()71 public void testAlgorithmList() { 72 // Assert that Android Keystore Provider exposes exactly the expected KeyGenerator 73 // algorithms. We don't care whether the algorithms are exposed via aliases, as long as 74 // canonical names of algorithms are accepted. If the Provider exposes extraneous 75 // algorithms, it'll be caught because it'll have to expose at least one Service for such an 76 // algorithm, and this Service's algorithm will not be in the expected set. 77 78 Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME); 79 Set<Service> services = provider.getServices(); 80 Set<String> actualAlgsLowerCase = new HashSet<String>(); 81 Set<String> expectedAlgsLowerCase = new HashSet<String>( 82 Arrays.asList(TestUtils.toLowerCase(EXPECTED_ALGORITHMS))); 83 for (Service service : services) { 84 if ("KeyGenerator".equalsIgnoreCase(service.getType())) { 85 String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US); 86 actualAlgsLowerCase.add(algLowerCase); 87 } 88 } 89 90 TestUtils.assertContentsInAnyOrder(actualAlgsLowerCase, 91 expectedAlgsLowerCase.toArray(new String[0])); 92 } 93 testGenerateWithoutInitThrowsIllegalStateException()94 public void testGenerateWithoutInitThrowsIllegalStateException() throws Exception { 95 for (String algorithm : EXPECTED_ALGORITHMS) { 96 try { 97 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 98 try { 99 keyGenerator.generateKey(); 100 fail(); 101 } catch (IllegalStateException expected) {} 102 } catch (Throwable e) { 103 throw new RuntimeException("Failed for " + algorithm, e); 104 } 105 } 106 } 107 testInitWithKeySizeThrowsUnsupportedOperationException()108 public void testInitWithKeySizeThrowsUnsupportedOperationException() throws Exception { 109 for (String algorithm : EXPECTED_ALGORITHMS) { 110 try { 111 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 112 int keySizeBits = DEFAULT_KEY_SIZES.get(algorithm); 113 try { 114 keyGenerator.init(keySizeBits); 115 fail(); 116 } catch (UnsupportedOperationException expected) {} 117 } catch (Throwable e) { 118 throw new RuntimeException("Failed for " + algorithm, e); 119 } 120 } 121 } 122 testInitWithKeySizeAndSecureRandomThrowsUnsupportedOperationException()123 public void testInitWithKeySizeAndSecureRandomThrowsUnsupportedOperationException() 124 throws Exception { 125 SecureRandom rng = new SecureRandom(); 126 for (String algorithm : EXPECTED_ALGORITHMS) { 127 try { 128 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 129 int keySizeBits = DEFAULT_KEY_SIZES.get(algorithm); 130 try { 131 keyGenerator.init(keySizeBits, rng); 132 fail(); 133 } catch (UnsupportedOperationException expected) {} 134 } catch (Throwable e) { 135 throw new RuntimeException("Failed for " + algorithm, e); 136 } 137 } 138 } 139 testInitWithNullAlgParamsThrowsInvalidAlgorithmParameterException()140 public void testInitWithNullAlgParamsThrowsInvalidAlgorithmParameterException() 141 throws Exception { 142 for (String algorithm : EXPECTED_ALGORITHMS) { 143 try { 144 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 145 try { 146 keyGenerator.init((AlgorithmParameterSpec) null); 147 fail(); 148 } catch (InvalidAlgorithmParameterException expected) {} 149 } catch (Throwable e) { 150 throw new RuntimeException("Failed for " + algorithm, e); 151 } 152 } 153 } 154 testInitWithNullAlgParamsAndSecureRandomThrowsInvalidAlgorithmParameterException()155 public void testInitWithNullAlgParamsAndSecureRandomThrowsInvalidAlgorithmParameterException() 156 throws Exception { 157 SecureRandom rng = new SecureRandom(); 158 for (String algorithm : EXPECTED_ALGORITHMS) { 159 try { 160 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 161 try { 162 keyGenerator.init((AlgorithmParameterSpec) null, rng); 163 fail(); 164 } catch (InvalidAlgorithmParameterException expected) {} 165 } catch (Throwable e) { 166 throw new RuntimeException("Failed for " + algorithm, e); 167 } 168 } 169 } 170 testInitWithAlgParamsAndNullSecureRandom()171 public void testInitWithAlgParamsAndNullSecureRandom() 172 throws Exception { 173 for (String algorithm : EXPECTED_ALGORITHMS) { 174 try { 175 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 176 keyGenerator.init(getWorkingSpec().build(), (SecureRandom) null); 177 // Check that generateKey doesn't fail either, just in case null SecureRandom 178 // causes trouble there. 179 keyGenerator.generateKey(); 180 } catch (Throwable e) { 181 throw new RuntimeException("Failed for " + algorithm, e); 182 } 183 } 184 } 185 testInitWithUnsupportedAlgParamsTypeThrowsInvalidAlgorithmParameterException()186 public void testInitWithUnsupportedAlgParamsTypeThrowsInvalidAlgorithmParameterException() 187 throws Exception { 188 for (String algorithm : EXPECTED_ALGORITHMS) { 189 try { 190 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 191 try { 192 keyGenerator.init(new ECGenParameterSpec("secp256r1")); 193 fail(); 194 } catch (InvalidAlgorithmParameterException expected) {} 195 } catch (Throwable e) { 196 throw new RuntimeException("Failed for " + algorithm, e); 197 } 198 } 199 } 200 testDefaultKeySize()201 public void testDefaultKeySize() throws Exception { 202 for (String algorithm : EXPECTED_ALGORITHMS) { 203 try { 204 int expectedSizeBits = DEFAULT_KEY_SIZES.get(algorithm); 205 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 206 keyGenerator.init(getWorkingSpec().build()); 207 SecretKey key = keyGenerator.generateKey(); 208 assertEquals(expectedSizeBits, TestUtils.getKeyInfo(key).getKeySize()); 209 } catch (Throwable e) { 210 throw new RuntimeException("Failed for " + algorithm, e); 211 } 212 } 213 } 214 testAesKeySupportedSizes()215 public void testAesKeySupportedSizes() throws Exception { 216 KeyGenerator keyGenerator = getKeyGenerator("AES"); 217 KeyGenParameterSpec.Builder goodSpec = getWorkingSpec(); 218 CountingSecureRandom rng = new CountingSecureRandom(); 219 for (int i = -16; i <= 512; i++) { 220 try { 221 rng.resetCounters(); 222 KeyGenParameterSpec spec; 223 if (i >= 0) { 224 spec = TestUtils.buildUpon(goodSpec.setKeySize(i)).build(); 225 } else { 226 try { 227 spec = TestUtils.buildUpon(goodSpec.setKeySize(i)).build(); 228 fail(); 229 } catch (IllegalArgumentException expected) { 230 continue; 231 } 232 } 233 rng.resetCounters(); 234 if (TestUtils.contains(AES_SUPPORTED_KEY_SIZES, i)) { 235 keyGenerator.init(spec, rng); 236 SecretKey key = keyGenerator.generateKey(); 237 assertEquals(i, TestUtils.getKeyInfo(key).getKeySize()); 238 assertEquals((i + 7) / 8, rng.getOutputSizeBytes()); 239 } else { 240 try { 241 keyGenerator.init(spec, rng); 242 fail(); 243 } catch (InvalidAlgorithmParameterException expected) {} 244 assertEquals(0, rng.getOutputSizeBytes()); 245 } 246 } catch (Throwable e) { 247 throw new RuntimeException("Failed for key size " + i, e); 248 } 249 } 250 } 251 testHmacKeySupportedSizes()252 public void testHmacKeySupportedSizes() throws Exception { 253 CountingSecureRandom rng = new CountingSecureRandom(); 254 for (String algorithm : EXPECTED_ALGORITHMS) { 255 if (!TestUtils.isHmacAlgorithm(algorithm)) { 256 continue; 257 } 258 259 for (int i = -16; i <= 1024; i++) { 260 try { 261 rng.resetCounters(); 262 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 263 KeyGenParameterSpec spec; 264 if (i >= 0) { 265 spec = getWorkingSpec().setKeySize(i).build(); 266 } else { 267 try { 268 spec = getWorkingSpec().setKeySize(i).build(); 269 fail(); 270 } catch (IllegalArgumentException expected) { 271 continue; 272 } 273 } 274 if ((i >= 64) && ((i % 8 ) == 0)) { 275 keyGenerator.init(spec, rng); 276 SecretKey key = keyGenerator.generateKey(); 277 assertEquals(i, TestUtils.getKeyInfo(key).getKeySize()); 278 assertEquals((i + 7) / 8, rng.getOutputSizeBytes()); 279 } else if (i >= 64) { 280 try { 281 keyGenerator.init(spec, rng); 282 fail(); 283 } catch (InvalidAlgorithmParameterException expected) {} 284 assertEquals(0, rng.getOutputSizeBytes()); 285 } 286 } catch (Throwable e) { 287 throw new RuntimeException( 288 "Failed for " + algorithm + " with key size " + i, e); 289 } 290 } 291 } 292 } 293 testHmacKeyOnlyOneDigestCanBeAuthorized()294 public void testHmacKeyOnlyOneDigestCanBeAuthorized() throws Exception { 295 for (String algorithm : EXPECTED_ALGORITHMS) { 296 if (!TestUtils.isHmacAlgorithm(algorithm)) { 297 continue; 298 } 299 300 try { 301 String digest = TestUtils.getHmacAlgorithmDigest(algorithm); 302 assertNotNull(digest); 303 304 KeyGenParameterSpec.Builder goodSpec = new KeyGenParameterSpec.Builder( 305 "test1", KeyProperties.PURPOSE_SIGN); 306 307 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 308 309 // Digests authorization not specified in algorithm parameters 310 assertFalse(goodSpec.build().isDigestsSpecified()); 311 keyGenerator.init(goodSpec.build()); 312 SecretKey key = keyGenerator.generateKey(); 313 TestUtils.assertContentsInAnyOrder( 314 Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest); 315 316 // The same digest is specified in algorithm parameters 317 keyGenerator.init(TestUtils.buildUpon(goodSpec).setDigests(digest).build()); 318 key = keyGenerator.generateKey(); 319 TestUtils.assertContentsInAnyOrder( 320 Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest); 321 322 // No digests specified in algorithm parameters 323 try { 324 keyGenerator.init(TestUtils.buildUpon(goodSpec).setDigests().build()); 325 fail(); 326 } catch (InvalidAlgorithmParameterException expected) {} 327 328 // A different digest specified in algorithm parameters 329 String anotherDigest = "SHA-256".equalsIgnoreCase(digest) ? "SHA-384" : "SHA-256"; 330 try { 331 keyGenerator.init(TestUtils.buildUpon(goodSpec) 332 .setDigests(anotherDigest) 333 .build()); 334 fail(); 335 } catch (InvalidAlgorithmParameterException expected) {} 336 try { 337 keyGenerator.init(TestUtils.buildUpon(goodSpec) 338 .setDigests(digest, anotherDigest) 339 .build()); 340 fail(); 341 } catch (InvalidAlgorithmParameterException expected) {} 342 } catch (Throwable e) { 343 throw new RuntimeException("Failed for " + algorithm, e); 344 } 345 } 346 } 347 testInitWithUnknownBlockModeFails()348 public void testInitWithUnknownBlockModeFails() { 349 for (String algorithm : EXPECTED_ALGORITHMS) { 350 try { 351 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 352 try { 353 keyGenerator.init(getWorkingSpec().setBlockModes("weird").build()); 354 fail(); 355 } catch (InvalidAlgorithmParameterException expected) {} 356 } catch (Throwable e) { 357 throw new RuntimeException("Failed for " + algorithm, e); 358 } 359 } 360 } 361 testInitWithUnknownEncryptionPaddingFails()362 public void testInitWithUnknownEncryptionPaddingFails() { 363 for (String algorithm : EXPECTED_ALGORITHMS) { 364 try { 365 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 366 try { 367 keyGenerator.init(getWorkingSpec().setEncryptionPaddings("weird").build()); 368 fail(); 369 } catch (InvalidAlgorithmParameterException expected) {} 370 } catch (Throwable e) { 371 throw new RuntimeException("Failed for " + algorithm, e); 372 } 373 } 374 } 375 testInitWithSignaturePaddingFails()376 public void testInitWithSignaturePaddingFails() { 377 for (String algorithm : EXPECTED_ALGORITHMS) { 378 try { 379 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 380 try { 381 keyGenerator.init(getWorkingSpec() 382 .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) 383 .build()); 384 fail(); 385 } catch (InvalidAlgorithmParameterException expected) {} 386 } catch (Throwable e) { 387 throw new RuntimeException("Failed for " + algorithm, e); 388 } 389 } 390 } 391 testInitWithUnknownDigestFails()392 public void testInitWithUnknownDigestFails() { 393 for (String algorithm : EXPECTED_ALGORITHMS) { 394 try { 395 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 396 try { 397 String[] digests; 398 if (TestUtils.isHmacAlgorithm(algorithm)) { 399 // The digest from HMAC key algorithm must be specified in the list of 400 // authorized digests (if the list if provided). 401 digests = new String[] {algorithm, "weird"}; 402 } else { 403 digests = new String[] {"weird"}; 404 } 405 keyGenerator.init(getWorkingSpec().setDigests(digests).build()); 406 fail(); 407 } catch (InvalidAlgorithmParameterException expected) {} 408 } catch (Throwable e) { 409 throw new RuntimeException("Failed for " + algorithm, e); 410 } 411 } 412 } 413 testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFails()414 public void testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFails() { 415 for (String algorithm : EXPECTED_ALGORITHMS) { 416 if (!TestUtils.isHmacAlgorithm(algorithm)) { 417 continue; 418 } 419 try { 420 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 421 422 // Authorized for digest(s) none of which is the one implied by key algorithm. 423 try { 424 String digest = TestUtils.getHmacAlgorithmDigest(algorithm); 425 String anotherDigest = KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest) 426 ? KeyProperties.DIGEST_SHA512 : KeyProperties.DIGEST_SHA256; 427 keyGenerator.init(getWorkingSpec().setDigests(anotherDigest).build()); 428 fail(); 429 } catch (InvalidAlgorithmParameterException expected) {} 430 431 // Authorized for empty set of digests 432 try { 433 keyGenerator.init(getWorkingSpec().setDigests().build()); 434 fail(); 435 } catch (InvalidAlgorithmParameterException expected) {} 436 } catch (Throwable e) { 437 throw new RuntimeException("Failed for " + algorithm, e); 438 } 439 } 440 } 441 testInitRandomizedEncryptionRequiredButViolatedFails()442 public void testInitRandomizedEncryptionRequiredButViolatedFails() throws Exception { 443 for (String algorithm : EXPECTED_ALGORITHMS) { 444 try { 445 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 446 try { 447 keyGenerator.init(getWorkingSpec( 448 KeyProperties.PURPOSE_ENCRYPT) 449 .setBlockModes(KeyProperties.BLOCK_MODE_ECB) 450 .build()); 451 fail(); 452 } catch (InvalidAlgorithmParameterException expected) {} 453 } catch (Throwable e) { 454 throw new RuntimeException("Failed for " + algorithm, e); 455 } 456 } 457 } 458 testGenerateHonorsRequestedAuthorizations()459 public void testGenerateHonorsRequestedAuthorizations() throws Exception { 460 Date keyValidityStart = new Date(System.currentTimeMillis() - TestUtils.DAY_IN_MILLIS); 461 Date keyValidityForOriginationEnd = 462 new Date(System.currentTimeMillis() + TestUtils.DAY_IN_MILLIS); 463 Date keyValidityForConsumptionEnd = 464 new Date(System.currentTimeMillis() + 3 * TestUtils.DAY_IN_MILLIS); 465 for (String algorithm : EXPECTED_ALGORITHMS) { 466 try { 467 String[] blockModes = 468 new String[] {KeyProperties.BLOCK_MODE_GCM, KeyProperties.BLOCK_MODE_CBC}; 469 String[] encryptionPaddings = 470 new String[] {KeyProperties.ENCRYPTION_PADDING_PKCS7, 471 KeyProperties.ENCRYPTION_PADDING_NONE}; 472 String[] digests; 473 int purposes; 474 if (TestUtils.isHmacAlgorithm(algorithm)) { 475 // HMAC key can only be authorized for one digest, the one implied by the key's 476 // JCA algorithm name. 477 digests = new String[] {TestUtils.getHmacAlgorithmDigest(algorithm)}; 478 purposes = KeyProperties.PURPOSE_SIGN; 479 } else { 480 digests = new String[] {KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA1}; 481 purposes = KeyProperties.PURPOSE_DECRYPT; 482 } 483 KeyGenerator keyGenerator = getKeyGenerator(algorithm); 484 keyGenerator.init(getWorkingSpec(purposes) 485 .setBlockModes(blockModes) 486 .setEncryptionPaddings(encryptionPaddings) 487 .setDigests(digests) 488 .setKeyValidityStart(keyValidityStart) 489 .setKeyValidityForOriginationEnd(keyValidityForOriginationEnd) 490 .setKeyValidityForConsumptionEnd(keyValidityForConsumptionEnd) 491 .build()); 492 SecretKey key = keyGenerator.generateKey(); 493 assertEquals(algorithm, key.getAlgorithm()); 494 495 KeyInfo keyInfo = TestUtils.getKeyInfo(key); 496 assertEquals(purposes, keyInfo.getPurposes()); 497 TestUtils.assertContentsInAnyOrder( 498 Arrays.asList(blockModes), keyInfo.getBlockModes()); 499 TestUtils.assertContentsInAnyOrder( 500 Arrays.asList(encryptionPaddings), keyInfo.getEncryptionPaddings()); 501 TestUtils.assertContentsInAnyOrder(Arrays.asList(digests), keyInfo.getDigests()); 502 MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getSignaturePaddings())); 503 assertEquals(keyValidityStart, keyInfo.getKeyValidityStart()); 504 assertEquals(keyValidityForOriginationEnd, 505 keyInfo.getKeyValidityForOriginationEnd()); 506 assertEquals(keyValidityForConsumptionEnd, 507 keyInfo.getKeyValidityForConsumptionEnd()); 508 assertFalse(keyInfo.isUserAuthenticationRequired()); 509 assertFalse(keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware()); 510 } catch (Throwable e) { 511 throw new RuntimeException("Failed for " + algorithm, e); 512 } 513 } 514 } 515 getWorkingSpec()516 private static KeyGenParameterSpec.Builder getWorkingSpec() { 517 return getWorkingSpec(0); 518 } 519 getWorkingSpec(int purposes)520 private static KeyGenParameterSpec.Builder getWorkingSpec(int purposes) { 521 return new KeyGenParameterSpec.Builder("test1", purposes); 522 } 523 getKeyGenerator(String algorithm)524 private static KeyGenerator getKeyGenerator(String algorithm) throws NoSuchAlgorithmException, 525 NoSuchProviderException { 526 return KeyGenerator.getInstance(algorithm, EXPECTED_PROVIDER_NAME); 527 } 528 } 529