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.AndroidTestCase;
23 import android.test.MoreAsserts;
24 
25 import com.google.common.collect.ObjectArrays;
26 
27 import junit.framework.TestCase;
28 
29 import java.security.InvalidAlgorithmParameterException;
30 import java.security.NoSuchAlgorithmException;
31 import java.security.NoSuchProviderException;
32 import java.security.Provider;
33 import java.security.Security;
34 import java.security.spec.AlgorithmParameterSpec;
35 import java.security.spec.ECGenParameterSpec;
36 import java.security.Provider.Service;
37 import java.security.SecureRandom;
38 import java.util.Arrays;
39 import java.util.Date;
40 import java.util.HashSet;
41 import java.util.Locale;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.TreeMap;
45 
46 import javax.crypto.KeyGenerator;
47 import javax.crypto.SecretKey;
48 
49 
50 public class KeyGeneratorTest extends AndroidTestCase {
51     private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_PROVIDER_NAME;
52 
53     static String[] EXPECTED_ALGORITHMS = {
54         "AES",
55         "HmacSHA1",
56         "HmacSHA224",
57         "HmacSHA256",
58         "HmacSHA384",
59         "HmacSHA512",
60     };
61 
62     static String[] EXPECTED_STRONGBOX_ALGORITHMS = {
63         "AES",
64         "HmacSHA256",
65     };
66 
67     {
68         if (TestUtils.supports3DES()) {
69             EXPECTED_ALGORITHMS = ObjectArrays.concat(EXPECTED_ALGORITHMS, "DESede");
70         }
71     }
72 
73     private static final Map<String, Integer> DEFAULT_KEY_SIZES =
74             new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
75     static {
76         DEFAULT_KEY_SIZES.put("AES", 128);
77         DEFAULT_KEY_SIZES.put("DESede", 168);
78         DEFAULT_KEY_SIZES.put("HmacSHA1", 160);
79         DEFAULT_KEY_SIZES.put("HmacSHA224", 224);
80         DEFAULT_KEY_SIZES.put("HmacSHA256", 256);
81         DEFAULT_KEY_SIZES.put("HmacSHA384", 384);
82         DEFAULT_KEY_SIZES.put("HmacSHA512", 512);
83     }
84 
85     static final int[] AES_SUPPORTED_KEY_SIZES = new int[] {128, 192, 256};
86     static final int[] AES_STRONGBOX_SUPPORTED_KEY_SIZES = new int[] {128, 256};
87     static final int[] DES_SUPPORTED_KEY_SIZES = new int[] {168};
88 
testAlgorithmList()89     public void testAlgorithmList() {
90         // Assert that Android Keystore Provider exposes exactly the expected KeyGenerator
91         // algorithms. We don't care whether the algorithms are exposed via aliases, as long as
92         // canonical names of algorithms are accepted. If the Provider exposes extraneous
93         // algorithms, it'll be caught because it'll have to expose at least one Service for such an
94         // algorithm, and this Service's algorithm will not be in the expected set.
95 
96         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
97         Set<Service> services = provider.getServices();
98         Set<String> actualAlgsLowerCase = new HashSet<String>();
99         Set<String> expectedAlgsLowerCase = new HashSet<String>(
100                 Arrays.asList(TestUtils.toLowerCase(EXPECTED_ALGORITHMS)));
101         for (Service service : services) {
102             if ("KeyGenerator".equalsIgnoreCase(service.getType())) {
103                 String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US);
104                 actualAlgsLowerCase.add(algLowerCase);
105             }
106         }
107 
108         TestUtils.assertContentsInAnyOrder(actualAlgsLowerCase,
109                 expectedAlgsLowerCase.toArray(new String[0]));
110     }
111 
testGenerateWithoutInitThrowsIllegalStateException()112     public void testGenerateWithoutInitThrowsIllegalStateException() throws Exception {
113         for (String algorithm : EXPECTED_ALGORITHMS) {
114             try {
115                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
116                 try {
117                     keyGenerator.generateKey();
118                     fail();
119                 } catch (IllegalStateException expected) {}
120             } catch (Throwable e) {
121                 throw new RuntimeException("Failed for " + algorithm, e);
122             }
123         }
124     }
125 
testInitWithKeySizeThrowsUnsupportedOperationException()126     public void testInitWithKeySizeThrowsUnsupportedOperationException() throws Exception {
127         for (String algorithm : EXPECTED_ALGORITHMS) {
128             try {
129                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
130                 int keySizeBits = DEFAULT_KEY_SIZES.get(algorithm);
131                 try {
132                     keyGenerator.init(keySizeBits);
133                     fail();
134                 } catch (UnsupportedOperationException expected) {}
135             } catch (Throwable e) {
136                 throw new RuntimeException("Failed for " + algorithm, e);
137             }
138         }
139     }
140 
testInitWithKeySizeAndSecureRandomThrowsUnsupportedOperationException()141     public void testInitWithKeySizeAndSecureRandomThrowsUnsupportedOperationException()
142             throws Exception {
143         SecureRandom rng = new SecureRandom();
144         for (String algorithm : EXPECTED_ALGORITHMS) {
145             try {
146                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
147                 int keySizeBits = DEFAULT_KEY_SIZES.get(algorithm);
148                 try {
149                     keyGenerator.init(keySizeBits, rng);
150                     fail();
151                 } catch (UnsupportedOperationException expected) {}
152             } catch (Throwable e) {
153                 throw new RuntimeException("Failed for " + algorithm, e);
154             }
155         }
156     }
157 
testInitWithNullAlgParamsThrowsInvalidAlgorithmParameterException()158     public void testInitWithNullAlgParamsThrowsInvalidAlgorithmParameterException()
159             throws Exception {
160         for (String algorithm : EXPECTED_ALGORITHMS) {
161             try {
162                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
163                 try {
164                     keyGenerator.init((AlgorithmParameterSpec) null);
165                     fail();
166                 } catch (InvalidAlgorithmParameterException expected) {}
167             } catch (Throwable e) {
168                 throw new RuntimeException("Failed for " + algorithm, e);
169             }
170         }
171     }
172 
testInitWithNullAlgParamsAndSecureRandomThrowsInvalidAlgorithmParameterException()173     public void testInitWithNullAlgParamsAndSecureRandomThrowsInvalidAlgorithmParameterException()
174             throws Exception {
175         SecureRandom rng = new SecureRandom();
176         for (String algorithm : EXPECTED_ALGORITHMS) {
177             try {
178                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
179                 try {
180                     keyGenerator.init((AlgorithmParameterSpec) null, rng);
181                     fail();
182                 } catch (InvalidAlgorithmParameterException expected) {}
183             } catch (Throwable e) {
184                 throw new RuntimeException("Failed for " + algorithm, e);
185             }
186         }
187     }
188 
testInitWithAlgParamsAndNullSecureRandom()189     public void testInitWithAlgParamsAndNullSecureRandom()
190             throws Exception {
191         testInitWithAlgParamsAndNullSecureRandomHelper(false /* useStrongbox */);
192         if (TestUtils.hasStrongBox(getContext())) {
193             testInitWithAlgParamsAndNullSecureRandomHelper(true /* useStrongbox */);
194         }
195     }
196 
testInitWithAlgParamsAndNullSecureRandomHelper(boolean useStrongbox)197     private void testInitWithAlgParamsAndNullSecureRandomHelper(boolean useStrongbox)
198             throws Exception {
199         for (String algorithm :
200             (useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS)) {
201             try {
202                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
203                 keyGenerator.init(getWorkingSpec()
204                     .setIsStrongBoxBacked(useStrongbox)
205                     .build(),
206                     (SecureRandom) null);
207                 // Check that generateKey doesn't fail either, just in case null SecureRandom
208                 // causes trouble there.
209                 keyGenerator.generateKey();
210             } catch (Throwable e) {
211                 throw new RuntimeException("Failed for " + algorithm, e);
212             }
213         }
214     }
215 
testInitWithUnsupportedAlgParamsTypeThrowsInvalidAlgorithmParameterException()216     public void testInitWithUnsupportedAlgParamsTypeThrowsInvalidAlgorithmParameterException()
217             throws Exception {
218         for (String algorithm : EXPECTED_ALGORITHMS) {
219             try {
220                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
221                 try {
222                     keyGenerator.init(new ECGenParameterSpec("secp256r1"));
223                     fail();
224                 } catch (InvalidAlgorithmParameterException expected) {}
225             } catch (Throwable e) {
226                 throw new RuntimeException("Failed for " + algorithm, e);
227             }
228         }
229     }
230 
testDefaultKeySize()231     public void testDefaultKeySize() throws Exception {
232         testDefaultKeySize(false /* useStrongbox */);
233         if (TestUtils.hasStrongBox(getContext())) {
234             testDefaultKeySize(true /* useStrongbox */);
235         }
236     }
237 
testDefaultKeySize(boolean useStrongbox)238     private void testDefaultKeySize(boolean useStrongbox) throws Exception {
239         for (String algorithm :
240             (useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS)) {
241             try {
242                 int expectedSizeBits = DEFAULT_KEY_SIZES.get(algorithm);
243                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
244                 keyGenerator.init(getWorkingSpec().build());
245                 SecretKey key = keyGenerator.generateKey();
246                 assertEquals(expectedSizeBits, TestUtils.getKeyInfo(key).getKeySize());
247             } catch (Throwable e) {
248                 throw new RuntimeException("Failed for " + algorithm, e);
249             }
250         }
251     }
252 
testAesKeySupportedSizes()253     public void testAesKeySupportedSizes() throws Exception {
254         testAesKeySupportedSizesHelper(false /* useStrongbox */);
255         if (TestUtils.hasStrongBox(getContext())) {
256             testAesKeySupportedSizesHelper(true /* useStrongbox */);
257         }
258     }
259 
testAesKeySupportedSizesHelper(boolean useStrongbox)260     private void testAesKeySupportedSizesHelper(boolean useStrongbox) throws Exception {
261         KeyGenerator keyGenerator = getKeyGenerator("AES");
262         KeyGenParameterSpec.Builder goodSpec = getWorkingSpec();
263         CountingSecureRandom rng = new CountingSecureRandom();
264         for (int i = -16; i <= 512; i++) {
265             try {
266                 rng.resetCounters();
267                 KeyGenParameterSpec spec;
268                 if (i >= 0) {
269                     spec = TestUtils.buildUpon(
270                         goodSpec.setKeySize(i)).setIsStrongBoxBacked(useStrongbox).build();
271                 } else {
272                     try {
273                         spec = TestUtils.buildUpon(
274                             goodSpec.setKeySize(i)).setIsStrongBoxBacked(useStrongbox).build();
275                         fail();
276                     } catch (IllegalArgumentException expected) {
277                         continue;
278                     }
279                 }
280                 rng.resetCounters();
281                 if (TestUtils.contains(useStrongbox ?
282                         AES_STRONGBOX_SUPPORTED_KEY_SIZES : AES_SUPPORTED_KEY_SIZES, i)) {
283                     keyGenerator.init(spec, rng);
284                     SecretKey key = keyGenerator.generateKey();
285                     assertEquals(i, TestUtils.getKeyInfo(key).getKeySize());
286                     assertEquals((i + 7) / 8, rng.getOutputSizeBytes());
287                 } else {
288                     try {
289                         if (useStrongbox && (i == 192))
290                             throw new InvalidAlgorithmParameterException("Strongbox does not"
291                                     + " support key size 192.");
292                         keyGenerator.init(spec, rng);
293                         fail();
294                     } catch (InvalidAlgorithmParameterException expected) {}
295                     assertEquals(0, rng.getOutputSizeBytes());
296                 }
297             } catch (Throwable e) {
298                 throw new RuntimeException("Failed for key size " + i, e);
299             }
300         }
301     }
302 
303     // TODO: This test will fail until b/117509689 is resolved.
testDESKeySupportedSizes()304     public void testDESKeySupportedSizes() throws Exception {
305         if (!TestUtils.supports3DES()) {
306             return;
307         }
308         KeyGenerator keyGenerator = getKeyGenerator("DESede");
309         KeyGenParameterSpec.Builder goodSpec = getWorkingSpec();
310         CountingSecureRandom rng = new CountingSecureRandom();
311         for (int i = -16; i <= 168; i++) {
312             try {
313                 rng.resetCounters();
314                 KeyGenParameterSpec spec;
315                 if (i >= 0) {
316                     spec = TestUtils.buildUpon(goodSpec.setKeySize(i)).build();
317                 } else {
318                     try {
319                         spec = TestUtils.buildUpon(goodSpec.setKeySize(i)).build();
320                         fail();
321                     } catch (IllegalArgumentException expected) {
322                         continue;
323                     }
324                 }
325                 rng.resetCounters();
326                 if (TestUtils.contains(DES_SUPPORTED_KEY_SIZES, i)) {
327                     keyGenerator.init(spec, rng);
328                     SecretKey key = keyGenerator.generateKey();
329                     assertEquals(i, TestUtils.getKeyInfo(key).getKeySize());
330                 } else {
331                     try {
332                         keyGenerator.init(spec, rng);
333                         fail();
334                     } catch (InvalidAlgorithmParameterException expected) {}
335                     assertEquals(0, rng.getOutputSizeBytes());
336                 }
337             } catch (Throwable e) {
338                 throw new RuntimeException("Failed for key size " + i +
339                     "\n***This test will continue to fail until b/117509689 is resolved***", e);
340             }
341         }
342     }
343 
testHmacKeySupportedSizes()344     public void testHmacKeySupportedSizes() throws Exception {
345         testHmacKeySupportedSizesHelper(false /* useStrongbox */);
346         if (TestUtils.hasStrongBox(getContext())) {
347             testHmacKeySupportedSizesHelper(true /* useStrongbox */);
348         }
349     }
350 
testHmacKeySupportedSizesHelper(boolean useStrongbox)351     private void testHmacKeySupportedSizesHelper(boolean useStrongbox) throws Exception {
352         CountingSecureRandom rng = new CountingSecureRandom();
353         for (String algorithm :
354             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
355             if (!TestUtils.isHmacAlgorithm(algorithm)) {
356                 continue;
357             }
358 
359             for (int i = -16; i <= 1024; i++) {
360                 try {
361                     rng.resetCounters();
362                     KeyGenerator keyGenerator = getKeyGenerator(algorithm);
363                     KeyGenParameterSpec spec;
364                     if (i >= 0) {
365                         spec = getWorkingSpec()
366                             .setKeySize(i)
367                             .setIsStrongBoxBacked(useStrongbox)
368                             .build();
369                     } else {
370                         try {
371                             spec = getWorkingSpec()
372                                 .setKeySize(i)
373                                 .setIsStrongBoxBacked(useStrongbox)
374                                 .build();
375                             fail();
376                         } catch (IllegalArgumentException expected) {
377                             continue;
378                         }
379                     }
380                     if (i > 512) {
381                         try {
382                             keyGenerator.init(spec, rng);
383                             fail();
384                         } catch (InvalidAlgorithmParameterException expected) {
385                             assertEquals(0, rng.getOutputSizeBytes());
386                         }
387                     } else if ((i >= 64) && ((i % 8 ) == 0)) {
388                         keyGenerator.init(spec, rng);
389                         SecretKey key = keyGenerator.generateKey();
390                         assertEquals(i, TestUtils.getKeyInfo(key).getKeySize());
391                         assertEquals((i + 7) / 8, rng.getOutputSizeBytes());
392                     } else if (i >= 64) {
393                         try {
394                             keyGenerator.init(spec, rng);
395                             fail();
396                         } catch (InvalidAlgorithmParameterException expected) {}
397                         assertEquals(0, rng.getOutputSizeBytes());
398                     }
399                 } catch (Throwable e) {
400                     throw new RuntimeException(
401                             "Failed for " + algorithm + " with key size " + i
402                             + ". Use Strongbox: " + useStrongbox, e);
403                 }
404             }
405         }
406     }
407 
testHmacKeyOnlyOneDigestCanBeAuthorized()408     public void testHmacKeyOnlyOneDigestCanBeAuthorized() throws Exception {
409         testHmacKeyOnlyOneDigestCanBeAuthorizedHelper(false /* useStrongbox */);
410         if (TestUtils.hasStrongBox(getContext())) {
411             testHmacKeyOnlyOneDigestCanBeAuthorizedHelper(true /* useStrongbox */);
412         }
413     }
414 
testHmacKeyOnlyOneDigestCanBeAuthorizedHelper(boolean useStrongbox)415     private void testHmacKeyOnlyOneDigestCanBeAuthorizedHelper(boolean useStrongbox)
416         throws Exception {
417         for (String algorithm :
418             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
419             if (!TestUtils.isHmacAlgorithm(algorithm)) {
420                 continue;
421             }
422 
423             try {
424                 String digest = TestUtils.getHmacAlgorithmDigest(algorithm);
425                 assertNotNull(digest);
426 
427                 KeyGenParameterSpec.Builder goodSpec = new KeyGenParameterSpec.Builder(
428                         "test1", KeyProperties.PURPOSE_SIGN);
429 
430                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
431 
432                 // Digests authorization not specified in algorithm parameters
433                 assertFalse(goodSpec
434                     .setIsStrongBoxBacked(useStrongbox)
435                     .build()
436                     .isDigestsSpecified());
437                 keyGenerator.init(goodSpec.setIsStrongBoxBacked(useStrongbox).build());
438                 SecretKey key = keyGenerator.generateKey();
439                 TestUtils.assertContentsInAnyOrder(
440                         Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest);
441 
442                 // The same digest is specified in algorithm parameters
443                 keyGenerator.init(TestUtils.buildUpon(goodSpec)
444                     .setDigests(digest)
445                     .setIsStrongBoxBacked(useStrongbox)
446                     .build());
447                 key = keyGenerator.generateKey();
448                 TestUtils.assertContentsInAnyOrder(
449                         Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest);
450 
451                 // No digests specified in algorithm parameters
452                 try {
453                     keyGenerator.init(TestUtils.buildUpon(goodSpec)
454                         .setDigests()
455                         .setIsStrongBoxBacked(useStrongbox)
456                         .build());
457                     fail();
458                 } catch (InvalidAlgorithmParameterException expected) {}
459 
460                 // A different digest specified in algorithm parameters
461                 String anotherDigest = "SHA-256".equalsIgnoreCase(digest) ? "SHA-384" : "SHA-256";
462                 try {
463                     keyGenerator.init(TestUtils.buildUpon(goodSpec)
464                             .setDigests(anotherDigest)
465                             .setIsStrongBoxBacked(useStrongbox)
466                             .build());
467                     fail();
468                 } catch (InvalidAlgorithmParameterException expected) {}
469                 try {
470                     keyGenerator.init(TestUtils.buildUpon(goodSpec)
471                             .setDigests(digest, anotherDigest)
472                             .setIsStrongBoxBacked(useStrongbox)
473                             .build());
474                     fail();
475                 } catch (InvalidAlgorithmParameterException expected) {}
476             } catch (Throwable e) {
477                 throw new RuntimeException("Failed for " + algorithm, e);
478             }
479         }
480     }
481 
testInitWithUnknownBlockModeFails()482     public void testInitWithUnknownBlockModeFails() {
483         testInitWithUnknownBlockModeFailsHelper(false /* useStrongbox */);
484         if (TestUtils.hasStrongBox(getContext())) {
485             testInitWithUnknownBlockModeFailsHelper(true /* useStrongbox */);
486         }
487     }
488 
testInitWithUnknownBlockModeFailsHelper(boolean useStrongbox)489     public void testInitWithUnknownBlockModeFailsHelper(boolean useStrongbox) {
490         for (String algorithm :
491             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
492             try {
493                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
494                 try {
495                     keyGenerator.init(
496                         getWorkingSpec()
497                         .setBlockModes("weird")
498                         .setIsStrongBoxBacked(useStrongbox)
499                         .build());
500                     fail();
501                 } catch (InvalidAlgorithmParameterException expected) {}
502             } catch (Throwable e) {
503                 throw new RuntimeException("Failed for " + algorithm, e);
504             }
505         }
506     }
507 
testInitWithUnknownEncryptionPaddingFails()508     public void testInitWithUnknownEncryptionPaddingFails() {
509         testInitWithUnknownEncryptionPaddingFailsHelper(false /* useStrongbox */);
510         if (TestUtils.hasStrongBox(getContext())) {
511             testInitWithUnknownEncryptionPaddingFailsHelper(true /* useStrongbox */);
512         }
513     }
514 
testInitWithUnknownEncryptionPaddingFailsHelper(boolean useStrongbox)515     private void testInitWithUnknownEncryptionPaddingFailsHelper(boolean useStrongbox) {
516         for (String algorithm :
517             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
518             try {
519                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
520                 try {
521                     keyGenerator.init(
522                         getWorkingSpec()
523                         .setEncryptionPaddings("weird")
524                         .setIsStrongBoxBacked(useStrongbox)
525                         .build());
526                     fail();
527                 } catch (InvalidAlgorithmParameterException expected) {}
528             } catch (Throwable e) {
529                 throw new RuntimeException("Failed for " + algorithm, e);
530             }
531         }
532     }
533 
testInitWithSignaturePaddingFails()534     public void testInitWithSignaturePaddingFails() {
535         testInitWithSignaturePaddingFailsHelper(false /* useStrongbox */);
536         if (TestUtils.hasStrongBox(getContext())) {
537             testInitWithSignaturePaddingFailsHelper(true /* useStrongbox */);
538         }
539     }
540 
testInitWithSignaturePaddingFailsHelper(boolean useStrongbox)541     private void testInitWithSignaturePaddingFailsHelper(boolean useStrongbox) {
542         for (String algorithm :
543             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
544             try {
545                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
546                 try {
547                     keyGenerator.init(getWorkingSpec()
548                             .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
549                             .setIsStrongBoxBacked(useStrongbox)
550                             .build());
551                     fail();
552                 } catch (InvalidAlgorithmParameterException expected) {}
553             } catch (Throwable e) {
554                 throw new RuntimeException("Failed for " + algorithm, e);
555             }
556         }
557     }
558 
testInitWithUnknownDigestFails()559     public void testInitWithUnknownDigestFails() {
560         testInitWithUnknownDigestFailsHelper(false /* useStrongbox */);
561         if (TestUtils.hasStrongBox(getContext())) {
562             testInitWithUnknownDigestFailsHelper(true /* useStrongbox */);
563         }
564     }
565 
testInitWithUnknownDigestFailsHelper(boolean useStrongbox)566     public void testInitWithUnknownDigestFailsHelper(boolean useStrongbox) {
567         for (String algorithm :
568             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
569             try {
570                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
571                 try {
572                     String[] digests;
573                     if (TestUtils.isHmacAlgorithm(algorithm)) {
574                         // The digest from HMAC key algorithm must be specified in the list of
575                         // authorized digests (if the list if provided).
576                         digests = new String[] {algorithm, "weird"};
577                     } else {
578                         digests = new String[] {"weird"};
579                     }
580                     keyGenerator.init(
581                         getWorkingSpec()
582                         .setDigests(digests)
583                         .setIsStrongBoxBacked(useStrongbox)
584                         .build());
585                     fail();
586                 } catch (InvalidAlgorithmParameterException expected) {}
587             } catch (Throwable e) {
588                 throw new RuntimeException("Failed for " + algorithm, e);
589             }
590         }
591     }
592 
testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFails()593     public void testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFails() {
594         testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFailsHelper(
595             false /* useStrongbox */);
596         if (TestUtils.hasStrongBox(getContext())) {
597             testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFailsHelper(
598                 true /* useStrongbox */);
599         }
600     }
601 
testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFailsHelper(boolean useStrongbox)602     private void testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFailsHelper(boolean useStrongbox) {
603         for (String algorithm :
604             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
605             if (!TestUtils.isHmacAlgorithm(algorithm)) {
606                 continue;
607             }
608             try {
609                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
610 
611                 // Authorized for digest(s) none of which is the one implied by key algorithm.
612                 try {
613                     String digest = TestUtils.getHmacAlgorithmDigest(algorithm);
614                     String anotherDigest = KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest)
615                             ? KeyProperties.DIGEST_SHA512 : KeyProperties.DIGEST_SHA256;
616                     keyGenerator.init(
617                         getWorkingSpec()
618                         .setDigests(anotherDigest)
619                         .setIsStrongBoxBacked(useStrongbox)
620                         .build());
621                     fail();
622                 } catch (InvalidAlgorithmParameterException expected) {}
623 
624                 // Authorized for empty set of digests
625                 try {
626                     keyGenerator.init(
627                         getWorkingSpec()
628                         .setDigests()
629                         .setIsStrongBoxBacked(useStrongbox)
630                         .build());
631                     fail();
632                 } catch (InvalidAlgorithmParameterException expected) {}
633             } catch (Throwable e) {
634                 throw new RuntimeException("Failed for " + algorithm, e);
635             }
636         }
637     }
638 
testInitRandomizedEncryptionRequiredButViolatedFails()639     public void testInitRandomizedEncryptionRequiredButViolatedFails() throws Exception {
640         testInitRandomizedEncryptionRequiredButViolatedFailsHelper(false /* useStrongbox */);
641         if (TestUtils.hasStrongBox(getContext())) {
642             testInitRandomizedEncryptionRequiredButViolatedFailsHelper(true /* useStrongbox */);
643         }
644     }
645 
testInitRandomizedEncryptionRequiredButViolatedFailsHelper(boolean useStrongbox)646     public void testInitRandomizedEncryptionRequiredButViolatedFailsHelper(boolean useStrongbox)
647         throws Exception {
648         for (String algorithm :
649             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
650             try {
651                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
652                 try {
653                     keyGenerator.init(getWorkingSpec(
654                             KeyProperties.PURPOSE_ENCRYPT)
655                             .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
656                             .setIsStrongBoxBacked(useStrongbox)
657                             .build());
658                     fail();
659                 } catch (InvalidAlgorithmParameterException expected) {}
660             } catch (Throwable e) {
661                 throw new RuntimeException("Failed for " + algorithm, e);
662             }
663         }
664     }
665 
testGenerateHonorsRequestedAuthorizations()666     public void testGenerateHonorsRequestedAuthorizations() throws Exception {
667         testGenerateHonorsRequestedAuthorizationsHelper(false /* useStrongbox */);
668         if (TestUtils.hasStrongBox(getContext())) {
669             testGenerateHonorsRequestedAuthorizationsHelper(true /* useStrongbox */);
670         }
671     }
672 
testGenerateHonorsRequestedAuthorizationsHelper(boolean useStrongbox)673     private void testGenerateHonorsRequestedAuthorizationsHelper(boolean useStrongbox)
674         throws Exception {
675         Date keyValidityStart = new Date(System.currentTimeMillis() - TestUtils.DAY_IN_MILLIS);
676         Date keyValidityForOriginationEnd =
677                 new Date(System.currentTimeMillis() + TestUtils.DAY_IN_MILLIS);
678         Date keyValidityForConsumptionEnd =
679                 new Date(System.currentTimeMillis() + 3 * TestUtils.DAY_IN_MILLIS);
680         for (String algorithm :
681             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
682             try {
683                 String[] blockModes =
684                         new String[] {KeyProperties.BLOCK_MODE_GCM, KeyProperties.BLOCK_MODE_CBC};
685                 String[] encryptionPaddings =
686                         new String[] {KeyProperties.ENCRYPTION_PADDING_PKCS7,
687                                 KeyProperties.ENCRYPTION_PADDING_NONE};
688                 String[] digests;
689                 int purposes;
690                 if (TestUtils.isHmacAlgorithm(algorithm)) {
691                     // HMAC key can only be authorized for one digest, the one implied by the key's
692                     // JCA algorithm name.
693                     digests = new String[] {TestUtils.getHmacAlgorithmDigest(algorithm)};
694                     purposes = KeyProperties.PURPOSE_SIGN;
695                 } else {
696                     digests = new String[] {KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA1};
697                     purposes = KeyProperties.PURPOSE_DECRYPT;
698                 }
699                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
700                 keyGenerator.init(getWorkingSpec(purposes)
701                         .setBlockModes(blockModes)
702                         .setEncryptionPaddings(encryptionPaddings)
703                         .setDigests(digests)
704                         .setKeyValidityStart(keyValidityStart)
705                         .setKeyValidityForOriginationEnd(keyValidityForOriginationEnd)
706                         .setKeyValidityForConsumptionEnd(keyValidityForConsumptionEnd)
707                         .setIsStrongBoxBacked(useStrongbox)
708                         .build());
709                 SecretKey key = keyGenerator.generateKey();
710                 assertEquals(algorithm, key.getAlgorithm());
711 
712                 KeyInfo keyInfo = TestUtils.getKeyInfo(key);
713                 assertEquals(purposes, keyInfo.getPurposes());
714                 TestUtils.assertContentsInAnyOrder(
715                         Arrays.asList(blockModes), keyInfo.getBlockModes());
716                 TestUtils.assertContentsInAnyOrder(
717                         Arrays.asList(encryptionPaddings), keyInfo.getEncryptionPaddings());
718                 TestUtils.assertContentsInAnyOrder(Arrays.asList(digests), keyInfo.getDigests());
719                 MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getSignaturePaddings()));
720                 assertEquals(keyValidityStart, keyInfo.getKeyValidityStart());
721                 assertEquals(keyValidityForOriginationEnd,
722                         keyInfo.getKeyValidityForOriginationEnd());
723                 assertEquals(keyValidityForConsumptionEnd,
724                         keyInfo.getKeyValidityForConsumptionEnd());
725                 assertFalse(keyInfo.isUserAuthenticationRequired());
726                 assertFalse(keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware());
727             } catch (Throwable e) {
728                 throw new RuntimeException("Failed for " + algorithm, e);
729             }
730         }
731     }
732 
testLimitedUseKey()733     public void testLimitedUseKey() throws Exception {
734         testLimitedUseKey(false /* useStrongbox */);
735         if (TestUtils.hasStrongBox(getContext())) {
736             testLimitedUseKey(true /* useStrongbox */);
737         }
738     }
739 
testLimitedUseKey(boolean useStrongbox)740     private void testLimitedUseKey(boolean useStrongbox) throws Exception {
741         int maxUsageCount = 1;
742         for (String algorithm :
743                 (useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS)) {
744             try {
745                 int expectedSizeBits = DEFAULT_KEY_SIZES.get(algorithm);
746                 KeyGenerator keyGenerator = getKeyGenerator(algorithm);
747                 keyGenerator.init(getWorkingSpec().setMaxUsageCount(maxUsageCount).build());
748                 SecretKey key = keyGenerator.generateKey();
749                 assertEquals(expectedSizeBits, TestUtils.getKeyInfo(key).getKeySize());
750                 assertEquals(maxUsageCount, TestUtils.getKeyInfo(key).getRemainingUsageCount());
751             } catch (Throwable e) {
752                 throw new RuntimeException("Failed for " + algorithm, e);
753             }
754         }
755     }
756 
getWorkingSpec()757     private static KeyGenParameterSpec.Builder getWorkingSpec() {
758         return getWorkingSpec(0);
759     }
760 
getWorkingSpec(int purposes)761     private static KeyGenParameterSpec.Builder getWorkingSpec(int purposes) {
762         return new KeyGenParameterSpec.Builder("test1", purposes);
763     }
764 
getKeyGenerator(String algorithm)765     private static KeyGenerator getKeyGenerator(String algorithm) throws NoSuchAlgorithmException,
766             NoSuchProviderException {
767         return KeyGenerator.getInstance(algorithm, EXPECTED_PROVIDER_NAME);
768     }
769 }
770