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