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