/* Copyright 2016, The Android Open Source Project, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.example; import static com.google.android.attestation.Constants.GOOGLE_ROOT_CA_PUB_KEY; import static com.google.android.attestation.ParsedAttestationRecord.createParsedAttestationRecord; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.android.attestation.AttestationApplicationId; import com.google.android.attestation.AttestationApplicationId.AttestationPackageInfo; import com.google.android.attestation.CertificateRevocationStatus; import com.google.android.attestation.AuthorizationList; import com.google.android.attestation.ParsedAttestationRecord; import com.google.android.attestation.RootOfTrust; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import org.bouncycastle.util.encoders.Base64; /** * This is an illustration of how you can use the Bouncy Castle ASN.1 parser to extract information * from an Android attestation data structure. On a secure server that you trust, create similar * logic to verify that a key pair has been generated in an Android device. The app on the device * must retrieve the key's certificate chain using KeyStore.getCertificateChain(), then send the * contents to the trusted server. * *

In this example, the certificate chain includes hard-coded excerpts of each certificate. * *

This example demonstrates the following tasks: * *

1. Loading the certificates from PEM-encoded strings. * *

2. Verifying the certificate chain, up to the root. Note that this example does NOT require * the root certificate to appear within Google's list of root certificates. However, if you're * verifying the properties of hardware-backed keys on a device that ships with hardware-level key * attestation, Android 7.0 (API level 24) or higher, and Google Play services, your production code * should enforce this requirement. * *

3. Checking if any certificate in the chain has been revoked or suspended. * *

4. Extracting the attestation extension data from the attestation certificate. * *

5. Verifying (and printing) several important data elements from the attestation extension. * */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class KeyAttestationExample { private KeyAttestationExample() {} public static void main(String[] args) throws CertificateException, IOException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { List certs; if (args.length == 1) { String certFilesDir = args[0]; certs = loadCertificates(certFilesDir); } else { throw new IOException("Expected path to a directory containing certificates as an argument."); } verifyCertificateChain(certs); ParsedAttestationRecord parsedAttestationRecord = createParsedAttestationRecord(certs); System.out.println("Attestation version: " + parsedAttestationRecord.attestationVersion); System.out.println( "Attestation Security Level: " + parsedAttestationRecord.attestationSecurityLevel.name()); System.out.println("Keymaster Version: " + parsedAttestationRecord.keymasterVersion); System.out.println( "Keymaster Security Level: " + parsedAttestationRecord.keymasterSecurityLevel.name()); System.out.println( "Attestation Challenge: " + new String(parsedAttestationRecord.attestationChallenge, UTF_8)); System.out.println("Unique ID: " + Arrays.toString(parsedAttestationRecord.uniqueId)); System.out.println("Software Enforced Authorization List:"); AuthorizationList softwareEnforced = parsedAttestationRecord.softwareEnforced; printAuthorizationList(softwareEnforced, "\t"); System.out.println("TEE Enforced Authorization List:"); AuthorizationList teeEnforced = parsedAttestationRecord.teeEnforced; printAuthorizationList(teeEnforced, "\t"); } private static void printAuthorizationList(AuthorizationList authorizationList, String indent) { // Detailed explanation of the keys and their values can be found here: // https://source.android.com/security/keystore/tags printOptional(authorizationList.purpose, indent + "Purpose(s)"); printOptional(authorizationList.algorithm, indent + "Algorithm"); printOptional(authorizationList.keySize, indent + "Key Size"); printOptional(authorizationList.digest, indent + "Digest"); printOptional(authorizationList.padding, indent + "Padding"); printOptional(authorizationList.ecCurve, indent + "EC Curve"); printOptional(authorizationList.rsaPublicExponent, indent + "RSA Public Exponent"); System.out.println(indent + "Rollback Resistance: " + authorizationList.rollbackResistance); printOptional(authorizationList.activeDateTime, indent + "Active DateTime"); printOptional( authorizationList.originationExpireDateTime, indent + "Origination Expire DateTime"); printOptional(authorizationList.usageExpireDateTime, indent + "Usage Expire DateTime"); System.out.println(indent + "No Auth Required: " + authorizationList.noAuthRequired); printOptional(authorizationList.userAuthType, indent + "User Auth Type"); printOptional(authorizationList.authTimeout, indent + "Auth Timeout"); System.out.println(indent + "Allow While On Body: " + authorizationList.allowWhileOnBody); System.out.println( indent + "Trusted User Presence Required: " + authorizationList.trustedUserPresenceRequired); System.out.println( indent + "Trusted Confirmation Required: " + authorizationList.trustedConfirmationRequired); System.out.println( indent + "Unlocked Device Required: " + authorizationList.unlockedDeviceRequired); System.out.println(indent + "All Applications: " + authorizationList.allApplications); printOptional(authorizationList.applicationId, indent + "Application ID"); printOptional(authorizationList.creationDateTime, indent + "Creation DateTime"); printOptional(authorizationList.origin, indent + "Origin"); System.out.println(indent + "Rollback Resistant: " + authorizationList.rollbackResistant); if (authorizationList.rootOfTrust.isPresent()) { System.out.println(indent + "Root Of Trust:"); printRootOfTrust(authorizationList.rootOfTrust, indent + "\t"); } printOptional(authorizationList.osVersion, indent + "OS Version"); printOptional(authorizationList.osPatchLevel, indent + "OS Patch Level"); if (authorizationList.attestationApplicationId.isPresent()) { System.out.println(indent + "Attestation Application ID:"); printAttestationApplicationId(authorizationList.attestationApplicationId, indent + "\t"); } printOptional( authorizationList.attestationApplicationIdBytes, indent + "Attestation Application ID Bytes"); printOptional(authorizationList.attestationIdBrand, indent + "Attestation ID Brand"); printOptional(authorizationList.attestationIdDevice, indent + "Attestation ID Device"); printOptional(authorizationList.attestationIdProduct, indent + "Attestation ID Product"); printOptional(authorizationList.attestationIdSerial, indent + "Attestation ID Serial"); printOptional(authorizationList.attestationIdImei, indent + "Attestation ID IMEI"); printOptional( authorizationList.attestationIdSecondImei, indent + "Attestation ID SECOND IMEI"); printOptional(authorizationList.attestationIdMeid, indent + "Attestation ID MEID"); printOptional( authorizationList.attestationIdManufacturer, indent + "Attestation ID Manufacturer"); printOptional(authorizationList.attestationIdModel, indent + "Attestation ID Model"); printOptional(authorizationList.vendorPatchLevel, indent + "Vendor Patch Level"); printOptional(authorizationList.bootPatchLevel, indent + "Boot Patch Level"); System.out.println( indent + "Identity Credential Key: " + authorizationList.identityCredentialKey); } private static void printRootOfTrust(Optional rootOfTrust, String indent) { if (rootOfTrust.isPresent()) { System.out.println( indent + "Verified Boot Key: " + Base64.toBase64String(rootOfTrust.get().verifiedBootKey)); System.out.println(indent + "Device Locked: " + rootOfTrust.get().deviceLocked); System.out.println( indent + "Verified Boot State: " + rootOfTrust.get().verifiedBootState.name()); System.out.println( indent + "Verified Boot Hash: " + Base64.toBase64String(rootOfTrust.get().verifiedBootHash)); } } private static void printAttestationApplicationId( Optional attestationApplicationId, String indent) { if (attestationApplicationId.isPresent()) { System.out.println(indent + "Package Infos (, ): "); for (AttestationPackageInfo info : attestationApplicationId.get().packageInfos) { System.out.println(indent + "\t" + info.packageName + ", " + info.version); } System.out.println(indent + "Signature Digests:"); for (byte[] digest : attestationApplicationId.get().signatureDigests) { System.out.println(indent + "\t" + Base64.toBase64String(digest)); } } } private static void printOptional(Optional optional, String caption) { if (optional.isPresent()) { if (optional.get() instanceof byte[]) { System.out.println(caption + ": " + Base64.toBase64String((byte[]) optional.get())); } else { System.out.println(caption + ": " + optional.get()); } } } private static void verifyCertificateChain(List certs) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, IOException { X509Certificate parent = certs.get(certs.size() - 1); for (int i = certs.size() - 1; i >= 0; i--) { X509Certificate cert = certs.get(i); // Verify that the certificate has not expired. cert.checkValidity(); cert.verify(parent.getPublicKey()); parent = cert; try { CertificateRevocationStatus certStatus = CertificateRevocationStatus .fetchStatus(cert.getSerialNumber()); if (certStatus != null) { throw new CertificateException( "Certificate revocation status is " + certStatus.status.name()); } } catch (IOException e) { throw new IOException("Unable to fetch certificate status. Check connectivity."); } } // If the attestation is trustworthy and the device ships with hardware- // backed key attestation, Android 7.0 (API level 24) or higher, and // Google Play services, the root certificate should be signed with the // Google attestation root key. byte[] googleRootCaPubKey = Base64.decode(GOOGLE_ROOT_CA_PUB_KEY); if (Arrays.equals( googleRootCaPubKey, certs.get(certs.size() - 1).getPublicKey().getEncoded())) { System.out.println( "The root certificate is correct, so this attestation is trustworthy, as long as none of" + " the certificates in the chain have been revoked."); } else { System.out.println( "The root certificate is NOT correct. The attestation was probably generated by" + " software, not in secure hardware. This means that there is no guarantee that the" + " claims within the attestation are correct. If you're using a production-level" + " system, you should disregard any claims made within this attestation certificate" + " as there is no authority backing them up."); } } private static ImmutableList loadCertificates(String certFilesDir) throws CertificateException, IOException { // Load the attestation certificates from the directory in alphabetic order. List records; try (Stream pathStream = Files.walk(Paths.get(certFilesDir))) { records = pathStream.filter(Files::isRegularFile).sorted().collect(Collectors.toList()); } ImmutableList.Builder certs = new ImmutableList.Builder<>(); CertificateFactory factory = CertificateFactory.getInstance("X.509"); for (int i = 0; i < records.size(); ++i) { byte[] encodedCert = Files.readAllBytes(records.get(i)); ByteArrayInputStream inputStream = new ByteArrayInputStream(encodedCert); certs.add((X509Certificate) factory.generateCertificate(inputStream)); } return certs.build(); } }