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

Commit

Permalink
Adds most logic needed for validating cert chain
Browse files Browse the repository at this point in the history
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"
  • Loading branch information
leshi committed Mar 1, 2016
1 parent 686e088 commit 53dc623
Show file tree
Hide file tree
Showing 18 changed files with 518 additions and 239 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@
import com.google.gson.JsonPrimitive;
import com.google.u2f.server.data.SecurityKeyData;
import com.google.u2f.server.data.SecurityKeyData.Transports;
import com.google.u2f.server.impl.CaCerts;
import com.google.u2f.server.impl.attestation.android.AndroidKeyStoreAttestation;
import com.google.u2f.tools.X509Util;

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

import java.io.ByteArrayInputStream;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
Expand All @@ -30,7 +28,7 @@ public class TokenStorageData {
private List<Transports> transports;
private byte[] keyHandle;
private byte[] publicKey;
private byte[] attestationCert;
private byte[] attestationCertChain;
private int counter;

// used by the storage layer
Expand All @@ -40,11 +38,8 @@ public TokenStorageData(SecurityKeyData tokenData) {
this.enrollmentTime = tokenData.getEnrollmentTime();
this.keyHandle = tokenData.getKeyHandle();
this.publicKey = tokenData.getPublicKey();
try {
this.attestationCert = tokenData.getAttestationCertificate().getEncoded();
} catch (CertificateEncodingException e) {
throw new RuntimeException();
}
this.attestationCertChain =
X509Util.encodeCertArray(tokenData.getAttestationCertificateChain());
this.transports = tokenData.getTransports();
this.counter = tokenData.getCounter();
}
Expand All @@ -54,22 +49,30 @@ public void updateCounter(int newCounterValue) {
}

public SecurityKeyData getSecurityKeyData() {
X509Certificate x509cert = parseCertificate(attestationCert);
return new SecurityKeyData(enrollmentTime, transports, keyHandle, publicKey, x509cert, counter);
X509Certificate[] x509certChain = null;
if (attestationCertChain != null) {
x509certChain = X509Util.parseCertificateChain(attestationCertChain);
}
return new SecurityKeyData(
enrollmentTime, transports, keyHandle, publicKey, x509certChain, counter);
}

public JsonObject toJson() {
X509Certificate x509cert = getSecurityKeyData().getAttestationCertificate();
X509Certificate[] x509certChain = getSecurityKeyData().getAttestationCertificateChain();
JsonObject json = new JsonObject();
json.addProperty("enrollment_time", enrollmentTime);
json.add("transports", getJsonTransports());
json.addProperty("key_handle", Hex.encodeHexString(keyHandle));
json.addProperty("public_key", Hex.encodeHexString(publicKey));
json.addProperty("issuer", x509cert.getIssuerX500Principal().getName());
json.addProperty("issuer", x509certChain[0].getIssuerX500Principal().getName());

try {
// We currently use the temporary Android software CA root. This root is
// TEMPORARY until a final root is chosen.
// TODO(aczeskis): use the actual root ca cert when it's available
AndroidKeyStoreAttestation androidKeyStoreAttestation =
AndroidKeyStoreAttestation.Parse(x509cert);
AndroidKeyStoreAttestation.Parse(x509certChain,
X509Util.parseCertificateChain(CaCerts.TEMPORARY_ANDROID_EC_ROOT));
if (androidKeyStoreAttestation != null) {
json.add("android_attestation", androidKeyStoreAttestation.toJson());
}
Expand Down Expand Up @@ -103,7 +106,8 @@ public String toString() {

@Override
public int hashCode() {
return Objects.hash(enrollmentTime, transports, keyHandle, publicKey, attestationCert, counter);
return Objects.hash(
enrollmentTime, transports, keyHandle, publicKey, attestationCertChain, counter);
}

@Override
Expand All @@ -115,15 +119,6 @@ public boolean equals(Object obj) {
&& SecurityKeyData.containSameTransports(this.transports, that.transports)
&& (this.counter == that.counter) && Arrays.equals(this.keyHandle, that.keyHandle)
&& Arrays.equals(this.publicKey, that.publicKey)
&& Arrays.equals(this.attestationCert, that.attestationCert);
}

private static X509Certificate parseCertificate(byte[] encodedDerCertificate) {
try {
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(
new ByteArrayInputStream(encodedDerCertificate));
} catch (CertificateException e) {
throw new RuntimeException(e);
}
&& Arrays.equals(this.attestationCertChain, that.attestationCertChain);
}
}
10 changes: 8 additions & 2 deletions u2f-gae-demo/src/soy/card.soy
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<div class="androidAttestationLabel">Android Attestation <button class="toggleAttestationButton">show</button><br/></div>
<div class="androidAttestation">
<ul>
<li><span class="label">Cert Chain Verified: </span><span class="chainVerified">false</span><br/>
<li><span class="label">Cert Chain Verified: </span><span class="chainVerified"></span><br/>
<li><span class="label">Keymaster Version: </span><span class="keymasterVersion">none</span><br/>
<li><span class="label">Attestation Challenge: </span><span class="challenge">none</span><br/>
<li><span class="label">Software Enforced: </span>
Expand All @@ -39,7 +39,13 @@
</ul>
</div>
<li><span class="label">TEE Enforced: </span>
<span class="teeEnforced"></span>
<div class="teeEnforced">
<ul>
<li><span class="label">Purpose: </span> <span class="purpose">none</span>
<li><span class="label">Algorithm: </span> <span class="algorithm">none</span>
<li><span class="label">Key Size: </span> <span class="keysize">none</span>
<li><span class="label">Block Mode: </span> <span class="blockmode">none</span>
</ul>
</ul>
</div>
<div class="cardLabel">key handle</div>
Expand Down
31 changes: 28 additions & 3 deletions u2f-gae-demo/war/js/u2fdemo.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,55 @@ function tokenToDom(token) {
}
if (token.android_attestation) {
card.querySelector('.androidAttestationLabel').style.display = "inline";

card.querySelector('.chainVerified').textContent = token.android_attestation.chain_validated;

card.querySelector('.keymasterVersion').textContent
= token.android_attestation.keymaster_version;
card.querySelector('.challenge').textContent
= token.android_attestation.attestation_challenge;
= "0x" + token.android_attestation.attestation_challenge;

var showSoftwareEnforced = false;
if (token.android_attestation.software_encoded.algorithm) {
card.querySelector('.softwareEnforced .algorithm').textContent
= token.android_attestation.software_encoded.algorithm;
showSoftwareEnforced = true;
}
if (token.android_attestation.software_encoded.purpose) {
card.querySelector('.softwareEnforced .purpose').textContent
= token.android_attestation.software_encoded.purpose.join(', ');
showSoftwareEnforced = true;
}
if (token.android_attestation.software_encoded.keysize) {
card.querySelector('.softwareEnforced .keysize').textContent
= token.android_attestation.software_encoded.keysize;
showSoftwareEnforced = true;
}
if (token.android_attestation.software_encoded.blockmode) {
card.querySelector('.softwareEnforced .blockmode').textContent
= token.android_attestation.software_encoded.blockmode.join(', ');
showSoftwareEnforced = true;
}
if (!showSoftwareEnforced) {
card.querySelector('.softwareEnforced').style.display = "none";
}

card.querySelector('.teeEnforced').textContent
= JSON.stringify(token.android_attestation.tee_encoded, null, 2);
if (token.android_attestation.tee_encoded.algorithm) {
card.querySelector('.teeEnforced .algorithm').textContent
= token.android_attestation.tee_encoded.algorithm;
}
if (token.android_attestation.tee_encoded.purpose) {
card.querySelector('.teeEnforced .purpose').textContent
= token.android_attestation.tee_encoded.purpose.join(', ');
}
if (token.android_attestation.tee_encoded.keysize) {
card.querySelector('.teeEnforced .keysize').textContent
= token.android_attestation.tee_encoded.keysize;
}
if (token.android_attestation.tee_encoded.blockmode) {
card.querySelector('.teeEnforced .blockmode').textContent
= token.android_attestation.tee_encoded.blockmode.join(', ');
}
}
card.querySelector('.keyHandle').textContent = token.key_handle;
card.querySelector('.publicKey').textContent = token.public_key;
Expand Down
42 changes: 20 additions & 22 deletions u2f-ref-code/java/src/com/google/u2f/codec/RawMessageCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@

package com.google.u2f.codec;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import com.google.u2f.U2FException;
import com.google.u2f.key.messages.AuthenticateRequest;
import com.google.u2f.key.messages.AuthenticateResponse;
import com.google.u2f.key.messages.RegisterRequest;
import com.google.u2f.key.messages.RegisterResponse;
import com.google.u2f.tools.X509Util;

import org.bouncycastle.util.Arrays;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.cert.X509Certificate;

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

byte[] attestationCertificateBytes;
try {
attestationCertificateBytes = attestationCertificate.getEncoded();
} catch (CertificateEncodingException e) {
throw new U2FException("Error when encoding attestation certificate.", e);
}
attestationCertificateBytes = X509Util.encodeCertArray(attestationCertificateChain);

if (keyHandle.length > 255) {
throw new U2FException("keyHandle length cannot be longer than 255 bytes!");
Expand All @@ -95,10 +91,14 @@ public static RegisterResponse decodeRegisterResponse(byte[] data) throws U2FExc
inputStream.readFully(userPublicKey);
byte[] keyHandle = new byte[inputStream.readUnsignedByte()];
inputStream.readFully(keyHandle);
X509Certificate attestationCertificate = (X509Certificate) CertificateFactory.getInstance(
"X.509").generateCertificate(inputStream);
byte[] signature = new byte[inputStream.available()];
inputStream.readFully(signature);

// We don't know the length of the signature or cert chain, so we tread carefully
byte[] certsAndSig = new byte[inputStream.available()];
inputStream.readFully(certsAndSig);

X509Certificate[] attestationCertificateChain = X509Util.parseCertificateChain(certsAndSig);
byte[] signature = Arrays.copyOfRange(certsAndSig,
X509Util.encodeCertArray(attestationCertificateChain).length, certsAndSig.length);

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

return new RegisterResponse(userPublicKey, keyHandle, attestationCertificate, signature);
return new RegisterResponse(userPublicKey, keyHandle, attestationCertificateChain, signature);
} catch (IOException e) {
throw new U2FException("Error when parsing raw RegistrationResponse", e);
} catch (CertificateException e) {
throw new U2FException("Error when parsing attestation certificate", e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@

package com.google.u2f.key.impl;

import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.logging.Logger;

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

import com.google.u2f.U2FException;
import com.google.u2f.codec.RawMessageCodec;
import com.google.u2f.key.Crypto;
Expand All @@ -26,21 +19,28 @@
import com.google.u2f.key.messages.RegisterRequest;
import com.google.u2f.key.messages.RegisterResponse;

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

import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.logging.Logger;

public class U2FKeyReferenceImpl implements U2FKey {
private static final Logger Log = Logger.getLogger(U2FKeyReferenceImpl.class.getName());

private final X509Certificate vendorCertificate;
private final X509Certificate[] vendorCertificateChain;
private final PrivateKey certificatePrivateKey;
private final KeyPairGenerator keyPairGenerator;
private final KeyHandleGenerator keyHandleGenerator;
private final DataStore dataStore;
private final UserPresenceVerifier userPresenceVerifier;
private final Crypto crypto;

public U2FKeyReferenceImpl(X509Certificate vendorCertificate, PrivateKey certificatePrivateKey,
public U2FKeyReferenceImpl(X509Certificate[] vendorCertificateChain, PrivateKey certificatePrivateKey,
KeyPairGenerator keyPairGenerator, KeyHandleGenerator keyHandleGenerator,
DataStore dataStore, UserPresenceVerifier userPresenceVerifier, Crypto crypto) {
this.vendorCertificate = vendorCertificate;
this.vendorCertificateChain = vendorCertificateChain;
this.certificatePrivateKey = certificatePrivateKey;
this.keyPairGenerator = keyPairGenerator;
this.keyHandleGenerator = keyHandleGenerator;
Expand Down Expand Up @@ -81,12 +81,12 @@ public RegisterResponse register(RegisterRequest registerRequest) throws U2FExce
Log.info(" -- Outputs --");
Log.info(" userPublicKey: " + Hex.encodeHexString(userPublicKey));
Log.info(" keyHandle: " + Hex.encodeHexString(keyHandle));
Log.info(" vendorCertificate: " + vendorCertificate);
Log.info(" vendorCertificate: " + vendorCertificateChain);
Log.info(" signature: " + Hex.encodeHexString(signature));

Log.info("<< register");

return new RegisterResponse(userPublicKey, keyHandle, vendorCertificate, signature);
return new RegisterResponse(userPublicKey, keyHandle, vendorCertificateChain, signature);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
public class RegisterResponse extends U2FResponse {
private final byte[] userPublicKey;
private final byte[] keyHandle;
private final X509Certificate attestationCertificate;
private final X509Certificate[] attestationCertificateChain;
private final byte[] signature;

public RegisterResponse(byte[] userPublicKey, byte[] keyHandle,
X509Certificate attestationCertificate, byte[] signature) {
X509Certificate[] attestationCertificateChain, byte[] signature) {
super();
this.userPublicKey = userPublicKey;
this.keyHandle = keyHandle;
this.attestationCertificate = attestationCertificate;
this.attestationCertificateChain = attestationCertificateChain;
this.signature = signature;
}

Expand All @@ -43,10 +43,10 @@ public byte[] getKeyHandle() {
}

/**
* This is a X.509 certificate.
* This is a X.509 certificate chain.
*/
public X509Certificate getAttestationCertificate() {
return attestationCertificate;
public X509Certificate[] getAttestationCertificateChain() {
return attestationCertificateChain;
}

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

@Override
public int hashCode() {
return Objects.hash(userPublicKey, keyHandle, attestationCertificate, signature);
return Objects.hash(userPublicKey, keyHandle, attestationCertificateChain, signature);
}

@Override
Expand All @@ -71,6 +71,6 @@ public boolean equals(Object obj) {
return Arrays.equals(userPublicKey, other.userPublicKey)
&& Arrays.equals(keyHandle, other.keyHandle)
&& Arrays.equals(signature, other.signature)
&& Objects.equals(attestationCertificate, other.attestationCertificate);
&& Objects.equals(attestationCertificateChain, other.attestationCertificateChain);
}
}
Loading

0 comments on commit 53dc623

Please sign in to comment.