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