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