• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2016, The Android Open Source Project, Inc.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *   http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 package com.android.example;
17 
18 import static com.google.android.attestation.Constants.GOOGLE_ROOT_CA_PUB_KEY;
19 import static com.google.android.attestation.ParsedAttestationRecord.createParsedAttestationRecord;
20 import static java.nio.charset.StandardCharsets.UTF_8;
21 
22 import com.google.android.attestation.AttestationApplicationId;
23 import com.google.android.attestation.AttestationApplicationId.AttestationPackageInfo;
24 import com.google.android.attestation.CertificateRevocationStatus;
25 import com.google.android.attestation.AuthorizationList;
26 import com.google.android.attestation.ParsedAttestationRecord;
27 import com.google.android.attestation.RootOfTrust;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.ImmutableList.Builder;
30 import java.io.ByteArrayInputStream;
31 import java.io.IOException;
32 import java.nio.file.Files;
33 import java.nio.file.Path;
34 import java.nio.file.Paths;
35 import java.security.InvalidKeyException;
36 import java.security.NoSuchAlgorithmException;
37 import java.security.NoSuchProviderException;
38 import java.security.SignatureException;
39 import java.security.cert.CertificateException;
40 import java.security.cert.CertificateFactory;
41 import java.security.cert.X509Certificate;
42 import java.util.Arrays;
43 import java.util.List;
44 import java.util.Optional;
45 import java.util.stream.Collectors;
46 import java.util.stream.Stream;
47 import org.bouncycastle.util.encoders.Base64;
48 
49 /**
50  * This is an illustration of how you can use the Bouncy Castle ASN.1 parser to extract information
51  * from an Android attestation data structure. On a secure server that you trust, create similar
52  * logic to verify that a key pair has been generated in an Android device. The app on the device
53  * must retrieve the key's certificate chain using KeyStore.getCertificateChain(), then send the
54  * contents to the trusted server.
55  *
56  * <p>In this example, the certificate chain includes hard-coded excerpts of each certificate.
57  *
58  * <p>This example demonstrates the following tasks:
59  *
60  * <p>1. Loading the certificates from PEM-encoded strings.
61  *
62  * <p>2. Verifying the certificate chain, up to the root. Note that this example does NOT require
63  * the root certificate to appear within Google's list of root certificates. However, if you're
64  * verifying the properties of hardware-backed keys on a device that ships with hardware-level key
65  * attestation, Android 7.0 (API level 24) or higher, and Google Play services, your production code
66  * should enforce this requirement.
67  *
68  * <p>3. Checking if any certificate in the chain has been revoked or suspended.
69  *
70  * <p>4. Extracting the attestation extension data from the attestation certificate.
71  *
72  * <p>5. Verifying (and printing) several important data elements from the attestation extension.
73  *
74  */
75 @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
76 public class KeyAttestationExample {
77 
KeyAttestationExample()78   private KeyAttestationExample() {}
79 
main(String[] args)80   public static void main(String[] args)
81       throws CertificateException, IOException, NoSuchProviderException, NoSuchAlgorithmException,
82           InvalidKeyException, SignatureException {
83     List<X509Certificate> certs;
84     if (args.length == 1) {
85       String certFilesDir = args[0];
86       certs = loadCertificates(certFilesDir);
87     } else {
88       throw new IOException("Expected path to a directory containing certificates as an argument.");
89     }
90 
91     verifyCertificateChain(certs);
92 
93     ParsedAttestationRecord parsedAttestationRecord = createParsedAttestationRecord(certs);
94 
95     System.out.println("Attestation version: " + parsedAttestationRecord.attestationVersion);
96     System.out.println(
97         "Attestation Security Level: " + parsedAttestationRecord.attestationSecurityLevel.name());
98     System.out.println("Keymaster Version: " + parsedAttestationRecord.keymasterVersion);
99     System.out.println(
100         "Keymaster Security Level: " + parsedAttestationRecord.keymasterSecurityLevel.name());
101 
102     System.out.println(
103         "Attestation Challenge: "
104             + new String(parsedAttestationRecord.attestationChallenge, UTF_8));
105     System.out.println("Unique ID: " + Arrays.toString(parsedAttestationRecord.uniqueId));
106 
107     System.out.println("Software Enforced Authorization List:");
108     AuthorizationList softwareEnforced = parsedAttestationRecord.softwareEnforced;
109     printAuthorizationList(softwareEnforced, "\t");
110 
111     System.out.println("TEE Enforced Authorization List:");
112     AuthorizationList teeEnforced = parsedAttestationRecord.teeEnforced;
113     printAuthorizationList(teeEnforced, "\t");
114   }
115 
printAuthorizationList(AuthorizationList authorizationList, String indent)116   private static void printAuthorizationList(AuthorizationList authorizationList, String indent) {
117     // Detailed explanation of the keys and their values can be found here:
118     // https://source.android.com/security/keystore/tags
119     printOptional(authorizationList.purpose, indent + "Purpose(s)");
120     printOptional(authorizationList.algorithm, indent + "Algorithm");
121     printOptional(authorizationList.keySize, indent + "Key Size");
122     printOptional(authorizationList.digest, indent + "Digest");
123     printOptional(authorizationList.padding, indent + "Padding");
124     printOptional(authorizationList.ecCurve, indent + "EC Curve");
125     printOptional(authorizationList.rsaPublicExponent, indent + "RSA Public Exponent");
126     System.out.println(indent + "Rollback Resistance: " + authorizationList.rollbackResistance);
127     printOptional(authorizationList.activeDateTime, indent + "Active DateTime");
128     printOptional(
129         authorizationList.originationExpireDateTime, indent + "Origination Expire DateTime");
130     printOptional(authorizationList.usageExpireDateTime, indent + "Usage Expire DateTime");
131     System.out.println(indent + "No Auth Required: " + authorizationList.noAuthRequired);
132     printOptional(authorizationList.userAuthType, indent + "User Auth Type");
133     printOptional(authorizationList.authTimeout, indent + "Auth Timeout");
134     System.out.println(indent + "Allow While On Body: " + authorizationList.allowWhileOnBody);
135     System.out.println(
136         indent
137             + "Trusted User Presence Required: "
138             + authorizationList.trustedUserPresenceRequired);
139     System.out.println(
140         indent + "Trusted Confirmation Required: " + authorizationList.trustedConfirmationRequired);
141     System.out.println(
142         indent + "Unlocked Device Required: " + authorizationList.unlockedDeviceRequired);
143     System.out.println(indent + "All Applications: " + authorizationList.allApplications);
144     printOptional(authorizationList.applicationId, indent + "Application ID");
145     printOptional(authorizationList.creationDateTime, indent + "Creation DateTime");
146     printOptional(authorizationList.origin, indent + "Origin");
147     System.out.println(indent + "Rollback Resistant: " + authorizationList.rollbackResistant);
148     if (authorizationList.rootOfTrust.isPresent()) {
149       System.out.println(indent + "Root Of Trust:");
150       printRootOfTrust(authorizationList.rootOfTrust, indent + "\t");
151     }
152     printOptional(authorizationList.osVersion, indent + "OS Version");
153     printOptional(authorizationList.osPatchLevel, indent + "OS Patch Level");
154     if (authorizationList.attestationApplicationId.isPresent()) {
155       System.out.println(indent + "Attestation Application ID:");
156       printAttestationApplicationId(authorizationList.attestationApplicationId, indent + "\t");
157     }
158     printOptional(
159         authorizationList.attestationApplicationIdBytes,
160         indent + "Attestation Application ID Bytes");
161     printOptional(authorizationList.attestationIdBrand, indent + "Attestation ID Brand");
162     printOptional(authorizationList.attestationIdDevice, indent + "Attestation ID Device");
163     printOptional(authorizationList.attestationIdProduct, indent + "Attestation ID Product");
164     printOptional(authorizationList.attestationIdSerial, indent + "Attestation ID Serial");
165     printOptional(authorizationList.attestationIdImei, indent + "Attestation ID IMEI");
166     printOptional(
167         authorizationList.attestationIdSecondImei, indent + "Attestation ID SECOND IMEI");
168     printOptional(authorizationList.attestationIdMeid, indent + "Attestation ID MEID");
169     printOptional(
170         authorizationList.attestationIdManufacturer, indent + "Attestation ID Manufacturer");
171     printOptional(authorizationList.attestationIdModel, indent + "Attestation ID Model");
172     printOptional(authorizationList.vendorPatchLevel, indent + "Vendor Patch Level");
173     printOptional(authorizationList.bootPatchLevel, indent + "Boot Patch Level");
174     System.out.println(
175         indent + "Identity Credential Key: " + authorizationList.identityCredentialKey);
176   }
177 
printRootOfTrust(Optional<RootOfTrust> rootOfTrust, String indent)178   private static void printRootOfTrust(Optional<RootOfTrust> rootOfTrust, String indent) {
179     if (rootOfTrust.isPresent()) {
180       System.out.println(
181           indent
182               + "Verified Boot Key: "
183               + Base64.toBase64String(rootOfTrust.get().verifiedBootKey));
184       System.out.println(indent + "Device Locked: " + rootOfTrust.get().deviceLocked);
185       System.out.println(
186           indent + "Verified Boot State: " + rootOfTrust.get().verifiedBootState.name());
187       System.out.println(
188           indent
189               + "Verified Boot Hash: "
190               + Base64.toBase64String(rootOfTrust.get().verifiedBootHash));
191     }
192   }
193 
printAttestationApplicationId( Optional<AttestationApplicationId> attestationApplicationId, String indent)194   private static void printAttestationApplicationId(
195       Optional<AttestationApplicationId> attestationApplicationId, String indent) {
196     if (attestationApplicationId.isPresent()) {
197       System.out.println(indent + "Package Infos (<package name>, <version>): ");
198       for (AttestationPackageInfo info : attestationApplicationId.get().packageInfos) {
199         System.out.println(indent + "\t" + info.packageName + ", " + info.version);
200       }
201       System.out.println(indent + "Signature Digests:");
202       for (byte[] digest : attestationApplicationId.get().signatureDigests) {
203         System.out.println(indent + "\t" + Base64.toBase64String(digest));
204       }
205     }
206   }
207 
printOptional(Optional<T> optional, String caption)208   private static <T> void printOptional(Optional<T> optional, String caption) {
209     if (optional.isPresent()) {
210       if (optional.get() instanceof byte[]) {
211         System.out.println(caption + ": " + Base64.toBase64String((byte[]) optional.get()));
212       } else {
213         System.out.println(caption + ": " + optional.get());
214       }
215     }
216   }
217 
verifyCertificateChain(List<X509Certificate> certs)218   private static void verifyCertificateChain(List<X509Certificate> certs)
219       throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
220       NoSuchProviderException, SignatureException, IOException {
221     X509Certificate parent = certs.get(certs.size() - 1);
222     for (int i = certs.size() - 1; i >= 0; i--) {
223       X509Certificate cert = certs.get(i);
224       // Verify that the certificate has not expired.
225       cert.checkValidity();
226       cert.verify(parent.getPublicKey());
227       parent = cert;
228       try {
229         CertificateRevocationStatus certStatus = CertificateRevocationStatus
230             .fetchStatus(cert.getSerialNumber());
231         if (certStatus != null) {
232           throw new CertificateException(
233               "Certificate revocation status is " + certStatus.status.name());
234         }
235       } catch (IOException e) {
236         throw new IOException("Unable to fetch certificate status. Check connectivity.");
237       }
238     }
239 
240     // If the attestation is trustworthy and the device ships with hardware-
241     // backed key attestation, Android 7.0 (API level 24) or higher, and
242     // Google Play services, the root certificate should be signed with the
243     // Google attestation root key.
244     byte[] googleRootCaPubKey = Base64.decode(GOOGLE_ROOT_CA_PUB_KEY);
245     if (Arrays.equals(
246         googleRootCaPubKey,
247         certs.get(certs.size() - 1).getPublicKey().getEncoded())) {
248       System.out.println(
249           "The root certificate is correct, so this attestation is trustworthy, as long as none of"
250               + " the certificates in the chain have been revoked.");
251     } else {
252       System.out.println(
253           "The root certificate is NOT correct. The attestation was probably generated by"
254               + " software, not in secure hardware. This means that there is no guarantee that the"
255               + " claims within the attestation are correct. If you're using a production-level"
256               + " system, you should disregard any claims made within this attestation certificate"
257               + " as there is no authority backing them up.");
258     }
259   }
260 
loadCertificates(String certFilesDir)261   private static ImmutableList<X509Certificate> loadCertificates(String certFilesDir)
262       throws CertificateException, IOException {
263     // Load the attestation certificates from the directory in alphabetic order.
264     List<Path> records;
265     try (Stream<Path> pathStream = Files.walk(Paths.get(certFilesDir))) {
266       records = pathStream.filter(Files::isRegularFile).sorted().collect(Collectors.toList());
267     }
268     ImmutableList.Builder<X509Certificate> certs = new ImmutableList.Builder<>();
269     CertificateFactory factory = CertificateFactory.getInstance("X.509");
270     for (int i = 0; i < records.size(); ++i) {
271       byte[] encodedCert = Files.readAllBytes(records.get(i));
272       ByteArrayInputStream inputStream = new ByteArrayInputStream(encodedCert);
273       certs.add((X509Certificate) factory.generateCertificate(inputStream));
274     }
275     return certs.build();
276   }
277 }
278