1 /* 2 * Copyright (C) 2023 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 package com.android.compatibility.common.deviceinfo; 17 18 import static android.security.keystore.KeyProperties.DIGEST_SHA256; 19 import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC; 20 import static android.security.keystore.KeyProperties.PURPOSE_SIGN; 21 import static android.security.keystore.KeyProperties.PURPOSE_VERIFY; 22 23 import static com.android.bedstead.nene.packages.CommonPackages.FEATURE_DEVICE_ID_ATTESTATION; 24 25 import static com.google.android.attestation.ParsedAttestationRecord.createParsedAttestationRecord; 26 27 import static org.junit.Assert.assertTrue; 28 29 import static java.nio.charset.StandardCharsets.UTF_8; 30 31 import android.content.pm.PackageManager; 32 import android.security.keystore.DeviceIdAttestationException; 33 import android.security.keystore.KeyGenParameterSpec; 34 import android.util.Log; 35 36 import com.android.compatibility.common.util.DeviceInfoStore; 37 38 import com.google.android.attestation.AuthorizationList; 39 import com.google.android.attestation.ParsedAttestationRecord; 40 import com.google.android.attestation.RootOfTrust; 41 42 import java.io.IOException; 43 import java.security.InvalidAlgorithmParameterException; 44 import java.security.KeyPair; 45 import java.security.KeyPairGenerator; 46 import java.security.KeyStore; 47 import java.security.NoSuchAlgorithmException; 48 import java.security.NoSuchProviderException; 49 import java.security.cert.Certificate; 50 import java.security.cert.X509Certificate; 51 import java.security.spec.ECGenParameterSpec; 52 import java.util.ArrayList; 53 import java.util.Base64; 54 import java.util.List; 55 import java.util.Objects; 56 import java.util.Optional; 57 58 /** 59 * Feature Keystore Attestation device info collector. Collector collects information from the 60 * device's KeyMint implementation as reflected by the data in the key attestation record. These 61 * will be collected across devices and do not collect and PII outside of the device information. 62 */ 63 public final class KeystoreAttestationDeviceInfo extends DeviceInfo { 64 private static final String LOG_TAG = "KeystoreAttestationDeviceInfo"; 65 private static final String TEST_ALIAS_KEYSTORE = "testKeystore"; 66 private static final String TEST_ALIAS_STRONG_BOX = "testStrongBox"; 67 private static final byte[] CHALLENGE = "challenge".getBytes(); 68 69 @Override collectDeviceInfo(DeviceInfoStore store)70 protected void collectDeviceInfo(DeviceInfoStore store) throws Exception { 71 collectKeystoreAttestation(store); 72 if (getContext() 73 .getPackageManager() 74 .hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)) { 75 collectStrongBoxAttestation(store); 76 } 77 } 78 generateKeyPair(String algorithm, KeyGenParameterSpec spec)79 private void generateKeyPair(String algorithm, KeyGenParameterSpec spec) 80 throws NoSuchAlgorithmException, 81 NoSuchProviderException, 82 InvalidAlgorithmParameterException { 83 KeyPairGenerator keyPairGenerator = 84 KeyPairGenerator.getInstance(algorithm, "AndroidKeyStore"); 85 keyPairGenerator.initialize(spec); 86 KeyPair kp = keyPairGenerator.generateKeyPair(); 87 88 if (kp == null) { 89 Log.e(LOG_TAG, "Key generation failed"); 90 return; 91 } 92 } 93 collectKeystoreAttestation(DeviceInfoStore localStore)94 private void collectKeystoreAttestation(DeviceInfoStore localStore) throws Exception { 95 KeyStore mKeyStore = KeyStore.getInstance("AndroidKeyStore"); 96 mKeyStore.load(null); 97 mKeyStore.deleteEntry(TEST_ALIAS_KEYSTORE); 98 99 localStore.startGroup("keymint_key_attestation"); 100 loadCertAndCollectAttestation(mKeyStore, localStore, TEST_ALIAS_KEYSTORE, false); 101 localStore.endGroup(); 102 } 103 collectStrongBoxAttestation(DeviceInfoStore localStore)104 private void collectStrongBoxAttestation(DeviceInfoStore localStore) throws Exception { 105 KeyStore mKeyStore = KeyStore.getInstance("AndroidKeyStore"); 106 mKeyStore.load(null); 107 mKeyStore.deleteEntry(TEST_ALIAS_STRONG_BOX); 108 109 localStore.startGroup("strong_box_key_attestation"); 110 loadCertAndCollectAttestation(mKeyStore, localStore, TEST_ALIAS_STRONG_BOX, true); 111 localStore.endGroup(); 112 } 113 loadCertAndCollectAttestation( KeyStore keystore, DeviceInfoStore localStore, String testAlias, boolean isStrongBoxBacked)114 private void loadCertAndCollectAttestation( 115 KeyStore keystore, 116 DeviceInfoStore localStore, 117 String testAlias, 118 boolean isStrongBoxBacked) 119 throws Exception { 120 Objects.requireNonNull(keystore); 121 KeyGenParameterSpec spec = 122 new KeyGenParameterSpec.Builder(testAlias, PURPOSE_SIGN | PURPOSE_VERIFY) 123 .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) 124 .setDigests(DIGEST_SHA256) 125 .setDevicePropertiesAttestationIncluded( 126 getContext() 127 .getPackageManager() 128 .hasSystemFeature(FEATURE_DEVICE_ID_ATTESTATION)) 129 .setAttestationChallenge(CHALLENGE) 130 .setIsStrongBoxBacked(isStrongBoxBacked) 131 .build(); 132 133 generateKeyPair(KEY_ALGORITHM_EC, spec); 134 135 Certificate[] certificates = keystore.getCertificateChain(testAlias); 136 assertTrue(certificates.length >= 1); 137 138 final AuthorizationList keyDetailsList; 139 140 /* convert Certificate to List of X509Certificate */ 141 List<X509Certificate> x509Certificates = new ArrayList<>(); 142 for (Certificate certificate : certificates) { 143 if (certificate instanceof X509Certificate) { 144 x509Certificates.add((X509Certificate) certificate); 145 } 146 } 147 assertTrue(x509Certificates.size() >= 1); 148 149 ParsedAttestationRecord parsedAttestationRecord = 150 createParsedAttestationRecord(x509Certificates); 151 152 keyDetailsList = parsedAttestationRecord.teeEnforced; 153 154 collectStoredInformation(localStore, keyDetailsList); 155 } 156 collectStoredInformation( DeviceInfoStore localStore, AuthorizationList keyDetailsList)157 private static void collectStoredInformation( 158 DeviceInfoStore localStore, AuthorizationList keyDetailsList) 159 throws DeviceIdAttestationException, IOException { 160 if (keyDetailsList.rootOfTrust.isPresent()) { 161 collectRootOfTrust(keyDetailsList.rootOfTrust, localStore); 162 } 163 if (keyDetailsList.osVersion.isPresent()) { 164 localStore.addResult("os_version", keyDetailsList.osVersion.get()); 165 } 166 if (keyDetailsList.osPatchLevel.isPresent()) { 167 localStore.addResult("patch_level", keyDetailsList.osPatchLevel.get()); 168 } 169 if (keyDetailsList.attestationIdBrand.isPresent()) { 170 localStore.addResult( 171 "attestation_id_brand", 172 new String(keyDetailsList.attestationIdBrand.get(), UTF_8)); 173 } 174 if (keyDetailsList.attestationIdDevice.isPresent()) { 175 localStore.addResult( 176 "attestation_id_device", 177 new String(keyDetailsList.attestationIdDevice.get(), UTF_8)); 178 } 179 if (keyDetailsList.attestationIdProduct.isPresent()) { 180 localStore.addResult( 181 "attestation_id_product", 182 new String(keyDetailsList.attestationIdProduct.get(), UTF_8)); 183 } 184 if (keyDetailsList.attestationIdManufacturer.isPresent()) { 185 localStore.addResult( 186 "attestation_id_manufacturer", 187 new String(keyDetailsList.attestationIdManufacturer.get(), UTF_8)); 188 } 189 if (keyDetailsList.attestationIdModel.isPresent()) { 190 localStore.addResult( 191 "attestation_id_model", 192 new String(keyDetailsList.attestationIdModel.get(), UTF_8)); 193 } 194 if (keyDetailsList.vendorPatchLevel.isPresent()) { 195 localStore.addResult("vendor_patch_level", keyDetailsList.vendorPatchLevel.get()); 196 } 197 if (keyDetailsList.bootPatchLevel.isPresent()) { 198 localStore.addResult("boot_patch_level", keyDetailsList.bootPatchLevel.get()); 199 } 200 } 201 collectRootOfTrust( Optional<RootOfTrust> rootOfTrust, DeviceInfoStore localStore)202 private static void collectRootOfTrust( 203 Optional<RootOfTrust> rootOfTrust, DeviceInfoStore localStore) throws IOException { 204 if (rootOfTrust.isPresent()) { 205 localStore.addResult( 206 "verified_boot_key", 207 Base64.getEncoder().encodeToString(rootOfTrust.get().verifiedBootKey)); 208 localStore.addResult("device_locked", rootOfTrust.get().deviceLocked); 209 localStore.addResult("verified_boot_state", rootOfTrust.get().verifiedBootState.name()); 210 localStore.addResult( 211 "verified_boot_hash", 212 Base64.getEncoder().encodeToString(rootOfTrust.get().verifiedBootHash)); 213 } 214 } 215 } 216