| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * 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.google.attestationexample; |
| |
| import com.google.common.base.CharMatcher; |
| import com.google.common.io.BaseEncoding; |
| |
| import org.bouncycastle.asn1.ASN1Sequence; |
| |
| import java.security.cert.CertificateParsingException; |
| import java.security.cert.X509Certificate; |
| |
| /** |
| * Parses an attestation certificate and provides an easy-to-use interface for examining the |
| * contents. |
| */ |
| public class Attestation { |
| static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17"; |
| static final int ATTESTATION_VERSION_INDEX = 0; |
| static final int ATTESTATION_SECURITY_LEVEL_INDEX = 1; |
| static final int KEYMASTER_VERSION_INDEX = 2; |
| static final int KEYMASTER_SECURITY_LEVEL_INDEX = 3; |
| static final int ATTESTATION_CHALLENGE_INDEX = 4; |
| static final int UNIQUE_ID_INDEX = 5; |
| static final int SW_ENFORCED_INDEX = 6; |
| static final int TEE_ENFORCED_INDEX = 7; |
| |
| public static final int KM_SECURITY_LEVEL_SOFTWARE = 0; |
| public static final int KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT = 1; |
| public static final int KM_SECURITY_LEVEL_STRONGBOX = 2; |
| |
| private final boolean haveAttestation; |
| private final int attestationVersion; |
| private final int attestationSecurityLevel; |
| private final int keymasterVersion; |
| private final int keymasterSecurityLevel; |
| private final byte[] attestationChallenge; |
| private final byte[] uniqueId; |
| private final AuthorizationList softwareEnforced; |
| private final AuthorizationList teeEnforced; |
| |
| |
| /** |
| * Constructs an {@code Attestation} object from the provided {@link X509Certificate}, |
| * extracting the attestation data from the attestation extension. |
| * |
| * @throws CertificateParsingException if the certificate does not contain a properly-formatted |
| * attestation extension. |
| */ |
| public Attestation(X509Certificate x509Cert) throws CertificateParsingException { |
| ASN1Sequence seq = getAttestationSequence(x509Cert); |
| if (seq == null) { |
| haveAttestation = false; |
| attestationVersion = 0; |
| attestationSecurityLevel = 0; |
| keymasterVersion = 0; |
| keymasterSecurityLevel = 0; |
| attestationChallenge = null; |
| uniqueId = null; |
| softwareEnforced = null; |
| teeEnforced = null; |
| return; |
| } |
| |
| haveAttestation = true; |
| attestationVersion = |
| Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_VERSION_INDEX)); |
| attestationSecurityLevel = |
| Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX)); |
| keymasterVersion = Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_VERSION_INDEX)); |
| keymasterSecurityLevel = |
| Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX)); |
| |
| attestationChallenge = |
| Asn1Utils.getByteArrayFromAsn1( |
| seq.getObjectAt(Attestation.ATTESTATION_CHALLENGE_INDEX)); |
| |
| uniqueId = Asn1Utils.getByteArrayFromAsn1(seq.getObjectAt(Attestation.UNIQUE_ID_INDEX)); |
| |
| softwareEnforced = new AuthorizationList(seq.getObjectAt(SW_ENFORCED_INDEX)); |
| teeEnforced = new AuthorizationList(seq.getObjectAt(TEE_ENFORCED_INDEX)); |
| } |
| |
| public static String securityLevelToString(int attestationSecurityLevel) { |
| switch (attestationSecurityLevel) { |
| case KM_SECURITY_LEVEL_SOFTWARE: |
| return "Software"; |
| case KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT: |
| return "TEE"; |
| case KM_SECURITY_LEVEL_STRONGBOX: |
| return "StrongBox"; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| public int getAttestationVersion() { |
| return attestationVersion; |
| } |
| |
| public int getAttestationSecurityLevel() { |
| return attestationSecurityLevel; |
| } |
| |
| public int getKeymasterVersion() { |
| return keymasterVersion; |
| } |
| |
| public int getKeymasterSecurityLevel() { |
| return keymasterSecurityLevel; |
| } |
| |
| public byte[] getAttestationChallenge() { |
| return attestationChallenge; |
| } |
| |
| public byte[] getUniqueId() { |
| return uniqueId; |
| } |
| |
| public AuthorizationList getSoftwareEnforced() { |
| return softwareEnforced; |
| } |
| |
| public AuthorizationList getTeeEnforced() { |
| return teeEnforced; |
| } |
| |
| @Override |
| public String toString() { |
| if (!haveAttestation) { |
| return "No attestation"; |
| } |
| |
| StringBuilder s = new StringBuilder(); |
| s.append("Attestation version: " + attestationVersion); |
| s.append("\nAttestation security: " + securityLevelToString(attestationSecurityLevel)); |
| s.append("\nKM version: " + keymasterVersion); |
| s.append("\nKM security: " + securityLevelToString(keymasterSecurityLevel)); |
| |
| s.append("\nChallenge"); |
| String stringChallenge = new String(attestationChallenge); |
| if (CharMatcher.ascii().matchesAllOf(stringChallenge)) { |
| s.append(": [" + stringChallenge + "]"); |
| } else { |
| s.append(" (base64): [" + BaseEncoding.base64().encode(attestationChallenge) + "]"); |
| } |
| if (uniqueId != null) { |
| s.append("\nUnique ID (base64): [" + BaseEncoding.base64().encode(uniqueId) + "]"); |
| } |
| |
| s.append("\n\n-- SW enforced --"); |
| s.append(softwareEnforced); |
| s.append("\n\n-- TEE enforced --"); |
| s.append(teeEnforced); |
| s.append("\n"); |
| |
| return s.toString(); |
| } |
| |
| private ASN1Sequence getAttestationSequence(X509Certificate x509Cert) |
| throws CertificateParsingException { |
| byte[] attestationExtensionBytes = x509Cert.getExtensionValue(KEY_DESCRIPTION_OID); |
| if (attestationExtensionBytes == null || attestationExtensionBytes.length == 0) { |
| return null; |
| } |
| return Asn1Utils.getAsn1SequenceFromBytes(attestationExtensionBytes); |
| } |
| |
| } |