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