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

Commit 53dc623

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 53dc623

File tree

18 files changed

+518
-239
lines changed

18 files changed

+518
-239
lines changed

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

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@
1111
import com.google.gson.JsonPrimitive;
1212
import com.google.u2f.server.data.SecurityKeyData;
1313
import com.google.u2f.server.data.SecurityKeyData.Transports;
14+
import com.google.u2f.server.impl.CaCerts;
1415
import com.google.u2f.server.impl.attestation.android.AndroidKeyStoreAttestation;
16+
import com.google.u2f.tools.X509Util;
1517

1618
import org.apache.commons.codec.binary.Hex;
1719

18-
import java.io.ByteArrayInputStream;
19-
import java.security.cert.CertificateEncodingException;
20-
import java.security.cert.CertificateException;
21-
import java.security.cert.CertificateFactory;
2220
import java.security.cert.CertificateParsingException;
2321
import java.security.cert.X509Certificate;
2422
import java.util.Arrays;
@@ -30,7 +28,7 @@ public class TokenStorageData {
3028
private List<Transports> transports;
3129
private byte[] keyHandle;
3230
private byte[] publicKey;
33-
private byte[] attestationCert;
31+
private byte[] attestationCertChain;
3432
private int counter;
3533

3634
// used by the storage layer
@@ -40,11 +38,8 @@ public TokenStorageData(SecurityKeyData tokenData) {
4038
this.enrollmentTime = tokenData.getEnrollmentTime();
4139
this.keyHandle = tokenData.getKeyHandle();
4240
this.publicKey = tokenData.getPublicKey();
43-
try {
44-
this.attestationCert = tokenData.getAttestationCertificate().getEncoded();
45-
} catch (CertificateEncodingException e) {
46-
throw new RuntimeException();
47-
}
41+
this.attestationCertChain =
42+
X509Util.encodeCertArray(tokenData.getAttestationCertificateChain());
4843
this.transports = tokenData.getTransports();
4944
this.counter = tokenData.getCounter();
5045
}
@@ -54,22 +49,30 @@ public void updateCounter(int newCounterValue) {
5449
}
5550

5651
public SecurityKeyData getSecurityKeyData() {
57-
X509Certificate x509cert = parseCertificate(attestationCert);
58-
return new SecurityKeyData(enrollmentTime, transports, keyHandle, publicKey, x509cert, counter);
52+
X509Certificate[] x509certChain = null;
53+
if (attestationCertChain != null) {
54+
x509certChain = X509Util.parseCertificateChain(attestationCertChain);
55+
}
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+
// We currently use the temporary Android software CA root. This root is
71+
// TEMPORARY until a final root is chosen.
72+
// TODO(aczeskis): use the actual root ca cert when it's available
7173
AndroidKeyStoreAttestation androidKeyStoreAttestation =
72-
AndroidKeyStoreAttestation.Parse(x509cert);
74+
AndroidKeyStoreAttestation.Parse(x509certChain,
75+
X509Util.parseCertificateChain(CaCerts.TEMPORARY_ANDROID_EC_ROOT));
7376
if (androidKeyStoreAttestation != null) {
7477
json.add("android_attestation", androidKeyStoreAttestation.toJson());
7578
}
@@ -103,7 +106,8 @@ public String toString() {
103106

104107
@Override
105108
public int hashCode() {
106-
return Objects.hash(enrollmentTime, transports, keyHandle, publicKey, attestationCert, counter);
109+
return Objects.hash(
110+
enrollmentTime, transports, keyHandle, publicKey, attestationCertChain, counter);
107111
}
108112

109113
@Override
@@ -115,15 +119,6 @@ public boolean equals(Object obj) {
115119
&& SecurityKeyData.containSameTransports(this.transports, that.transports)
116120
&& (this.counter == that.counter) && Arrays.equals(this.keyHandle, that.keyHandle)
117121
&& Arrays.equals(this.publicKey, that.publicKey)
118-
&& Arrays.equals(this.attestationCert, that.attestationCert);
119-
}
120-
121-
private static X509Certificate parseCertificate(byte[] encodedDerCertificate) {
122-
try {
123-
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(
124-
new ByteArrayInputStream(encodedDerCertificate));
125-
} catch (CertificateException e) {
126-
throw new RuntimeException(e);
127-
}
122+
&& Arrays.equals(this.attestationCertChain, that.attestationCertChain);
128123
}
129124
}

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

Lines changed: 8 additions & 2 deletions
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>
@@ -39,7 +39,13 @@
3939
</ul>
4040
</div>
4141
<li><span class="label">TEE Enforced: </span>
42-
<span class="teeEnforced"></span>
42+
<div class="teeEnforced">
43+
<ul>
44+
<li><span class="label">Purpose: </span> <span class="purpose">none</span>
45+
<li><span class="label">Algorithm: </span> <span class="algorithm">none</span>
46+
<li><span class="label">Key Size: </span> <span class="keysize">none</span>
47+
<li><span class="label">Block Mode: </span> <span class="blockmode">none</span>
48+
</ul>
4349
</ul>
4450
</div>
4551
<div class="cardLabel">key handle</div>

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,55 @@ 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

39+
var showSoftwareEnforced = false;
3640
if (token.android_attestation.software_encoded.algorithm) {
3741
card.querySelector('.softwareEnforced .algorithm').textContent
3842
= token.android_attestation.software_encoded.algorithm;
43+
showSoftwareEnforced = true;
3944
}
4045
if (token.android_attestation.software_encoded.purpose) {
4146
card.querySelector('.softwareEnforced .purpose').textContent
4247
= token.android_attestation.software_encoded.purpose.join(', ');
48+
showSoftwareEnforced = true;
4349
}
4450
if (token.android_attestation.software_encoded.keysize) {
4551
card.querySelector('.softwareEnforced .keysize').textContent
4652
= token.android_attestation.software_encoded.keysize;
53+
showSoftwareEnforced = true;
4754
}
4855
if (token.android_attestation.software_encoded.blockmode) {
4956
card.querySelector('.softwareEnforced .blockmode').textContent
5057
= token.android_attestation.software_encoded.blockmode.join(', ');
58+
showSoftwareEnforced = true;
59+
}
60+
if (!showSoftwareEnforced) {
61+
card.querySelector('.softwareEnforced').style.display = "none";
5162
}
5263

53-
card.querySelector('.teeEnforced').textContent
54-
= JSON.stringify(token.android_attestation.tee_encoded, null, 2);
64+
if (token.android_attestation.tee_encoded.algorithm) {
65+
card.querySelector('.teeEnforced .algorithm').textContent
66+
= token.android_attestation.tee_encoded.algorithm;
67+
}
68+
if (token.android_attestation.tee_encoded.purpose) {
69+
card.querySelector('.teeEnforced .purpose').textContent
70+
= token.android_attestation.tee_encoded.purpose.join(', ');
71+
}
72+
if (token.android_attestation.tee_encoded.keysize) {
73+
card.querySelector('.teeEnforced .keysize').textContent
74+
= token.android_attestation.tee_encoded.keysize;
75+
}
76+
if (token.android_attestation.tee_encoded.blockmode) {
77+
card.querySelector('.teeEnforced .blockmode').textContent
78+
= token.android_attestation.tee_encoded.blockmode.join(', ');
79+
}
5580
}
5681
card.querySelector('.keyHandle').textContent = token.key_handle;
5782
card.querySelector('.publicKey').textContent = token.public_key;

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
}

0 commit comments

Comments
 (0)