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