1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.keystore.cts;
18 
19 import com.google.common.base.CharMatcher;
20 import com.google.common.collect.ImmutableSet;
21 import com.google.common.io.BaseEncoding;
22 
23 import org.bouncycastle.asn1.ASN1Sequence;
24 
25 import java.security.cert.CertificateParsingException;
26 import java.security.cert.X509Certificate;
27 import java.util.Set;
28 import java.util.stream.Collectors;
29 
30 /**
31  * Parses an attestation certificate and provides an easy-to-use interface for examining the
32  * contents.
33  */
34 public class Attestation {
35     static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
36     static final String KEY_USAGE_OID = "2.5.29.15";  // Standard key usage extension.
37     static final int ATTESTATION_VERSION_INDEX = 0;
38     static final int ATTESTATION_SECURITY_LEVEL_INDEX = 1;
39     static final int KEYMASTER_VERSION_INDEX = 2;
40     static final int KEYMASTER_SECURITY_LEVEL_INDEX = 3;
41     static final int ATTESTATION_CHALLENGE_INDEX = 4;
42     static final int UNIQUE_ID_INDEX = 5;
43     static final int SW_ENFORCED_INDEX = 6;
44     static final int TEE_ENFORCED_INDEX = 7;
45 
46     public static final int KM_SECURITY_LEVEL_SOFTWARE = 0;
47     public static final int KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT = 1;
48     public static final int KM_SECURITY_LEVEL_STRONG_BOX = 2;
49 
50     private final int attestationVersion;
51     private final int attestationSecurityLevel;
52     private final int keymasterVersion;
53     private final int keymasterSecurityLevel;
54     private final byte[] attestationChallenge;
55     private final byte[] uniqueId;
56     private final AuthorizationList softwareEnforced;
57     private final AuthorizationList teeEnforced;
58     private final Set<String> unexpectedExtensionOids;
59 
60 
61     /**
62      * Constructs an {@code Attestation} object from the provided {@link X509Certificate},
63      * extracting the attestation data from the attestation extension.
64      *
65      * @throws CertificateParsingException if the certificate does not contain a properly-formatted
66      *                                     attestation extension.
67      */
Attestation(X509Certificate x509Cert)68     public Attestation(X509Certificate x509Cert) throws CertificateParsingException {
69         ASN1Sequence seq = getAttestationSequence(x509Cert);
70         unexpectedExtensionOids = retrieveUnexpectedExtensionOids(x509Cert);
71 
72         attestationVersion = Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_VERSION_INDEX));
73         attestationSecurityLevel = Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX));
74         keymasterVersion = Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_VERSION_INDEX));
75         keymasterSecurityLevel = Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX));
76 
77         attestationChallenge =
78                 Asn1Utils.getByteArrayFromAsn1(seq.getObjectAt(Attestation.ATTESTATION_CHALLENGE_INDEX));
79 
80         uniqueId = Asn1Utils.getByteArrayFromAsn1(seq.getObjectAt(Attestation.UNIQUE_ID_INDEX));
81 
82         softwareEnforced = new AuthorizationList(seq.getObjectAt(SW_ENFORCED_INDEX));
83         teeEnforced = new AuthorizationList(seq.getObjectAt(TEE_ENFORCED_INDEX));
84     }
85 
securityLevelToString(int attestationSecurityLevel)86     public static String securityLevelToString(int attestationSecurityLevel) {
87         switch (attestationSecurityLevel) {
88             case KM_SECURITY_LEVEL_SOFTWARE:
89                 return "Software";
90             case KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT:
91                 return "TEE";
92             case KM_SECURITY_LEVEL_STRONG_BOX:
93                 return "StrongBox";
94             default:
95                 return "Unkown";
96         }
97     }
98 
getAttestationVersion()99     public int getAttestationVersion() {
100         return attestationVersion;
101     }
102 
getAttestationSecurityLevel()103     public int getAttestationSecurityLevel() {
104         return attestationSecurityLevel;
105     }
106 
getKeymasterVersion()107     public int getKeymasterVersion() {
108         return keymasterVersion;
109     }
110 
getKeymasterSecurityLevel()111     public int getKeymasterSecurityLevel() {
112         return keymasterSecurityLevel;
113     }
114 
getAttestationChallenge()115     public byte[] getAttestationChallenge() {
116         return attestationChallenge;
117     }
118 
getUniqueId()119     public byte[] getUniqueId() {
120         return uniqueId;
121     }
122 
getSoftwareEnforced()123     public AuthorizationList getSoftwareEnforced() {
124         return softwareEnforced;
125     }
126 
getTeeEnforced()127     public AuthorizationList getTeeEnforced() {
128         return teeEnforced;
129     }
130 
getUnexpectedExtensionOids()131     public Set<String> getUnexpectedExtensionOids() {
132         return unexpectedExtensionOids;
133     }
134 
135     @Override
toString()136     public String toString() {
137         StringBuilder s = new StringBuilder();
138         s.append("Attest version: " + attestationVersion);
139         s.append("\nAttest security: " + securityLevelToString(attestationSecurityLevel));
140         s.append("\nKM version: " + keymasterVersion);
141         s.append("\nKM security: " + securityLevelToString(keymasterSecurityLevel));
142 
143         s.append("\nChallenge");
144         String stringChallenge = new String(attestationChallenge);
145         if (CharMatcher.ascii().matchesAllOf(stringChallenge)) {
146             s.append(": [" + stringChallenge + "]");
147         } else {
148             s.append(" (base64): [" + BaseEncoding.base64().encode(attestationChallenge) + "]");
149         }
150         if (uniqueId != null) {
151             s.append("\nUnique ID (base64): [" + BaseEncoding.base64().encode(uniqueId) + "]");
152         }
153 
154         s.append("\n-- SW enforced --");
155         s.append(softwareEnforced);
156         s.append("\n-- TEE enforced --");
157         s.append(teeEnforced);
158 
159         return s.toString();
160     }
161 
getAttestationSequence(X509Certificate x509Cert)162     private ASN1Sequence getAttestationSequence(X509Certificate x509Cert)
163             throws CertificateParsingException {
164         byte[] attestationExtensionBytes = x509Cert.getExtensionValue(KEY_DESCRIPTION_OID);
165         if (attestationExtensionBytes == null || attestationExtensionBytes.length == 0) {
166             throw new CertificateParsingException(
167                     "Did not find extension with OID " + KEY_DESCRIPTION_OID);
168         }
169         return Asn1Utils.getAsn1SequenceFromBytes(attestationExtensionBytes);
170     }
171 
retrieveUnexpectedExtensionOids(X509Certificate x509Cert)172     private Set<String> retrieveUnexpectedExtensionOids(X509Certificate x509Cert) {
173         return new ImmutableSet.Builder<String>()
174                 .addAll(x509Cert.getCriticalExtensionOIDs()
175                         .stream()
176                         .filter(s -> !KEY_USAGE_OID.equals(s))
177                         .iterator())
178                 .addAll(x509Cert.getNonCriticalExtensionOIDs()
179                         .stream()
180                         .filter(s -> !KEY_DESCRIPTION_OID.equals(s))
181                         .iterator())
182                 .build();
183     }
184 }
185