Skip to content
This repository was archived by the owner on Nov 9, 2023. It is now read-only.

Commit 4ab101c

Browse files
committed
Adds most logic needed for validating cert chain
Notes: * This is completely backward compatible to what U2F does now. * The registration request can now have more than one X.509 certificate. The chain must be DER encoded (basically, the X.509 certs are DER encoded and then concatenated one after anoter. * As noted in many comments, the leaf is in the 0th element of the chain, followed by intermediary certs. * The current code does not yet ship with the final Android attestation root CA, so all Android attestations will have "chain validated: false"
1 parent 686e088 commit 4ab101c

File tree

16 files changed

+404
-192
lines changed

16 files changed

+404
-192
lines changed

u2f-gae-demo/src/com/google/u2f/gaedemo/storage/TokenStorageData.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@
1212
import com.google.u2f.server.data.SecurityKeyData;
1313
import com.google.u2f.server.data.SecurityKeyData.Transports;
1414
import com.google.u2f.server.impl.attestation.android.AndroidKeyStoreAttestation;
15+
import com.google.u2f.tools.X509Util;
1516

1617
import org.apache.commons.codec.binary.Hex;
1718

1819
import java.io.ByteArrayInputStream;
19-
import java.security.cert.CertificateEncodingException;
20+
import java.security.cert.Certificate;
2021
import java.security.cert.CertificateException;
2122
import java.security.cert.CertificateFactory;
2223
import java.security.cert.CertificateParsingException;
2324
import java.security.cert.X509Certificate;
2425
import java.util.Arrays;
26+
import java.util.Collection;
2527
import java.util.List;
2628
import java.util.Objects;
2729

@@ -40,11 +42,7 @@ public TokenStorageData(SecurityKeyData tokenData) {
4042
this.enrollmentTime = tokenData.getEnrollmentTime();
4143
this.keyHandle = tokenData.getKeyHandle();
4244
this.publicKey = tokenData.getPublicKey();
43-
try {
44-
this.attestationCert = tokenData.getAttestationCertificate().getEncoded();
45-
} catch (CertificateEncodingException e) {
46-
throw new RuntimeException();
47-
}
45+
this.attestationCert = X509Util.encodeCertArray(tokenData.getAttestationCertificateChain());
4846
this.transports = tokenData.getTransports();
4947
this.counter = tokenData.getCounter();
5048
}
@@ -54,22 +52,24 @@ public void updateCounter(int newCounterValue) {
5452
}
5553

5654
public SecurityKeyData getSecurityKeyData() {
57-
X509Certificate x509cert = parseCertificate(attestationCert);
58-
return new SecurityKeyData(enrollmentTime, transports, keyHandle, publicKey, x509cert, counter);
55+
X509Certificate[] x509certChain = parseCertificateChain(attestationCert);
56+
return new SecurityKeyData(
57+
enrollmentTime, transports, keyHandle, publicKey, x509certChain, counter);
5958
}
6059

6160
public JsonObject toJson() {
62-
X509Certificate x509cert = getSecurityKeyData().getAttestationCertificate();
61+
X509Certificate[] x509certChain = getSecurityKeyData().getAttestationCertificateChain();
6362
JsonObject json = new JsonObject();
6463
json.addProperty("enrollment_time", enrollmentTime);
6564
json.add("transports", getJsonTransports());
6665
json.addProperty("key_handle", Hex.encodeHexString(keyHandle));
6766
json.addProperty("public_key", Hex.encodeHexString(publicKey));
68-
json.addProperty("issuer", x509cert.getIssuerX500Principal().getName());
67+
json.addProperty("issuer", x509certChain[0].getIssuerX500Principal().getName());
6968

7069
try {
70+
// TODO(aczeskis): use the actual root ca cert when it's available
7171
AndroidKeyStoreAttestation androidKeyStoreAttestation =
72-
AndroidKeyStoreAttestation.Parse(x509cert);
72+
AndroidKeyStoreAttestation.Parse(x509certChain, null);
7373
if (androidKeyStoreAttestation != null) {
7474
json.add("android_attestation", androidKeyStoreAttestation.toJson());
7575
}
@@ -118,10 +118,12 @@ public boolean equals(Object obj) {
118118
&& Arrays.equals(this.attestationCert, that.attestationCert);
119119
}
120120

121-
private static X509Certificate parseCertificate(byte[] encodedDerCertificate) {
121+
private static X509Certificate[] parseCertificateChain(byte[] encodedDerCertificates) {
122122
try {
123-
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(
124-
new ByteArrayInputStream(encodedDerCertificate));
123+
Collection<? extends Certificate> certCollection =
124+
CertificateFactory.getInstance("X.509").generateCertificates(
125+
new ByteArrayInputStream(encodedDerCertificates));
126+
return certCollection.toArray(new X509Certificate[0]);
125127
} catch (CertificateException e) {
126128
throw new RuntimeException(e);
127129
}

u2f-gae-demo/src/soy/card.soy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<div class="androidAttestationLabel">Android Attestation <button class="toggleAttestationButton">show</button><br/></div>
2727
<div class="androidAttestation">
2828
<ul>
29-
<li><span class="label">Cert Chain Verified: </span><span class="chainVerified">false</span><br/>
29+
<li><span class="label">Cert Chain Verified: </span><span class="chainVerified"></span><br/>
3030
<li><span class="label">Keymaster Version: </span><span class="keymasterVersion">none</span><br/>
3131
<li><span class="label">Attestation Challenge: </span><span class="challenge">none</span><br/>
3232
<li><span class="label">Software Enforced: </span>

u2f-gae-demo/war/js/u2fdemo.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@ function tokenToDom(token) {
2828
}
2929
if (token.android_attestation) {
3030
card.querySelector('.androidAttestationLabel').style.display = "inline";
31+
32+
card.querySelector('.chainVerified').textContent = token.android_attestation.chain_validated;
33+
3134
card.querySelector('.keymasterVersion').textContent
3235
= token.android_attestation.keymaster_version;
3336
card.querySelector('.challenge').textContent
34-
= token.android_attestation.attestation_challenge;
37+
= "0x" + token.android_attestation.attestation_challenge;
3538

3639
if (token.android_attestation.software_encoded.algorithm) {
3740
card.querySelector('.softwareEnforced .algorithm').textContent

u2f-ref-code/java/src/com/google/u2f/codec/RawMessageCodec.java

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@
66

77
package com.google.u2f.codec;
88

9-
import java.io.ByteArrayInputStream;
10-
import java.io.DataInputStream;
11-
import java.io.IOException;
12-
import java.nio.ByteBuffer;
13-
import java.security.cert.CertificateEncodingException;
14-
import java.security.cert.CertificateException;
15-
import java.security.cert.CertificateFactory;
16-
import java.security.cert.X509Certificate;
17-
189
import com.google.u2f.U2FException;
1910
import com.google.u2f.key.messages.AuthenticateRequest;
2011
import com.google.u2f.key.messages.AuthenticateResponse;
2112
import com.google.u2f.key.messages.RegisterRequest;
2213
import com.google.u2f.key.messages.RegisterResponse;
14+
import com.google.u2f.tools.X509Util;
15+
16+
import org.bouncycastle.util.Arrays;
17+
18+
import java.io.ByteArrayInputStream;
19+
import java.io.DataInputStream;
20+
import java.io.IOException;
21+
import java.nio.ByteBuffer;
22+
import java.security.cert.X509Certificate;
2323

2424
/**
2525
* Raw message formats, as per FIDO U2F: Raw Message Formats - Draft 4
@@ -61,15 +61,11 @@ public static byte[] encodeRegisterResponse(RegisterResponse registerResponse)
6161
throws U2FException {
6262
byte[] userPublicKey = registerResponse.getUserPublicKey();
6363
byte[] keyHandle = registerResponse.getKeyHandle();
64-
X509Certificate attestationCertificate = registerResponse.getAttestationCertificate();
64+
X509Certificate[] attestationCertificateChain = registerResponse.getAttestationCertificateChain();
6565
byte[] signature = registerResponse.getSignature();
6666

6767
byte[] attestationCertificateBytes;
68-
try {
69-
attestationCertificateBytes = attestationCertificate.getEncoded();
70-
} catch (CertificateEncodingException e) {
71-
throw new U2FException("Error when encoding attestation certificate.", e);
72-
}
68+
attestationCertificateBytes = X509Util.encodeCertArray(attestationCertificateChain);
7369

7470
if (keyHandle.length > 255) {
7571
throw new U2FException("keyHandle length cannot be longer than 255 bytes!");
@@ -95,10 +91,14 @@ public static RegisterResponse decodeRegisterResponse(byte[] data) throws U2FExc
9591
inputStream.readFully(userPublicKey);
9692
byte[] keyHandle = new byte[inputStream.readUnsignedByte()];
9793
inputStream.readFully(keyHandle);
98-
X509Certificate attestationCertificate = (X509Certificate) CertificateFactory.getInstance(
99-
"X.509").generateCertificate(inputStream);
100-
byte[] signature = new byte[inputStream.available()];
101-
inputStream.readFully(signature);
94+
95+
// We don't know the length of the signature or cert chain, so we tread carefully
96+
byte[] certsAndsig = new byte[inputStream.available()];
97+
inputStream.readFully(certsAndsig);
98+
99+
X509Certificate[] attestationCertificateChain = X509Util.parseCertificateChain(certsAndsig);
100+
byte[] signature = Arrays.copyOfRange(certsAndsig,
101+
X509Util.encodeCertArray(attestationCertificateChain).length, certsAndsig.length);
102102

103103
if (inputStream.available() != 0) {
104104
throw new U2FException("Message ends with unexpected data");
@@ -110,11 +110,9 @@ public static RegisterResponse decodeRegisterResponse(byte[] data) throws U2FExc
110110
REGISTRATION_RESERVED_BYTE_VALUE, reservedByte));
111111
}
112112

113-
return new RegisterResponse(userPublicKey, keyHandle, attestationCertificate, signature);
113+
return new RegisterResponse(userPublicKey, keyHandle, attestationCertificateChain, signature);
114114
} catch (IOException e) {
115115
throw new U2FException("Error when parsing raw RegistrationResponse", e);
116-
} catch (CertificateException e) {
117-
throw new U2FException("Error when parsing attestation certificate", e);
118116
}
119117
}
120118

u2f-ref-code/java/src/com/google/u2f/key/impl/U2FKeyReferenceImpl.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@
66

77
package com.google.u2f.key.impl;
88

9-
import java.security.KeyPair;
10-
import java.security.PrivateKey;
11-
import java.security.cert.X509Certificate;
12-
import java.util.logging.Logger;
13-
14-
import org.apache.commons.codec.binary.Hex;
15-
169
import com.google.u2f.U2FException;
1710
import com.google.u2f.codec.RawMessageCodec;
1811
import com.google.u2f.key.Crypto;
@@ -26,21 +19,28 @@
2619
import com.google.u2f.key.messages.RegisterRequest;
2720
import com.google.u2f.key.messages.RegisterResponse;
2821

22+
import org.apache.commons.codec.binary.Hex;
23+
24+
import java.security.KeyPair;
25+
import java.security.PrivateKey;
26+
import java.security.cert.X509Certificate;
27+
import java.util.logging.Logger;
28+
2929
public class U2FKeyReferenceImpl implements U2FKey {
3030
private static final Logger Log = Logger.getLogger(U2FKeyReferenceImpl.class.getName());
3131

32-
private final X509Certificate vendorCertificate;
32+
private final X509Certificate[] vendorCertificateChain;
3333
private final PrivateKey certificatePrivateKey;
3434
private final KeyPairGenerator keyPairGenerator;
3535
private final KeyHandleGenerator keyHandleGenerator;
3636
private final DataStore dataStore;
3737
private final UserPresenceVerifier userPresenceVerifier;
3838
private final Crypto crypto;
3939

40-
public U2FKeyReferenceImpl(X509Certificate vendorCertificate, PrivateKey certificatePrivateKey,
40+
public U2FKeyReferenceImpl(X509Certificate[] vendorCertificateChain, PrivateKey certificatePrivateKey,
4141
KeyPairGenerator keyPairGenerator, KeyHandleGenerator keyHandleGenerator,
4242
DataStore dataStore, UserPresenceVerifier userPresenceVerifier, Crypto crypto) {
43-
this.vendorCertificate = vendorCertificate;
43+
this.vendorCertificateChain = vendorCertificateChain;
4444
this.certificatePrivateKey = certificatePrivateKey;
4545
this.keyPairGenerator = keyPairGenerator;
4646
this.keyHandleGenerator = keyHandleGenerator;
@@ -81,12 +81,12 @@ public RegisterResponse register(RegisterRequest registerRequest) throws U2FExce
8181
Log.info(" -- Outputs --");
8282
Log.info(" userPublicKey: " + Hex.encodeHexString(userPublicKey));
8383
Log.info(" keyHandle: " + Hex.encodeHexString(keyHandle));
84-
Log.info(" vendorCertificate: " + vendorCertificate);
84+
Log.info(" vendorCertificate: " + vendorCertificateChain);
8585
Log.info(" signature: " + Hex.encodeHexString(signature));
8686

8787
Log.info("<< register");
8888

89-
return new RegisterResponse(userPublicKey, keyHandle, vendorCertificate, signature);
89+
return new RegisterResponse(userPublicKey, keyHandle, vendorCertificateChain, signature);
9090
}
9191

9292
@Override

u2f-ref-code/java/src/com/google/u2f/key/messages/RegisterResponse.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
public class RegisterResponse extends U2FResponse {
1414
private final byte[] userPublicKey;
1515
private final byte[] keyHandle;
16-
private final X509Certificate attestationCertificate;
16+
private final X509Certificate[] attestationCertificateChain;
1717
private final byte[] signature;
1818

1919
public RegisterResponse(byte[] userPublicKey, byte[] keyHandle,
20-
X509Certificate attestationCertificate, byte[] signature) {
20+
X509Certificate[] attestationCertificateChain, byte[] signature) {
2121
super();
2222
this.userPublicKey = userPublicKey;
2323
this.keyHandle = keyHandle;
24-
this.attestationCertificate = attestationCertificate;
24+
this.attestationCertificateChain = attestationCertificateChain;
2525
this.signature = signature;
2626
}
2727

@@ -43,10 +43,10 @@ public byte[] getKeyHandle() {
4343
}
4444

4545
/**
46-
* This is a X.509 certificate.
46+
* This is a X.509 certificate chain.
4747
*/
48-
public X509Certificate getAttestationCertificate() {
49-
return attestationCertificate;
48+
public X509Certificate[] getAttestationCertificateChain() {
49+
return attestationCertificateChain;
5050
}
5151

5252
/** This is a ECDSA signature (on P-256) */
@@ -56,7 +56,7 @@ public byte[] getSignature() {
5656

5757
@Override
5858
public int hashCode() {
59-
return Objects.hash(userPublicKey, keyHandle, attestationCertificate, signature);
59+
return Objects.hash(userPublicKey, keyHandle, attestationCertificateChain, signature);
6060
}
6161

6262
@Override
@@ -71,6 +71,6 @@ public boolean equals(Object obj) {
7171
return Arrays.equals(userPublicKey, other.userPublicKey)
7272
&& Arrays.equals(keyHandle, other.keyHandle)
7373
&& Arrays.equals(signature, other.signature)
74-
&& Objects.equals(attestationCertificate, other.attestationCertificate);
74+
&& Objects.equals(attestationCertificateChain, other.attestationCertificateChain);
7575
}
7676
}

u2f-ref-code/java/src/com/google/u2f/server/data/SecurityKeyData.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ public String toString() {
3636
private final List<Transports> transports;
3737
private final byte[] keyHandle;
3838
private final byte[] publicKey;
39-
private final X509Certificate attestationCert;
39+
private final X509Certificate[] attestationCertChain;
4040
private int counter;
4141

4242
public SecurityKeyData(
4343
long enrollmentTime,
4444
byte[] keyHandle,
4545
byte[] publicKey,
46-
X509Certificate attestationCert,
46+
X509Certificate[] attestationCert,
4747
int counter) {
4848
this(enrollmentTime, null /* transports */, keyHandle, publicKey, attestationCert, counter);
4949
}
@@ -53,13 +53,13 @@ public SecurityKeyData(
5353
List<Transports> transports,
5454
byte[] keyHandle,
5555
byte[] publicKey,
56-
X509Certificate attestationCert,
56+
X509Certificate[] attestationCertChain,
5757
int counter) {
5858
this.enrollmentTime = enrollmentTime;
5959
this.transports = transports;
6060
this.keyHandle = keyHandle;
6161
this.publicKey = publicKey;
62-
this.attestationCert = attestationCert;
62+
this.attestationCertChain = attestationCertChain;
6363
this.counter = counter;
6464
}
6565

@@ -82,8 +82,8 @@ public byte[] getPublicKey() {
8282
return publicKey;
8383
}
8484

85-
public X509Certificate getAttestationCertificate() {
86-
return attestationCert;
85+
public X509Certificate[] getAttestationCertificateChain() {
86+
return attestationCertChain;
8787
}
8888

8989
public int getCounter() {
@@ -101,7 +101,7 @@ public int hashCode() {
101101
transports,
102102
keyHandle,
103103
publicKey,
104-
attestationCert,
104+
attestationCertChain,
105105
counter);
106106
}
107107

@@ -115,7 +115,7 @@ public boolean equals(Object obj) {
115115
&& (this.enrollmentTime == that.enrollmentTime)
116116
&& containSameTransports(this.transports, that.transports)
117117
&& Arrays.equals(this.publicKey, that.publicKey)
118-
&& Objects.equals(this.attestationCert, that.attestationCert)
118+
&& Arrays.equals(this.attestationCertChain, that.attestationCertChain)
119119
&& Objects.equals(counter, counter);
120120
}
121121

@@ -149,7 +149,8 @@ public String toString() {
149149
.append(counter)
150150
.append("\n")
151151
.append("attestation certificate:\n")
152-
.append(attestationCert.toString())
152+
.append(attestationCertChain.toString())
153+
.append("\n")
153154
.append("transports: ")
154155
.append(transports)
155156
.append("\n")

0 commit comments

Comments
 (0)