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