Skip to content

Commit 78054d2

Browse files
committed
Adds parsing for android keymaster attestations
Note that this is the NON-FINAL attestation format.
1 parent ebee331 commit 78054d2

6 files changed

Lines changed: 568 additions & 0 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.google.u2f.server.impl.androidattestation;
2+
3+
import java.security.cert.CertificateParsingException;
4+
5+
/**
6+
* Keysmaster algorithm values as taken from: keymaster_defs.h
7+
*/
8+
public enum Algorithm {
9+
/* Asymmetric algorithms. */
10+
KM_ALGORITHM_RSA(1, "rsa"),
11+
KM_ALGORITHM_EC(3, "ec"),
12+
13+
/* Block ciphers algorithms */
14+
KM_ALGORITHM_AES(32, "aes"),
15+
16+
/* MAC algorithms */
17+
KM_ALGORITHM_HMAC(128, "hmac");
18+
19+
private final int value;
20+
private final String description;
21+
22+
public static Algorithm fromValue(int value) throws CertificateParsingException {
23+
for (Algorithm algorithm : Algorithm.values()) {
24+
if (algorithm.getValue() == value) {
25+
return algorithm;
26+
}
27+
}
28+
29+
throw new CertificateParsingException("Invalid algorithm value: " + value);
30+
}
31+
32+
private Algorithm(int value, String description) {
33+
this.value = value;
34+
this.description = description;
35+
}
36+
37+
public int getValue() {
38+
return value;
39+
}
40+
41+
@Override
42+
public String toString() {
43+
return description;
44+
}
45+
}
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
package com.google.u2f.server.impl.androidattestation;
2+
3+
import org.bouncycastle.asn1.ASN1Encodable;
4+
import org.bouncycastle.asn1.ASN1InputStream;
5+
import org.bouncycastle.asn1.ASN1Integer;
6+
import org.bouncycastle.asn1.ASN1Object;
7+
import org.bouncycastle.asn1.ASN1Primitive;
8+
import org.bouncycastle.asn1.ASN1Sequence;
9+
import org.bouncycastle.asn1.DEROctetString;
10+
import org.bouncycastle.asn1.DERSet;
11+
import org.bouncycastle.asn1.DERTaggedObject;
12+
import org.bouncycastle.asn1.DLSequence;
13+
14+
import java.io.IOException;
15+
import java.math.BigInteger;
16+
import java.security.cert.CertificateParsingException;
17+
import java.security.cert.X509Certificate;
18+
import java.util.ArrayList;
19+
import java.util.HashMap;
20+
import java.util.List;
21+
22+
/**
23+
* Parses and contains an Android KeyStore attestation.
24+
*/
25+
public class AndroidKeyStoreAttestation {
26+
private static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
27+
28+
// Indexes for data in KeyDescription sequence
29+
private static final int DESCRIPTION_LENGTH_MIN = 4;
30+
private static final int DESCRIPTION_LENGTH_MAX = 5;
31+
private static final int DESCRIPTION_VERSION_INDEX = 0;
32+
private static final int DESCRIPTION_CHALLENGE_INDEX = 1;
33+
private static final int DESCRIPTION_SOFTWARE_ENFORCED_INDEX = 2;
34+
private static final int DESCRIPTION_TEE_ENFORCED_INDEX = 3;
35+
private static final int DESCRIPTION_UNIQUE_INDEX = 4;
36+
37+
// Don't expect more than 32 bits for any INTEGER
38+
private static final int MAX_INTEGER_BITS = 32;
39+
40+
// Tags for Authorization List
41+
private static final int AUTHZ_PURPOSE_TAG = 1;
42+
private static final int AUTHZ_ALGORITHM_TAG = 2;
43+
44+
private final Integer keymasterVersion;
45+
private final byte[] attestationChallenge;
46+
private final AuthorizationList softwareAuthorizationList;
47+
private final byte[] uniqueId;
48+
49+
private AndroidKeyStoreAttestation(Integer keymasterVersion, byte[] attestationChallenge,
50+
AuthorizationList softwareAuthorizationList, byte[] uniqueId) {
51+
this.keymasterVersion = keymasterVersion;
52+
this.attestationChallenge = attestationChallenge;
53+
this.softwareAuthorizationList = softwareAuthorizationList;
54+
this.uniqueId = uniqueId;
55+
}
56+
57+
/**
58+
* Parses the key description extension. Expected format is:
59+
* KeyDescription ::= SEQUENCE {
60+
* keymasterVersion INTEGER,
61+
* attestationChallenge OCTET_STRING,
62+
* softwareEnforced AuthorizationList,
63+
* teeEnforced AuthorizationList,
64+
* uniqueId OCTET_STRING OPTIONAL,
65+
* }
66+
*
67+
* AuthorizationList ::= SEQUENCE {
68+
* -- See keymaster_purpose_t for purpose values.
69+
* purpose [1] IMPLICIT SET OF INTEGER OPTIONAL,
70+
* -- See keymaster_algorithm_t for algorithm values.
71+
* algorithm [2] IMPLICIT INTEGER OPTIONAL,
72+
* -- keySize is measured in bits, not bytes, and the value must be
73+
* -- positive and less than than 2^32, though realistic values are
74+
* -- much smaller.
75+
* keySize [3] IMPLICIT INTEGER OPTIONAL,
76+
* -- See keymaster_block_mode_t for blockMode values.
77+
* blockMode [4] IMPLICIT SET OF INTEGER OPTIONAL,
78+
* -- See keymaster_digest_t for digest values.
79+
* digest [5] IMPLICIT SET OF INTEGER OPTIONAL,
80+
* -- See keymaster_padding_t for padding values.
81+
* padding [6] IMPLICIT SET OF INTEGER OPTIONAL,
82+
* callerNonce [7] IMPLICIT NULL OPTIONAL,
83+
* -- minMacLength values must be positive and less than 2^32.
84+
* minMacLength [8] IMPLICIT INTEGER OPTIONAL,
85+
* -- See keymaster_kdf_t for kdf values.
86+
* kdf [9] IMPLICIT SEQUENCE OF INTEGER OPTIONAL,
87+
* -- See keymaster_ec_curve_t for ecCurve values
88+
* ecCurve [10] IMPLICIT INTEGER OPTIONAL,
89+
* -- rsaPublicExponent must be a valid RSA public exponent less
90+
* -- than 2^64.
91+
* rsaPublicExponent [200] IMPLICIT INTEGER OPTIONAL,
92+
* eciesSingleHashMode [201] IMPLICIT NULL OPTIONAL,
93+
* includeUniqueId [202] IMPLICIT NULL OPTIONAL,
94+
* -- See keymaster_key_blob_usage_requirements for
95+
* -- blobUsageRequirement values.
96+
* blobUsageRequirement [301] IMPLICIT INTEGER OPTIONAL,
97+
* bootloaderOnly [302] IMPLICIT NULL OPTIONAL,
98+
* -- activeDateTime must be a 64-bit Java date/time value.
99+
* activeDateTime [400] IMPLICIT INTEGER OPTIONAL
100+
* -- originationExpireDateTime must be a 64-bit Java date/time
101+
* -- value.
102+
* originationExpireDateTime [401] IMPLICIT INTEGER OPTIONAL
103+
* -- usageExpireDateTime must be a 64-bit Java date/time value.
104+
* usageExpireDateTime [402] IMPLICIT INTEGER OPTIONAL
105+
* -- minSecondsBetweenOps must be non-negative and less than 2^32.
106+
* minSecondsBetweenOps [403] IMPLICIT INTEGER OPTIONAL,
107+
* -- maxUsesPerBoot must be positive and less than 2^32.
108+
* maxUsesPerBoot [404] IMPLICIT INTEGER OPTIONAL,
109+
* noAuthRequired [503] IMPLICIT NULL OPTIONAL,
110+
* -- See hw_authenticator_type_t for userAuthType values. Note
111+
* -- this field is a bitmask; multiple authenticator types may be
112+
* -- ORed together.
113+
* userAuthType [504] IMPLICIT INTEGER OPTIONAL,
114+
* -- authTimeout, if present, must be positive and less than 2^32.
115+
* authTimeout [505] IMPLICIT INTEGER OPTIONAL,
116+
* allApplications [600] IMPLICIT NULL OPTIONAL,
117+
* applicationId [601] IMPLICIT OCTET_STRING OPTIONAL,
118+
* applicationData [700] IMPLICIT OCTET_STRING OPTIONAL,
119+
* -- creationDateTime must be a 64-bit Java date/time value.
120+
* creationDateTime [701] IMPLICIT INTEGER OPTIONAL,
121+
* -- See keymaster_origin_t for origin values.
122+
* origin [702] IMPLICIT INTEGER OPTIONAL,
123+
* rollbackResistant [703] IMPLICIT NULL OPTIONAL,
124+
* -- rootOfTrust is included only if bootloader is not locked.
125+
* rootOfTrust [704] IMPLICIT RootOfTrust OPTIONAL
126+
* osVersion [705] IMPLICIT INTEGER OPTIONAL,
127+
* patchLevel [706] IMPLICIT INTEGER OPTIONAL,
128+
* uniqueId [707] IMPLICIT NULL OPTIONAL,
129+
* }
130+
*
131+
* RootOfTrust ::= SEQUENCE {
132+
* verifiedBootKey OCTET_STRING,
133+
* osVersion INTEGER,
134+
* patchMonthYear INTEGER,
135+
* }
136+
*/
137+
public static AndroidKeyStoreAttestation Parse(X509Certificate cert)
138+
throws CertificateParsingException {
139+
// Extract the extension from the certificate
140+
byte[] extensionValue = extractExtensionValue(cert);
141+
142+
// Get the KeyDescription sequence
143+
DLSequence keyDescriptionSequence = getKeyDescriptionSequence(extensionValue);
144+
145+
// Extract version
146+
Integer keymasterVersion = getKeymasterVersion(keyDescriptionSequence);
147+
148+
// Extract challenge
149+
byte[] challenge = getAttestationChallenge(keyDescriptionSequence);
150+
151+
// Extract the software authorization list
152+
DLSequence softwareEnforcedSequence = getSoftwareEncodedSequence(keyDescriptionSequence);
153+
AuthorizationList softwareAuthorizationList =
154+
extractAuthorizationList(softwareEnforcedSequence);
155+
156+
// Get the unique id
157+
byte[] uniqueId = null;
158+
if (keyDescriptionSequence.size() == DESCRIPTION_LENGTH_MAX) {
159+
uniqueId = getUniqueId(keyDescriptionSequence);
160+
}
161+
162+
return new AndroidKeyStoreAttestation(
163+
keymasterVersion, challenge, softwareAuthorizationList, uniqueId);
164+
}
165+
166+
/**
167+
* @return parsed keymaster version
168+
*/
169+
public Integer getKeyMasterVersion() {
170+
return keymasterVersion;
171+
}
172+
173+
/**
174+
* @return parsed software authorization list
175+
*/
176+
public AuthorizationList getSoftwareAuthorizationList() {
177+
return softwareAuthorizationList;
178+
}
179+
180+
/**
181+
* @return the parsed unique id or {@code null} if none was included
182+
*/
183+
public byte[] getUniqueId() {
184+
return uniqueId;
185+
}
186+
187+
/**
188+
* @return the parsed attestation challenge
189+
*/
190+
public byte[] getAttestationChallenge() {
191+
return attestationChallenge;
192+
}
193+
194+
private static byte[] extractExtensionValue(X509Certificate cert)
195+
throws CertificateParsingException {
196+
byte[] extensionValue = cert.getExtensionValue(KEY_DESCRIPTION_OID);
197+
198+
if (extensionValue == null || extensionValue.length == 0) {
199+
throw new CertificateParsingException(
200+
"Did not find KeyDescription extension with OID " + KEY_DESCRIPTION_OID);
201+
}
202+
203+
return extensionValue;
204+
}
205+
206+
private static DLSequence getKeyDescriptionSequence(byte[] extensionValue)
207+
throws CertificateParsingException {
208+
ASN1InputStream ais = new ASN1InputStream(extensionValue);
209+
ASN1Object asn1Object;
210+
211+
// Read the key description octet string
212+
try {
213+
asn1Object = ais.readObject();
214+
ais.close();
215+
} catch (IOException e) {
216+
throw new CertificateParsingException("Not able to read KeyDescription ASN.1 object", e);
217+
}
218+
if (asn1Object == null || !(asn1Object instanceof DEROctetString)) {
219+
throw new CertificateParsingException("Expected KeyDescription Octet String.");
220+
}
221+
DEROctetString octet = (DEROctetString) asn1Object;
222+
223+
// Read out the Sequence
224+
ais = new ASN1InputStream(octet.getOctets());
225+
try {
226+
asn1Object = ais.readObject();
227+
ais.close();
228+
} catch (IOException e) {
229+
throw new CertificateParsingException("Not able to read KeyDescription Octet String.", e);
230+
}
231+
if (asn1Object == null || !(asn1Object instanceof DLSequence)) {
232+
throw new CertificateParsingException("Expected KeyDescription Sequence.");
233+
}
234+
DLSequence sequence = (DLSequence) asn1Object;
235+
236+
if (sequence.size() < DESCRIPTION_LENGTH_MIN || sequence.size() > DESCRIPTION_LENGTH_MAX) {
237+
throw new CertificateParsingException("KeyDescription Sequence has " + sequence.size()
238+
+ " elements. Expected length between " + DESCRIPTION_LENGTH_MIN + " and "
239+
+ DESCRIPTION_LENGTH_MAX);
240+
}
241+
242+
return sequence;
243+
}
244+
245+
private static DLSequence getSoftwareEncodedSequence(DLSequence keyDescriptionSequence)
246+
throws CertificateParsingException {
247+
ASN1Encodable asn1Encodable =
248+
keyDescriptionSequence.getObjectAt(DESCRIPTION_SOFTWARE_ENFORCED_INDEX);
249+
if (asn1Encodable == null || !(asn1Encodable instanceof DLSequence)) {
250+
throw new CertificateParsingException("Expected softwareEnforced DLSequence.");
251+
}
252+
return (DLSequence) asn1Encodable;
253+
}
254+
255+
private static int getIntFromAsn1Encodable(ASN1Encodable asn1Encodable)
256+
throws CertificateParsingException {
257+
if (asn1Encodable == null || !(asn1Encodable instanceof ASN1Integer)) {
258+
throw new CertificateParsingException("Expected INTEGER type.");
259+
}
260+
ASN1Integer asn1Integer = (ASN1Integer) asn1Encodable;
261+
BigInteger bigInt = asn1Integer.getPositiveValue();
262+
if (bigInt.bitLength() > MAX_INTEGER_BITS) {
263+
throw new CertificateParsingException("INTEGER too big");
264+
}
265+
return bigInt.intValue();
266+
}
267+
268+
private static byte[] getByteArrayFromAsn1Encodable(ASN1Encodable asn1Encodable)
269+
throws CertificateParsingException {
270+
if (asn1Encodable == null || !(asn1Encodable instanceof DEROctetString)) {
271+
throw new CertificateParsingException("Expected DEROctetString");
272+
}
273+
DEROctetString derOctectString = (DEROctetString) asn1Encodable;
274+
return derOctectString.getOctets();
275+
}
276+
277+
private static int checkValidTag(int tag) {
278+
// TODO(aczeskis): implement
279+
return tag;
280+
}
281+
282+
private static HashMap<Integer, ASN1Primitive> extractTaggedObjects(DLSequence dlSequence)
283+
throws CertificateParsingException {
284+
HashMap<Integer, ASN1Primitive> taggedObjects = new HashMap<Integer, ASN1Primitive>();
285+
286+
for (ASN1Encodable asn1EncodablePurpose : dlSequence.toArray()) {
287+
if (asn1EncodablePurpose == null || !(asn1EncodablePurpose instanceof DERTaggedObject)) {
288+
throw new CertificateParsingException("Expected DERTagged object");
289+
}
290+
DERTaggedObject derTaggedObject = (DERTaggedObject) asn1EncodablePurpose;
291+
taggedObjects.put(
292+
Integer.valueOf(checkValidTag(derTaggedObject.getTagNo())), derTaggedObject.getObject());
293+
}
294+
295+
return taggedObjects;
296+
}
297+
298+
private static int getKeymasterVersion(DLSequence keyDescriptionSequence)
299+
throws CertificateParsingException {
300+
ASN1Encodable asn1Encodable = keyDescriptionSequence.getObjectAt(DESCRIPTION_VERSION_INDEX);
301+
return getIntFromAsn1Encodable(asn1Encodable);
302+
}
303+
304+
private static byte[] getAttestationChallenge(DLSequence keyDescriptionSequence)
305+
throws CertificateParsingException {
306+
ASN1Encodable asn1Encodable = keyDescriptionSequence.getObjectAt(DESCRIPTION_CHALLENGE_INDEX);
307+
return getByteArrayFromAsn1Encodable(asn1Encodable);
308+
}
309+
310+
private static byte[] getUniqueId(DLSequence keyDescriptionSequence)
311+
throws CertificateParsingException {
312+
ASN1Encodable asn1Encodable = keyDescriptionSequence.getObjectAt(DESCRIPTION_UNIQUE_INDEX);
313+
return getByteArrayFromAsn1Encodable(asn1Encodable);
314+
}
315+
316+
private static List<Purpose> getPurpose(
317+
HashMap<Integer, ASN1Primitive> softwareEnforcedTaggedObjects)
318+
throws CertificateParsingException {
319+
ASN1Primitive asn1Primitive = softwareEnforcedTaggedObjects.get(AUTHZ_PURPOSE_TAG);
320+
if (!(asn1Primitive instanceof DERSet)) {
321+
throw new CertificateParsingException("Expected DERSet");
322+
}
323+
324+
DERSet set = (DERSet) asn1Primitive;
325+
List<Purpose> purpose = new ArrayList<Purpose>();
326+
for (ASN1Encodable asn1Encodable : set.toArray()) {
327+
purpose.add(Purpose.fromValue(getIntFromAsn1Encodable(asn1Encodable)));
328+
}
329+
330+
return purpose;
331+
}
332+
333+
private static Algorithm getAlgorithm(
334+
HashMap<Integer, ASN1Primitive> softwareEnforcedTaggedObjects)
335+
throws CertificateParsingException {
336+
ASN1Primitive asn1Primitive = softwareEnforcedTaggedObjects.get(AUTHZ_ALGORITHM_TAG);
337+
return Algorithm.fromValue(getIntFromAsn1Encodable(asn1Primitive));
338+
}
339+
340+
private static AuthorizationList extractAuthorizationList(DLSequence authorizationSequence)
341+
throws CertificateParsingException {
342+
HashMap<Integer, ASN1Primitive> softwareEnforcedTaggedObjects =
343+
extractTaggedObjects(authorizationSequence);
344+
345+
return new AuthorizationList.Builder()
346+
.setPurpose(getPurpose(softwareEnforcedTaggedObjects))
347+
.setAlgorithm(getAlgorithm(softwareEnforcedTaggedObjects))
348+
.build();
349+
}
350+
}

0 commit comments

Comments
 (0)