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