1 /*
2  * Copyright (C) 2021 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.KeyAttestationTest.verifyCertificateChain;
20 import static android.security.keystore.KeyProperties.DIGEST_SHA256;
21 import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
22 import static android.security.keystore.KeyProperties.KEY_ALGORITHM_RSA;
23 import static android.security.keystore.KeyProperties.PURPOSE_ATTEST_KEY;
24 import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
25 import static android.security.keystore.KeyProperties.SIGNATURE_PADDING_RSA_PSS;
26 
27 import static org.hamcrest.CoreMatchers.is;
28 import static org.hamcrest.MatcherAssert.assertThat;
29 import static org.hamcrest.Matchers.greaterThan;
30 import static org.junit.Assert.fail;
31 import static org.junit.Assume.assumeTrue;
32 
33 import android.content.pm.PackageManager;
34 import android.keystore.cts.util.TestUtils;
35 import android.security.keystore.KeyGenParameterSpec;
36 import android.util.Log;
37 
38 import androidx.test.platform.app.InstrumentationRegistry;
39 
40 import com.android.compatibility.common.util.CddTest;
41 
42 import org.junit.After;
43 import org.junit.Before;
44 import org.junit.Test;
45 
46 import java.security.GeneralSecurityException;
47 import java.security.InvalidAlgorithmParameterException;
48 import java.security.KeyPairGenerator;
49 import java.security.KeyStore;
50 import java.security.KeyStoreException;
51 import java.security.NoSuchAlgorithmException;
52 import java.security.NoSuchProviderException;
53 import java.security.cert.Certificate;
54 import java.security.cert.X509Certificate;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.stream.Stream;
58 
59 public class AttestKeyTest {
60     private static final String TAG = AttestKeyTest.class.getSimpleName();
61 
62     private KeyStore mKeyStore;
63     private ArrayList<String> mAliasesToDelete = new ArrayList();
64 
65     @Before
setUp()66     public void setUp() throws Exception {
67         mKeyStore = KeyStore.getInstance("AndroidKeyStore");
68         mKeyStore.load(null);
69 
70         // Assume attest key support for all tests in this class.
71         assumeAttestKey();
72     }
73 
74     @After
tearDown()75     public void tearDown() throws Exception {
76         for (String alias : mAliasesToDelete) {
77             try {
78                 mKeyStore.deleteEntry(alias);
79             } catch (Throwable t) {
80                 // Ignore any exception and delete the other aliases in the list.
81             }
82         }
83     }
84 
85     @Test
86     @CddTest(requirements = {"9.11/C-1-6"})
testEcAttestKey()87     public void testEcAttestKey() throws Exception {
88         testEcAttestKey(false /* useStrongBox */);
89     }
90 
91     @Test
92     @CddTest(requirements = {"9.11/C-1-6"})
testEcAttestKey_StrongBox()93     public void testEcAttestKey_StrongBox() throws Exception {
94         testEcAttestKey(true /* useStrongBox */);
95     }
96 
97     @Test
98     @CddTest(requirements = {"9.11/C-1-6"})
testRsaAttestKey()99     public void testRsaAttestKey() throws Exception {
100         testRsaAttestKey(false /* useStrongBox */);
101     }
102 
103     @Test
104     @CddTest(requirements = {"9.11/C-1-6"})
testRsaAttestKey_StrongBox()105     public void testRsaAttestKey_StrongBox() throws Exception {
106         testRsaAttestKey(true /* useStrongBox */);
107     }
108 
109     @Test
110     @CddTest(requirements = {"9.11/C-1-6"})
testAttestationWithNonAttestKey()111     public void testAttestationWithNonAttestKey() throws Exception {
112         final String nonAttestKeyAlias = "nonAttestKey";
113         final String attestedKeyAlias = "attestedKey";
114         generateKeyPair(KEY_ALGORITHM_EC,
115                 new KeyGenParameterSpec.Builder(nonAttestKeyAlias, PURPOSE_SIGN).build());
116 
117         try {
118             generateKeyPair(KEY_ALGORITHM_EC,
119                     new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
120                             .setAttestationChallenge("challenge".getBytes())
121                             .setAttestKeyAlias(nonAttestKeyAlias)
122                             .build());
123             fail("Expected exception.");
124         } catch (InvalidAlgorithmParameterException e) {
125             assertThat(e.getMessage(), is("Invalid attestKey, does not have PURPOSE_ATTEST_KEY"));
126         }
127     }
128 
129     @Test
130     @CddTest(requirements = {"9.11/C-1-6"})
testAttestKeyWithoutChallenge()131     public void testAttestKeyWithoutChallenge() throws Exception {
132         final String attestKeyAlias = "attestKey";
133         final String attestedKeyAlias = "attestedKey";
134         generateKeyPair(KEY_ALGORITHM_EC,
135                 new KeyGenParameterSpec.Builder(attestKeyAlias, PURPOSE_ATTEST_KEY).build());
136 
137         try {
138             generateKeyPair(KEY_ALGORITHM_EC,
139                     new KeyGenParameterSpec
140                             .Builder(attestedKeyAlias, PURPOSE_SIGN)
141                             // Don't set attestation challenge
142                             .setAttestKeyAlias(attestKeyAlias)
143                             .build());
144             fail("Expected exception.");
145         } catch (InvalidAlgorithmParameterException e) {
146             assertThat(e.getMessage(),
147                     is("AttestKey specified but no attestation challenge provided"));
148         }
149     }
150 
151     @Test
152     @CddTest(requirements = {"9.11/C-1-6"})
testStrongBoxCannotAttestToTeeKey()153     public void testStrongBoxCannotAttestToTeeKey() throws Exception {
154         assumeTrue("Can only test with strongbox keymint",
155                 TestUtils.hasKeystoreVersion(true /*isStrongBoxBased*/,
156                         Attestation.KM_VERSION_KEYMINT_1));
157 
158         final String strongBoxAttestKeyAlias = "nonAttestKey";
159         final String attestedKeyAlias = "attestedKey";
160         generateKeyPair(KEY_ALGORITHM_EC,
161                 new KeyGenParameterSpec.Builder(strongBoxAttestKeyAlias, PURPOSE_ATTEST_KEY)
162                         .setIsStrongBoxBacked(true)
163                         .build());
164 
165         try {
166             generateKeyPair(KEY_ALGORITHM_EC,
167                     new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
168                             .setAttestationChallenge("challenge".getBytes())
169                             .setAttestKeyAlias(strongBoxAttestKeyAlias)
170                             .build());
171             fail("Expected exception.");
172         } catch (InvalidAlgorithmParameterException e) {
173             assertThat(e.getMessage(),
174                     is("Invalid security level: Cannot sign non-StrongBox key with StrongBox "
175                             + "attestKey"));
176         }
177     }
178 
179     @Test
180     @CddTest(requirements = {"9.11/C-1-6"})
testTeeCannotAttestToStrongBoxKey()181     public void testTeeCannotAttestToStrongBoxKey() throws Exception {
182         TestUtils.assumeStrongBox();
183 
184         final String teeAttestKeyAlias = "nonAttestKey";
185         generateKeyPair(KEY_ALGORITHM_EC,
186                 new KeyGenParameterSpec.Builder(teeAttestKeyAlias, PURPOSE_ATTEST_KEY).build());
187 
188         try {
189             generateKeyPair(KEY_ALGORITHM_EC,
190                     new KeyGenParameterSpec.Builder("attestedKey", PURPOSE_SIGN)
191                             .setAttestationChallenge("challenge".getBytes())
192                             .setAttestKeyAlias(teeAttestKeyAlias)
193                             .setIsStrongBoxBacked(true)
194                             .build());
195             fail("Expected exception.");
196         } catch (InvalidAlgorithmParameterException e) {
197             assertThat(e.getMessage(),
198                     is("Invalid security level: Cannot sign StrongBox key with non-StrongBox "
199                             + "attestKey"));
200         }
201     }
202 
assumeAttestKey()203     private void assumeAttestKey() {
204         PackageManager packageManager =
205                 InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
206         assumeTrue("Can only test if we have the attest key feature.",
207                 packageManager.hasSystemFeature(PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY));
208     }
209 
testEcAttestKey(boolean useStrongBox)210     private void testEcAttestKey(boolean useStrongBox) throws Exception {
211         if (useStrongBox) {
212             TestUtils.assumeStrongBox();
213         }
214 
215         final String attestKeyAlias = "attestKey";
216 
217         Certificate attestKeyCertChain[] = generateKeyPair(KEY_ALGORITHM_EC,
218                 new KeyGenParameterSpec.Builder(attestKeyAlias, PURPOSE_ATTEST_KEY)
219                         .setAttestationChallenge("challenge".getBytes())
220                         .setIsStrongBoxBacked(useStrongBox)
221                         .build());
222         assertThat(attestKeyCertChain.length, greaterThan(1));
223 
224         testAttestKey(useStrongBox, attestKeyAlias, attestKeyCertChain);
225     }
226 
testRsaAttestKey(boolean useStrongBox)227     private void testRsaAttestKey(boolean useStrongBox) throws Exception {
228         if (useStrongBox) {
229             TestUtils.assumeStrongBox();
230         }
231 
232         final String attestKeyAlias = "attestKey";
233 
234         Certificate attestKeyCertChain[] = generateKeyPair(KEY_ALGORITHM_RSA,
235                 new KeyGenParameterSpec.Builder(attestKeyAlias, PURPOSE_ATTEST_KEY)
236                         .setAttestationChallenge("challenge".getBytes())
237                         .setIsStrongBoxBacked(useStrongBox)
238                         .build());
239         assertThat(attestKeyCertChain.length, greaterThan(1));
240 
241         testAttestKey(useStrongBox, attestKeyAlias, attestKeyCertChain);
242     }
243 
testAttestKey(boolean useStrongBox, String attestKeyAlias, Certificate[] attestKeyCertChain)244     private void testAttestKey(boolean useStrongBox, String attestKeyAlias,
245             Certificate[] attestKeyCertChain) throws Exception {
246         final String attestedEcKeyAlias = "attestedEcKey";
247         final Certificate attestedEcKeyCertChain[] = generateKeyPair(KEY_ALGORITHM_EC,
248                 new KeyGenParameterSpec.Builder(attestedEcKeyAlias, PURPOSE_SIGN)
249                         .setAttestationChallenge("challenge".getBytes())
250                         .setAttestKeyAlias(attestKeyAlias)
251                         .setIsStrongBoxBacked(useStrongBox)
252                         .build());
253 
254         // Even though we asked for an attestation, we only get one cert.
255         assertThat(attestedEcKeyCertChain.length, is(1));
256 
257         verifyCombinedChain(useStrongBox, attestKeyCertChain, attestedEcKeyCertChain);
258 
259         final X509Certificate attestationEcKeyCert = (X509Certificate) attestedEcKeyCertChain[0];
260         final Attestation ecKeyAttestation = Attestation.loadFromCertificate(attestationEcKeyCert);
261 
262         final String attestedRsaKeyAlias = "attestedRsaKey";
263         final Certificate attestedRsaKeyCertChain[] = generateKeyPair(KEY_ALGORITHM_RSA,
264                 new KeyGenParameterSpec.Builder(attestedRsaKeyAlias, PURPOSE_SIGN)
265                         .setAttestationChallenge("challenge".getBytes())
266                         .setAttestKeyAlias(attestKeyAlias)
267                         .setIsStrongBoxBacked(useStrongBox)
268                         .setDigests(DIGEST_SHA256)
269                         .setSignaturePaddings(SIGNATURE_PADDING_RSA_PSS)
270                         .build());
271 
272         // Even though we asked for an attestation, we only get one cert.
273         assertThat(attestedRsaKeyCertChain.length, is(1));
274 
275         verifyCombinedChain(useStrongBox, attestKeyCertChain, attestedRsaKeyCertChain);
276 
277         final X509Certificate attestationRsaKeyCert = (X509Certificate) attestedRsaKeyCertChain[0];
278         final Attestation rsaKeyAttestation =
279                 Attestation.loadFromCertificate(attestationRsaKeyCert);
280     }
281 
generateKeyPair(String algorithm, KeyGenParameterSpec spec)282     private Certificate[] generateKeyPair(String algorithm, KeyGenParameterSpec spec)
283             throws NoSuchAlgorithmException, NoSuchProviderException,
284                    InvalidAlgorithmParameterException, KeyStoreException {
285         KeyPairGenerator keyPairGenerator =
286                 KeyPairGenerator.getInstance(algorithm, "AndroidKeyStore");
287         keyPairGenerator.initialize(spec);
288         keyPairGenerator.generateKeyPair();
289         mAliasesToDelete.add(spec.getKeystoreAlias());
290 
291         return mKeyStore.getCertificateChain(spec.getKeystoreAlias());
292     }
293 
verifyCombinedChain(boolean useStrongBox, Certificate[] attestKeyCertChain, Certificate[] attestedKeyCertChain)294     private void verifyCombinedChain(boolean useStrongBox, Certificate[] attestKeyCertChain,
295             Certificate[] attestedKeyCertChain) throws GeneralSecurityException {
296         Certificate[] combinedChain = Stream.concat(Arrays.stream(attestedKeyCertChain),
297                                                     Arrays.stream(attestKeyCertChain))
298                                               .toArray(Certificate[] ::new);
299 
300         int i = 0;
301         for (Certificate cert : combinedChain) {
302             Log.e(TAG, "Certificate " + i + ": " + cert);
303             ++i;
304         }
305 
306         verifyCertificateChain((Certificate[]) combinedChain, useStrongBox);
307     }
308 }
309