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

Adds most logic needed for validating cert chain #101

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't java.util.Arrays.copyOfRange sufficient? It's in jre7, at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, removed.


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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This took some reading to figure out. I think what you're doing is:

  1. Assuming that the signature is all the bytes that remain at the end of the certificate chain.
  2. Decoding, then re-encoding, the certificate chain, to figure out where the end of the encoded certificate chain is.

Is that right? Maybe a comment to that effect would help?

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