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