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