diff --git a/u2f-gae-demo/src/com/google/u2f/gaedemo/storage/TokenStorageData.java b/u2f-gae-demo/src/com/google/u2f/gaedemo/storage/TokenStorageData.java index 61f59c7..35d783a 100644 --- a/u2f-gae-demo/src/com/google/u2f/gaedemo/storage/TokenStorageData.java +++ b/u2f-gae-demo/src/com/google/u2f/gaedemo/storage/TokenStorageData.java @@ -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; @@ -30,7 +28,7 @@ public class TokenStorageData { private List transports; private byte[] keyHandle; private byte[] publicKey; - private byte[] attestationCert; + private byte[] attestationCertChain; private int counter; // used by the storage layer @@ -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(); } @@ -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()); } @@ -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 @@ -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); } } diff --git a/u2f-gae-demo/src/soy/card.soy b/u2f-gae-demo/src/soy/card.soy index c85834f..7c695b9 100644 --- a/u2f-gae-demo/src/soy/card.soy +++ b/u2f-gae-demo/src/soy/card.soy @@ -26,7 +26,7 @@
Android Attestation
  • TEE Enforced: - +
    +
      +
    • Purpose: none +
    • Algorithm: none +
    • Key Size: none +
    • Block Mode: none +
    key handle
    diff --git a/u2f-gae-demo/war/js/u2fdemo.js b/u2f-gae-demo/war/js/u2fdemo.js index e6cea16..cbc3a26 100644 --- a/u2f-gae-demo/war/js/u2fdemo.js +++ b/u2f-gae-demo/war/js/u2fdemo.js @@ -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; diff --git a/u2f-ref-code/java/src/com/google/u2f/codec/RawMessageCodec.java b/u2f-ref-code/java/src/com/google/u2f/codec/RawMessageCodec.java index a1b35a2..e3fdd1f 100644 --- a/u2f-ref-code/java/src/com/google/u2f/codec/RawMessageCodec.java +++ b/u2f-ref-code/java/src/com/google/u2f/codec/RawMessageCodec.java @@ -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 @@ -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!"); @@ -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"); @@ -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); } } diff --git a/u2f-ref-code/java/src/com/google/u2f/key/impl/U2FKeyReferenceImpl.java b/u2f-ref-code/java/src/com/google/u2f/key/impl/U2FKeyReferenceImpl.java index 241b5b1..704d560 100644 --- a/u2f-ref-code/java/src/com/google/u2f/key/impl/U2FKeyReferenceImpl.java +++ b/u2f-ref-code/java/src/com/google/u2f/key/impl/U2FKeyReferenceImpl.java @@ -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; @@ -26,10 +19,17 @@ 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; @@ -37,10 +37,10 @@ public class U2FKeyReferenceImpl implements U2FKey { 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; @@ -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 diff --git a/u2f-ref-code/java/src/com/google/u2f/key/messages/RegisterResponse.java b/u2f-ref-code/java/src/com/google/u2f/key/messages/RegisterResponse.java index facc13a..c0f4bb5 100644 --- a/u2f-ref-code/java/src/com/google/u2f/key/messages/RegisterResponse.java +++ b/u2f-ref-code/java/src/com/google/u2f/key/messages/RegisterResponse.java @@ -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; } @@ -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) */ @@ -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 @@ -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); } } diff --git a/u2f-ref-code/java/src/com/google/u2f/server/data/SecurityKeyData.java b/u2f-ref-code/java/src/com/google/u2f/server/data/SecurityKeyData.java index ce093d8..b67c2a8 100644 --- a/u2f-ref-code/java/src/com/google/u2f/server/data/SecurityKeyData.java +++ b/u2f-ref-code/java/src/com/google/u2f/server/data/SecurityKeyData.java @@ -36,14 +36,14 @@ public String toString() { private final List transports; private final byte[] keyHandle; private final byte[] publicKey; - private final X509Certificate attestationCert; + private final X509Certificate[] attestationCertChain; private int counter; public SecurityKeyData( long enrollmentTime, byte[] keyHandle, byte[] publicKey, - X509Certificate attestationCert, + X509Certificate[] attestationCert, int counter) { this(enrollmentTime, null /* transports */, keyHandle, publicKey, attestationCert, counter); } @@ -53,13 +53,13 @@ public SecurityKeyData( List transports, byte[] keyHandle, byte[] publicKey, - X509Certificate attestationCert, + X509Certificate[] attestationCertChain, int counter) { this.enrollmentTime = enrollmentTime; this.transports = transports; this.keyHandle = keyHandle; this.publicKey = publicKey; - this.attestationCert = attestationCert; + this.attestationCertChain = attestationCertChain; this.counter = counter; } @@ -82,8 +82,8 @@ public byte[] getPublicKey() { return publicKey; } - public X509Certificate getAttestationCertificate() { - return attestationCert; + public X509Certificate[] getAttestationCertificateChain() { + return attestationCertChain; } public int getCounter() { @@ -101,7 +101,7 @@ public int hashCode() { transports, keyHandle, publicKey, - attestationCert, + attestationCertChain, counter); } @@ -115,7 +115,7 @@ public boolean equals(Object obj) { && (this.enrollmentTime == that.enrollmentTime) && containSameTransports(this.transports, that.transports) && Arrays.equals(this.publicKey, that.publicKey) - && Objects.equals(this.attestationCert, that.attestationCert) + && Arrays.equals(this.attestationCertChain, that.attestationCertChain) && Objects.equals(counter, counter); } @@ -139,20 +139,25 @@ public static boolean containSameTransports(List transports1, @Override public String toString() { return new StringBuilder() - .append("public_key: ") - .append(Base64.encodeBase64URLSafeString(publicKey)) - .append("\n") - .append("key_handle: ") - .append(Base64.encodeBase64URLSafeString(keyHandle)) - .append("\n") - .append("counter: ") - .append(counter) - .append("\n") - .append("attestation certificate:\n") - .append(attestationCert.toString()) - .append("transports: ") - .append(transports) - .append("\n") - .toString(); + .append("public_key: ") + .append(Base64.encodeBase64URLSafeString(publicKey)) + .append("\n") + .append("key_handle: ") + .append(Base64.encodeBase64URLSafeString(keyHandle)) + .append("\n") + .append("counter: ") + .append(counter) + .append("\n") + .append( + attestationCertChain.length > 1 + ? "attestation certificate chain" + : "attestation certificate") + .append(":\n") + .append(attestationCertChain.toString()) + .append("\n") + .append("transports: ") + .append(transports) + .append("\n") + .toString(); } } diff --git a/u2f-ref-code/java/src/com/google/u2f/server/impl/CaCerts.java b/u2f-ref-code/java/src/com/google/u2f/server/impl/CaCerts.java new file mode 100644 index 0000000..643c34e --- /dev/null +++ b/u2f-ref-code/java/src/com/google/u2f/server/impl/CaCerts.java @@ -0,0 +1,34 @@ +package com.google.u2f.server.impl; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; + +public class CaCerts { + private static final String TEMPORTARY_ANDROID_EC_ROOT_HEX = + "3082028b30820232a003020102020900a2059ed10e435b57300a06082a8648ce3d040302308198310b3009060355" + + "0406130255533113301106035504080c0a43616c69666f726e69613116301406035504070c0d4d6f756e746169" + + "6e205669657731153013060355040a0c0c476f6f676c652c20496e632e3110300e060355040b0c07416e64726f" + + "69643133303106035504030c2a416e64726f6964204b657973746f726520536f66747761726520417474657374" + + "6174696f6e20526f6f74301e170d3136303131313030343335305a170d3336303130363030343335305a308198" + + "310b30090603550406130255533113301106035504080c0a43616c69666f726e69613116301406035504070c0d" + + "4d6f756e7461696e205669657731153013060355040a0c0c476f6f676c652c20496e632e3110300e060355040b" + + "0c07416e64726f69643133303106035504030c2a416e64726f6964204b657973746f726520536f667477617265" + + "204174746573746174696f6e20526f6f743059301306072a8648ce3d020106082a8648ce3d03010703420004ee" + + "5d5ec7e1c0db6d03a67ee6b61bec4d6a5d6a682e0fff7f490e7d771f44226dbdb1affa16cbc7adc577d2569caa" + + "b7b02d54015d3e432b2a8ed74eec487541a4a3633061301d0603551d0e04160414c8ade9774c45c3a3cf0d1610" + + "e479433a215a30cf301f0603551d23041830168014c8ade9774c45c3a3cf0d1610e479433a215a30cf300f0603" + + "551d130101ff040530030101ff300e0603551d0f0101ff040403020284300a06082a8648ce3d04030203470030" + + "4402203521a3ef8b34461e9cd560f31d5889206adca36541f60d9ece8a198c6648607b02204d0bf351d9307c7d" + + "5bda35341da8471b63a585653cad4f24a7e74daf417df1bf"; + public static final byte[] TEMPORARY_ANDROID_EC_ROOT = + parseHex(TEMPORTARY_ANDROID_EC_ROOT_HEX); + + private static byte[] parseHex(String hexEncoded) { + try { + return Hex.decodeHex(hexEncoded.toCharArray()); + } catch (DecoderException e) { + throw new RuntimeException(e); + } + } +} + diff --git a/u2f-ref-code/java/src/com/google/u2f/server/impl/U2FServerReferenceImpl.java b/u2f-ref-code/java/src/com/google/u2f/server/impl/U2FServerReferenceImpl.java index 6c45a43..08f719f 100644 --- a/u2f-ref-code/java/src/com/google/u2f/server/impl/U2FServerReferenceImpl.java +++ b/u2f-ref-code/java/src/com/google/u2f/server/impl/U2FServerReferenceImpl.java @@ -32,13 +32,13 @@ import com.google.u2f.server.messages.RegistrationResponse; import com.google.u2f.server.messages.SignResponse; import com.google.u2f.server.messages.U2fSignRequest; +import com.google.u2f.tools.X509Util; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import java.net.URI; import java.net.URISyntaxException; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.Arrays; @@ -123,11 +123,15 @@ public SecurityKeyData processRegistrationResponse( byte[] userPublicKey = registerResponse.getUserPublicKey(); byte[] keyHandle = registerResponse.getKeyHandle(); - X509Certificate attestationCertificate = registerResponse.getAttestationCertificate(); + X509Certificate[] attestationCertificateChain = registerResponse.getAttestationCertificateChain(); byte[] signature = registerResponse.getSignature(); List transports = null; + + if (attestationCertificateChain.length != 1) { + Log.warning("Could not parse transports extension, attestation chain length != 1"); + } try { - transports = U2fAttestation.Parse(attestationCertificate).getTransports(); + transports = U2fAttestation.Parse(attestationCertificateChain[0]).getTransports(); } catch (CertificateParsingException e) { Log.warning("Could not parse transports extension " + e.getMessage()); } @@ -135,14 +139,10 @@ public SecurityKeyData processRegistrationResponse( Log.info("-- Parsed rawRegistrationResponse --"); Log.info(" userPublicKey: " + Hex.encodeHexString(userPublicKey)); Log.info(" keyHandle: " + Hex.encodeHexString(keyHandle)); - Log.info(" attestationCertificate: " + attestationCertificate.toString()); + Log.info(" attestationCertificate (chain): " + attestationCertificateChain.toString()); Log.info(" transports: " + transports); - try { - Log.info(" attestationCertificate bytes: " - + Hex.encodeHexString(attestationCertificate.getEncoded())); - } catch (CertificateEncodingException e) { - throw new U2FException("Cannot encode certificate", e); - } + Log.info(" attestationCertificate (chain) bytes: " + + Hex.encodeHexString(X509Util.encodeCertArray(attestationCertificateChain))); Log.info(" signature: " + Hex.encodeHexString(signature)); byte[] appIdSha256 = cryto.computeSha256(appId.getBytes()); @@ -150,8 +150,11 @@ public SecurityKeyData processRegistrationResponse( byte[] signedBytes = RawMessageCodec.encodeRegistrationSignedBytes( appIdSha256, clientDataSha256, keyHandle, userPublicKey); + // Note: This is an example of how an RP that wants to trust only certain attestation + // certificates could do so. Here we check that the root of the attestation chain is known: Set trustedCertificates = dataStore.getTrustedCertificates(); - if (!trustedCertificates.contains(attestationCertificate)) { + if (trustedCertificates.contains( + attestationCertificateChain[attestationCertificateChain.length - 1])) { Log.warning("attestion cert is not trusted"); } @@ -159,7 +162,7 @@ public SecurityKeyData processRegistrationResponse( new JsonParser().parse(clientData), "navigator.id.finishEnrollment", sessionData); Log.info("Verifying signature of bytes " + Hex.encodeHexString(signedBytes)); - if (!cryto.verifySignature(attestationCertificate, signedBytes, signature)) { + if (!cryto.verifySignature(attestationCertificateChain[0], signedBytes, signature)) { throw new U2FException("Signature is invalid"); } @@ -167,7 +170,7 @@ public SecurityKeyData processRegistrationResponse( // We don't actually know what the counter value of the real device is - but it will // be something bigger (or equal) to 0, so subsequent signatures will check out ok. SecurityKeyData securityKeyData = new SecurityKeyData(currentTimeInMillis, transports, - keyHandle, userPublicKey, attestationCertificate, /* initial counter value */ 0); + keyHandle, userPublicKey, attestationCertificateChain, /* initial counter value */ 0); dataStore.addSecurityKeyData(sessionData.getAccountName(), securityKeyData); Log.info("<< processRegistrationResponse"); diff --git a/u2f-ref-code/java/src/com/google/u2f/server/impl/attestation/android/AndroidKeyStoreAttestation.java b/u2f-ref-code/java/src/com/google/u2f/server/impl/attestation/android/AndroidKeyStoreAttestation.java index d14d995..7f321b4 100644 --- a/u2f-ref-code/java/src/com/google/u2f/server/impl/attestation/android/AndroidKeyStoreAttestation.java +++ b/u2f-ref-code/java/src/com/google/u2f/server/impl/attestation/android/AndroidKeyStoreAttestation.java @@ -1,7 +1,9 @@ package com.google.u2f.server.impl.attestation.android; import com.google.gson.JsonObject; +import com.google.u2f.U2FException; import com.google.u2f.server.impl.attestation.X509ExtensionParsingUtil; +import com.google.u2f.tools.X509Util; import org.apache.commons.codec.binary.Hex; import org.bouncycastle.asn1.ASN1Encodable; @@ -41,20 +43,23 @@ public class AndroidKeyStoreAttestation { private final byte[] attestationChallenge; private final AuthorizationList softwareAuthorizationList; private final AuthorizationList teeAuthorizationList; + private final boolean chainValidated; + private AndroidKeyStoreAttestation(Integer keymasterVersion, byte[] attestationChallenge, - AuthorizationList softwareAuthorizationList, AuthorizationList teeAuthorizationList) { + AuthorizationList softwareAuthorizationList, AuthorizationList teeAuthorizationList, + boolean chainValidated) { this.keymasterVersion = keymasterVersion; this.attestationChallenge = attestationChallenge; this.softwareAuthorizationList = softwareAuthorizationList; this.teeAuthorizationList = teeAuthorizationList; + this.chainValidated = chainValidated; } /** - * Parses the key description extension. Note that this method only parses the description - * extension in the leaf cert. It *does not* validate the certificate (or any chain). - * - * TODO(aczeskis): Add chain validation and remove/clarify the above comment. + * Parses the key description extension out of the leaf node of an AndroidKeyStore attestation + * X509 certificate chain. Note the leaf certificate is considered to be the 0th element in the + * certificate array. The chain is validated up to the root ca certificate. * * Expected format of the description extension is: * KeyDescription ::= SEQUENCE { @@ -134,12 +139,19 @@ private AndroidKeyStoreAttestation(Integer keymasterVersion, byte[] attestationC * osVersion INTEGER, * patchMonthYear INTEGER, * } + * + * @param certChain is the certificate chain to parse + * @param caCerts is an array of trusted CA Root Certificates */ - public static AndroidKeyStoreAttestation Parse(X509Certificate cert) - throws CertificateParsingException { - // Extract the extension from the certificate + public static AndroidKeyStoreAttestation Parse( + X509Certificate[] certChain, X509Certificate[] caCerts) throws CertificateParsingException { + if (certChain == null || certChain.length < 1) { + throw new CertificateParsingException("Certificate chain must have at least one cert"); + } + + // Extract the extension from the leaf certificate ASN1OctetString extensionValue = - X509ExtensionParsingUtil.extractExtensionValue(cert, KEY_DESCRIPTION_OID); + X509ExtensionParsingUtil.extractExtensionValue(certChain[0], KEY_DESCRIPTION_OID); if (extensionValue == null) { return null; @@ -163,8 +175,16 @@ public static AndroidKeyStoreAttestation Parse(X509Certificate cert) ASN1Sequence teeEnforcedSequence = getTeeEncodedSequence(keyDescriptionSequence); AuthorizationList teeAuthorizationList = extractAuthorizationList(teeEnforcedSequence); - return new AndroidKeyStoreAttestation( - keymasterVersion, challenge, softwareAuthorizationList, teeAuthorizationList); + boolean chainVerified; + + try { + chainVerified = X509Util.verifyCertChain(certChain, caCerts); + } catch (U2FException e) { + throw new CertificateParsingException(e); + } + + return new AndroidKeyStoreAttestation(keymasterVersion, challenge, softwareAuthorizationList, + teeAuthorizationList, chainVerified); } /** @@ -195,10 +215,17 @@ public AuthorizationList getTeeAuthorizationList() { return teeAuthorizationList; } + /** + * @return whether the attestation chain could be validated back to a known-valid root + */ + public boolean isChainValidated() { + return chainValidated; + } + @Override public int hashCode() { - return Objects.hash( - attestationChallenge, keymasterVersion, softwareAuthorizationList, teeAuthorizationList); + return Objects.hash(attestationChallenge, keymasterVersion, softwareAuthorizationList, + teeAuthorizationList, chainValidated); } @Override @@ -214,7 +241,8 @@ public boolean equals(Object obj) { return Objects.equals(attestationChallenge, other.attestationChallenge) && Objects.equals(keymasterVersion, other.keymasterVersion) && Objects.equals(softwareAuthorizationList, other.softwareAuthorizationList) - && Objects.equals(teeAuthorizationList, other.teeAuthorizationList); + && Objects.equals(teeAuthorizationList, other.teeAuthorizationList) + && chainValidated == other.chainValidated; } @Override @@ -237,6 +265,9 @@ public String toString() { attestation.append(teeAuthorizationList.toString().replaceAll("\n", "\n ")); } + attestation.append("\n chainValidated: "); + attestation.append(chainValidated); + attestation.append("\n]"); return attestation.toString(); @@ -248,6 +279,7 @@ public JsonObject toJson() { json.addProperty("attestation_challenge", Hex.encodeHexString(attestationChallenge)); json.add("software_encoded", softwareAuthorizationList.toJson()); json.add("tee_encoded", teeAuthorizationList.toJson()); + json.addProperty("chain_validated", chainValidated); return json; } diff --git a/u2f-ref-code/java/src/com/google/u2f/server/impl/attestation/u2f/U2fAttestation.java b/u2f-ref-code/java/src/com/google/u2f/server/impl/attestation/u2f/U2fAttestation.java index b18fc40..cced9a6 100644 --- a/u2f-ref-code/java/src/com/google/u2f/server/impl/attestation/u2f/U2fAttestation.java +++ b/u2f-ref-code/java/src/com/google/u2f/server/impl/attestation/u2f/U2fAttestation.java @@ -24,32 +24,30 @@ public class U2fAttestation { private final List transports; /** - * Parses a transport extension from an attestation certificate and returns - * a List of HardwareFeatures supported by the security key. The specification of - * the HardwareFeatures in the certificate should match their internal definition in - * device_auth.proto + * Parses a transport extension from an attestation certificate and returns a List of + * HardwareFeatures supported by the security key. * *

    The expected transport extension value is a BIT STRING containing the enabled * transports: * - *

    FIDOU2FTransports ::= BIT STRING { - * bluetoothRadio(0), -- Bluetooth Classic - * bluetoothLowEnergyRadio(1), - * uSB(2), - * nFC(3) - * } + *

    FIDOU2FTransports ::= BIT STRING { + * bluetoothRadio(0), -- Bluetooth Classic + * bluetoothLowEnergyRadio(1), + * uSB(2), + * nFC(3) + * } * - *

    Note that the BIT STRING must be wrapped in an OCTET STRING. - * An extension that encodes BT, BLE, and NFC then looks as follows: + *

    Note that the BIT STRING must be wrapped in an OCTET STRING. An extension that encodes BT, + * BLE, and NFC then looks as follows: * - *

    SEQUENCE (2 elem) - * OBJECT IDENTIFIER 1.3.6.1.4.1.45724.2.1.1 - * OCTET STRING (1 elem) - * BIT STRING (4 bits) 1101 + *

    SEQUENCE (2 elem) + * OBJECT IDENTIFIER 1.3.6.1.4.1.45724.2.1.1 + * OCTET STRING (1 elem) + * BIT STRING (4 bits) 1101 * * @param cert the certificate to parse for extension - * @return the supported transports as a List of HardwareFeatures or null if no extension - * was found + * @return the supported transports as a List of HardwareFeatures or null if no extension was + * found * @throws CertificateParsingException */ public static U2fAttestation Parse(X509Certificate cert) throws CertificateParsingException { diff --git a/u2f-ref-code/java/src/com/google/u2f/tools/X509Util.java b/u2f-ref-code/java/src/com/google/u2f/tools/X509Util.java new file mode 100644 index 0000000..56f04f7 --- /dev/null +++ b/u2f-ref-code/java/src/com/google/u2f/tools/X509Util.java @@ -0,0 +1,155 @@ +package com.google.u2f.tools; + +import com.google.u2f.U2FException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A set of high-level X509 parsing and validation utils + */ +public class X509Util { + private static final Logger Log = Logger.getLogger(X509Util.class.getName()); + private static final String X509 = "X.509"; + + /** + * Parse a single certificate out of a DER-encoded byte array + * + * @param encodedDerCertificate DER encoded X509 certificate + * @return a parsed certificate + * @throws CertificateException if parsing could not be done + */ + public static X509Certificate parseCertificate(byte[] encodedDerCertificate) + throws CertificateException { + return (X509Certificate) CertificateFactory.getInstance(X509).generateCertificate( + new ByteArrayInputStream(encodedDerCertificate)); + } + + /** + * Parse a certificate chain out of a DER-encoded byte array. Will attempt to parse certs + * out of the byte array until it can no longer do so. Will return all the certs (in order) that + * were parsed successfully. + * + * @param encodedDerCertificates DER encoded X509 certificates + * @return a parsed certificate chain + */ + public static X509Certificate[] parseCertificateChain(byte[] encodedDerCertificates) { + return parseCertificateChain( + new DataInputStream(new ByteArrayInputStream(encodedDerCertificates))); + } + + /** + * Parse a certificate chain out of a DER-encoded DataInputStream. Will attempt to parse certs + * out of the stream until it can no longer do so. Will return all the certs (in order) that were + * parsed successfully. + * + * @param encodedDerCertificates DER encoded X509 certificates + * @return a parsed certificate chain + */ + public static X509Certificate[] parseCertificateChain(DataInputStream encodedDerCertificates) { + List certChain = new LinkedList(); + try { + while (encodedDerCertificates.available() > 0) { + // Note: CertificateFactory#generateCertificate will extract one certificate from a byte + // stream even when there are trailing bytes remaining. Relying on this allows us to + // repeatedly call this method over a byte stream containing concatenated certificates. + certChain.add((X509Certificate) CertificateFactory.getInstance(X509).generateCertificate( + encodedDerCertificates)); + Log.info("Parsed cert: " + certChain.get(certChain.size() - 1)); + } + } catch (IOException | CertificateException e) { + // no more certs to read + Log.info("No more certs"); + } + return certChain.toArray(new X509Certificate[0]); + } + + /** + * Attempts to verify a certificate chain and makes sure that it chains up to at least one of the + * provided CA root certificates. + * + * @param certChain The certificate chain to validate. The leaf certificate is assumed to be in + * {@code certChain[0]} and it is assumed that {@code certChain[i]} certificate is signed by + * {@code certChain[i+1]}. Finally, {@code certChain[certChain.length-1]} should be signed + * by one of the root CA certificates. + * @param caCerts The trusted root certificates. + * @return {@code true} if validation succeeded + * @throws U2FException if there was a parsing error + */ + public static boolean verifyCertChain(X509Certificate[] certChain, X509Certificate[] caCerts) + throws U2FException { + if (caCerts == null || certChain == null || certChain.length == 0) { + return false; + } + + // Walk through intermediates up the chain + try { + for (int i = 0; i < certChain.length - 1; i++) { + certChain[i].verify(certChain[i + 1].getPublicKey()); + } + } catch (SignatureException | NoSuchProviderException | InvalidKeyException + | NoSuchAlgorithmException | CertificateException e) { + Log.log(Level.SEVERE, "Cannot validate cert chain is correctly signed by intermediaries", e); + return false; + } + + // Now attempt to verify up to one of the roots + boolean validated = true; + for (int i = 0; i < caCerts.length; i++) { + try { + certChain[certChain.length - 1].verify(caCerts[i].getPublicKey()); + } catch (SignatureException | NoSuchProviderException | InvalidKeyException + | NoSuchAlgorithmException | CertificateException e) { + Log.log(Level.INFO, "Cert chain validation failed to match a root.", e); + + // If it's the last cert, that means we didn't find a matching root ca + if (i == caCerts.length - 1) { + validated = false; + Log.log(Level.SEVERE, "Cannot validate cert chain is correctly signed by any known root"); + } + } + } + + return validated; + } + + /** + * Encodes an array of certificates into a byte array where all the certificates are DER encoded + * and concatenated in order. + * @param certsArray to encode + * @return the encoded certs + */ + public static byte[] encodeCertArray(X509Certificate[] certsArray) { + if (certsArray == null) { + return null; + } + + if (certsArray.length == 0) { + return new byte[0]; + } + + try { + ByteArrayOutputStream encodedCerts = new ByteArrayOutputStream(); + for (X509Certificate cert : certsArray) { + encodedCerts.write(cert.getEncoded()); + } + return encodedCerts.toByteArray(); + } catch (CertificateEncodingException | IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/u2f-ref-code/java/tests/com/google/u2f/TestUtils.java b/u2f-ref-code/java/tests/com/google/u2f/TestUtils.java index 0bb707e..49cfe6e 100644 --- a/u2f-ref-code/java/tests/com/google/u2f/TestUtils.java +++ b/u2f-ref-code/java/tests/com/google/u2f/TestUtils.java @@ -6,6 +6,8 @@ package com.google.u2f; +import com.google.u2f.tools.X509Util; + import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; @@ -17,7 +19,6 @@ import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECPoint; -import java.io.ByteArrayInputStream; import java.math.BigInteger; import java.security.KeyFactory; import java.security.MessageDigest; @@ -25,15 +26,11 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; -import java.security.cert.Certificate; import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; -import java.util.Collection; public class TestUtils { - static { Security.addProvider(new BouncyCastleProvider()); } @@ -47,50 +44,42 @@ public static byte[] parseHex(String hexEncoded) { } public static byte[] parseBase64(String base64Encoded) { - return Base64.decodeBase64(base64Encoded); + return Base64.decodeBase64(base64Encoded); } - public static X509Certificate parseCertificate(byte[] encodedDerCertificate) { + public static X509Certificate parseCertificateHex(String encodedDerCertificateHex) { try { - return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate( - new ByteArrayInputStream(encodedDerCertificate)); + return X509Util.parseCertificate(parseHex(encodedDerCertificateHex)); } catch (CertificateException e) { throw new RuntimeException(e); } } - public static X509Certificate parseCertificate(String encodedDerCertificateHex) { - return parseCertificate(parseHex(encodedDerCertificateHex)); - } - public static X509Certificate parseCertificateBase64(String encodedDerCertificate) { - return parseCertificate(parseBase64(encodedDerCertificate)); - } - - public static X509Certificate[] parseCertificateChainBase64(String encodedDerCertificates) { try { - Collection certCollection = - CertificateFactory.getInstance("X.509").generateCertificates( - new ByteArrayInputStream(parseBase64(encodedDerCertificates))); - return certCollection.toArray(new X509Certificate[0]); + return X509Util.parseCertificate(parseBase64(encodedDerCertificate)); } catch (CertificateException e) { throw new RuntimeException(e); } } + public static X509Certificate[] parseCertificateChainBase64(String encodedDerCertificates) { + return X509Util.parseCertificateChain(parseBase64(encodedDerCertificates)); + } + + public static X509Certificate[] parseCertificateChainHex(String encodedDerCertificates) { + return X509Util.parseCertificateChain(parseHex(encodedDerCertificates)); + } + public static PrivateKey parsePrivateKey(String keyBytesHex) { try { KeyFactory fac = KeyFactory.getInstance("ECDSA"); X9ECParameters curve = SECNamedCurves.getByName("secp256r1"); - ECParameterSpec curveSpec = new ECParameterSpec( - curve.getCurve(), curve.getG(), curve.getN(), curve.getH()); - ECPrivateKeySpec keySpec = new ECPrivateKeySpec( - new BigInteger(keyBytesHex, 16), - curveSpec); + ECParameterSpec curveSpec = + new ECParameterSpec(curve.getCurve(), curve.getG(), curve.getN(), curve.getH()); + ECPrivateKeySpec keySpec = new ECPrivateKeySpec(new BigInteger(keyBytesHex, 16), curveSpec); return fac.generatePrivate(keySpec); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (InvalidKeySpecException e) { + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new RuntimeException(e); } } @@ -98,14 +87,11 @@ public static PrivateKey parsePrivateKey(String keyBytesHex) { public static PublicKey parsePublicKey(byte[] keyBytes) { try { X9ECParameters curve = SECNamedCurves.getByName("secp256r1"); - ECParameterSpec curveSpec = new ECParameterSpec(curve.getCurve(), curve.getG(), curve.getN(), - curve.getH()); + ECParameterSpec curveSpec = + new ECParameterSpec(curve.getCurve(), curve.getG(), curve.getN(), curve.getH()); ECPoint point = curve.getCurve().decodePoint(keyBytes); - return KeyFactory.getInstance("ECDSA").generatePublic( - new ECPublicKeySpec(point, curveSpec)); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (InvalidKeySpecException e) { + return KeyFactory.getInstance("ECDSA").generatePublic(new ECPublicKeySpec(point, curveSpec)); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new RuntimeException(e); } } diff --git a/u2f-ref-code/java/tests/com/google/u2f/TestVectors.java b/u2f-ref-code/java/tests/com/google/u2f/TestVectors.java index 8dab6b5..b865bbb 100644 --- a/u2f-ref-code/java/tests/com/google/u2f/TestVectors.java +++ b/u2f-ref-code/java/tests/com/google/u2f/TestVectors.java @@ -7,9 +7,8 @@ package com.google.u2f; import static com.google.u2f.TestUtils.computeSha256; -import static com.google.u2f.TestUtils.parseCertificate; -import static com.google.u2f.TestUtils.parseCertificateBase64; import static com.google.u2f.TestUtils.parseCertificateChainBase64; +import static com.google.u2f.TestUtils.parseCertificateChainHex; import static com.google.u2f.TestUtils.parseHex; import static com.google.u2f.TestUtils.parsePrivateKey; import static com.google.u2f.TestUtils.parsePublicKey; @@ -56,8 +55,8 @@ public class TestVectors { + "03b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cd" + "b6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220" + "631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df"; - protected static final X509Certificate VENDOR_CERTIFICATE = - parseCertificate(VENDOR_CERTIFICATE_HEX); + protected static final X509Certificate[] VENDOR_CERTIFICATE = + parseCertificateChainHex(VENDOR_CERTIFICATE_HEX); protected static final PrivateKey VENDOR_CERTIFICATE_PRIVATE_KEY = parsePrivateKey("f3fccc0d00d8031954f90864d43c247f4bf5f0665c6b50cc17749a27d1cf7664"); protected static final String CHANNEL_ID_STRING = @@ -260,8 +259,8 @@ public class TestVectors { + "3046022100b4caea5dc60fbf9f004ed84fc4f18522981c1c303155c08274e889" + "f3f10c5b23022100faafb4f10b92f4754e3b08b5af353f78485bc903ece7ea91" + "1264fc1673b6598f"; - protected static final X509Certificate TRUSTED_CERTIFICATE_2 = - parseCertificate(TRUSTED_CERTIFICATE_2_HEX); + protected static final X509Certificate[] TRUSTED_CERTIFICATE_2 = + parseCertificateChainHex(TRUSTED_CERTIFICATE_2_HEX); // Has Bluetooth Radio transport private static final String TRUSTED_CERTIFICATE_ONE_TRANSPORT_BASE64 = @@ -274,8 +273,8 @@ public class TestVectors { + "HrZkwwgDR+UFYmwdXRXPoxcwFTATBgsrBgEEAYLlHAIBAQQEAwIHgDAKBggqhkjO" + "PQQDAgNIADBFAiAhBuNou+L8n4aZGCa5ClHGlLkPt8AZReepUx5LZTFaxQIhAKqO" + "daBx5kUAA3YVDH+u8bilfLS9QXKcKNm5vsdE67RJ"; - protected static final X509Certificate TRUSTED_CERTIFICATE_ONE_TRANSPORT = - parseCertificateBase64(TRUSTED_CERTIFICATE_ONE_TRANSPORT_BASE64); + protected static final X509Certificate[] TRUSTED_CERTIFICATE_ONE_TRANSPORT = + parseCertificateChainBase64(TRUSTED_CERTIFICATE_ONE_TRANSPORT_BASE64); // Has Bluetooth Radio, Bluetooth Low Energy, and NFC transports private static final String TRUSTED_CERTIFICATE_MULTIPLE_TRANSPORTS_BASE64 = @@ -288,8 +287,8 @@ public class TestVectors { + "HrZkwwgDR+UFYmwdXRXPoxcwFTATBgsrBgEEAYLlHAIBAQQEAwIE0DAKBggqhkjO" + "PQQDAgNHADBEAiBYtS8gXcl3LhvvkVlzYJgpD/tYUHae/Rw3z8lxQSeeXwIgDE2R" + "yWxFfRpgeg0WsLVHu7Ll4oZUkBEuS5RgezrcrRg="; - protected static final X509Certificate TRUSTED_CERTIFICATE_MULTIPLE_TRANSPORTS = - parseCertificateBase64(TRUSTED_CERTIFICATE_MULTIPLE_TRANSPORTS_BASE64); + protected static final X509Certificate[] TRUSTED_CERTIFICATE_MULTIPLE_TRANSPORTS = + parseCertificateChainBase64(TRUSTED_CERTIFICATE_MULTIPLE_TRANSPORTS_BASE64); private static final String TRUSTED_CERTIFICATE_MALFORMED_TRANSPORTS_EXTENSION_BASE64 = "MIIBmDCCAT6gAwIBAgIJASJCAAJVliZXMAoGCCqGSM49BAMCMEUxCzAJBgNVBAYT" @@ -301,8 +300,8 @@ public class TestVectors { + "HrZkwwgDR+UFYmwdXRXPoxUwEzARBgsrBgEEAYLlHAIBAQQCqoAwCgYIKoZIzj0E" + "AwIDSAAwRQIhAJB/ll8z2FeYKznZ9MIsy0pjNZ/BCq8IqBmXwOBLc9ybAiBPRdVW" + "ri6nGl/fpka1FlhNrahJVKXYudJ72wQeibIWtg=="; - protected static final X509Certificate TRUSTED_CERTIFICATE_MALFORMED_TRANSPORTS_EXTENSION = - parseCertificateBase64(TRUSTED_CERTIFICATE_MALFORMED_TRANSPORTS_EXTENSION_BASE64); + protected static final X509Certificate[] TRUSTED_CERTIFICATE_MALFORMED_TRANSPORTS_EXTENSION = + parseCertificateChainBase64(TRUSTED_CERTIFICATE_MALFORMED_TRANSPORTS_EXTENSION_BASE64); private static final String ANDROID_KEYSTORE_ATTESTATION_CERT_NO_VERSION_BASE64 = "MIIBlzCCAQCgAwIBAgICJxAwDQYJKoZIhvcNAQELBQAwHDEaMBgGA1UEAwwRQW5kcm9pZCBLZXlt" @@ -313,15 +312,15 @@ public class TestVectors { + "DZvsYbkgWAPv7QRa+cxLrFxrmv7M3HxYL7UdbpXP5/5sOp3hkhBdtAwlUW9tgGLdjheFFcz0lUSP" + "uK5et199s1ifeNzV4fePlBAGvzKFci6adJgGDMXDodM49jhIEF1KC4xlbwBWR/brl4vZa4h1EZ9H" + "ghyoJ3PFFZC8xYOB"; - protected static final X509Certificate ANDROID_KEYSTORE_ATTESTATION_CERT_NO_VERSION = - parseCertificateBase64(ANDROID_KEYSTORE_ATTESTATION_CERT_NO_VERSION_BASE64); + protected static final X509Certificate[] ANDROID_KEYSTORE_ATTESTATION_CERT_NO_VERSION = + parseCertificateChainBase64(ANDROID_KEYSTORE_ATTESTATION_CERT_NO_VERSION_BASE64); /** * Contains a chain where: * cert[0] = attestation certificate describing some new key * cert[1] = batch certificate * - * Note that cert[1] is signed by another cert that should be known to RPs. + * Note that cert[1] is signed by ANDROID_KEYSTORE_ATTESTATION_TEST_CA */ private static final String ANDROID_KEYSTORE_ATTESTATION_CERT_CHAIN_BASE64 = "MIIBjTCCATKgAwIBAgICJxAwCgYIKoZIzj0EAwIwHDEaMBgGA1UEAwwRQW5kcm9pZCBLZXltYXN0" @@ -331,22 +330,51 @@ public class TestVectors { + "AREEUjBQAgECBAljaGFsbGVuZ2UwPqEIMQYCAQICAQOiAwIBA6MEAgIBAKUFMQMCAQS/g3gDAgEB" + "v4N5BAICASy/hT0IAgYBUqi8MmC/hT4DAgEAMAAwCgYIKoZIzj0EAwIDSQAwRgIhANnmsSeWsnVH" + "aF5zII50tkiA7fRhIMNeZZBcPvSV2BN5AiEAwUZm63OxMZEHTIFL50ASKVN/sCLs8+gMY6uEVZRy" - + "61QwggK2MIICH6ADAgECAgIQADANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzETMBEGA1UE" - + "CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UECgwMR29vZ2xlLCBJ" - + "bmMuMRAwDgYDVQQLDAdBbmRyb2lkMB4XDTE2MDEwNDEyNDA1M1oXDTM1MTIzMDEyNDA1M1owdjEL" - + "MAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNVBAoMDEdvb2dsZSwgSW5jLjEQ" - + "MA4GA1UECwwHQW5kcm9pZDEpMCcGA1UEAwwgQW5kcm9pZCBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBL" - + "ZXkwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMCDI9xWiBu4MCBp9bCFYcbuvn8F4vWoQgSK" - + "votHvnb+rvJc8psq+jIAFBYBQpmJoV/PxoFes2NYPC/S8gvkmDKD3YFLFtfhhUF65Uq8KWo6bbXA" - + "BAg7aMVWwfAjOZFkGYZNULdNQK7KSEx3NWyJWgwnWr+sSZ1dfSNi8pxeAuhxAgMBAAGjZjBkMB0G" - + "A1UdDgQWBBTUDBAb+M1jufc5UrUOE1ym15mThjAfBgNVHSMEGDAWgBQp+vGszE3STJZAJ3W2sOky" - + "5Qf+LjASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIChDANBgkqhkiG9w0BAQsFAAOB" - + "gQCeLUhfjGcz3BqFrZnXUCPqFOxDsOGd6sIjRh5ytRncYCLkpWgxbAtVxOacoi2fOk+TazGLFngW" - + "DYjL2YvMgJ2E8MIn42s48f3R5xdyMVk1fZbzxX+rnY+WYSZPsr6Buw1JBCKKzp/39UIuJUT6IQcS" - + "WoO1Va0YgvhAFJucIGMEfw=="; + + "61QwggJ4MIICHqADAgECAgIQATAKBggqhkjOPQQDAjCBmDELMAkGA1UEBhMCVVMxEzARBgNVBAgM" + + "CkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTATBgNVBAoMDEdvb2dsZSwgSW5j" + + "LjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwqQW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBB" + + "dHRlc3RhdGlvbiBSb290MB4XDTE2MDExMTAwNDYwOVoXDTI2MDEwODAwNDYwOVowgYgxCzAJBgNV" + + "BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNV" + + "BAsMB0FuZHJvaWQxOzA5BgNVBAMMMkFuZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRp" + + "b24gSW50ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6555+EJjWazLKpFMiYbM" + + "cK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6luU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFKNmMGQwHQYD" + + "VR0OBBYEFD/8rNYasTqegSC41SUcxWW7HpGpMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0WEOR5Qzoh" + + "WjDPMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgKEMAoGCCqGSM49BAMCA0gAMEUC" + + "IEuKm3vugrzAM4euL8CJmLTdw42rJypFn2kMx8OS1A+OAiEA7toBXbb0MunUhDtiTJQE7zp8zL1e" + + "+yK75/65dz9ZP/s="; protected static final X509Certificate[] ANDROID_KEYSTORE_ATTESTATION_CERT_CHAIN = parseCertificateChainBase64(ANDROID_KEYSTORE_ATTESTATION_CERT_CHAIN_BASE64); + protected static final String ANDROID_KEYSTORE_ATTESTATION_TEST_CA_BASE64 = + "MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQGEwJVUzETMBEG" + + "A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UECgwMR29vZ2xl" + + "LCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYDVQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3" + + "YXJlIEF0dGVzdGF0aW9uIFJvb3QwHhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDEL" + + "MAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcx" + + "FTATBgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwqQW5kcm9p" + + "ZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0D" + + "AQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59dx9EIm29sa/6FsvHrcV30lacqrewLVQB" + + "XT5DKyqO107sSHVBpKNjMGEwHQYDVR0OBBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQY" + + "MBaAFMit6XdMRcOjzw0WEOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKE" + + "MAoGCCqGSM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBNC/NR" + + "2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw=="; + protected static final X509Certificate[] ANDROID_KEYSTORE_ATTESTATION_TEST_CA = + parseCertificateChainBase64(ANDROID_KEYSTORE_ATTESTATION_TEST_CA_BASE64); + + protected static final String ANDROID_KEYSTORE_ATTESTATION_FAKE_CA_BASE64 = + "MIICLjCCAdSgAwIBAgIJAOy0gtR7yj0ZMAkGByqGSM49BAEwRzELMAkGA1UEBhMCVVMxCzAJBgNVBA" + + "gTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMB4XDTE2MD" + + "IxMTA1MjIzNloXDTE3MDIxMDA1MjIzNlowRzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFA" + + "YDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMFkwEwYHKoZIzj0CAQYIKo" + + "ZIzj0DAQcDQgAEtqYoiLLVZnCSJOgP/M8umnZdC6i20hQd7dLVGBJBQ/BV5seh8tlBbCAbtpSJ6k" + + "od/xNxprMeyUXtWegSIVUsvaOBqTCBpjAdBgNVHQ4EFgQUUTPXfnwGnBIl7vEz3iW5ot/9szQwdw" + + "YDVR0jBHAwboAUUTPXfnwGnBIl7vEz3iW5ot/9szShS6RJMEcxCzAJBgNVBAYTAlVTMQswCQYDVQ" + + "QIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEGA1UEChMKR29vZ2xlIEluY4IJAOy0gt" + + "R7yj0ZMAwGA1UdEwQFMAMBAf8wCQYHKoZIzj0EAQNJADBGAiEA7sO6f/w77X75MxuFmardbtmLFu" + + "KxGFuyYOy0iUu8se4CIQCUfD3LMpa2wVH6aMHME6T17KOgTOk6zHa5l2I5bQkTOg=="; + protected static final X509Certificate[] ANDROID_KEYSTORE_ATTESTATION_FAKE_CA = + parseCertificateChainBase64(ANDROID_KEYSTORE_ATTESTATION_FAKE_CA_BASE64); + protected static final byte[] REGISTRATION_DATA_2 = parseHex("0504478E16BBDBBB741A660A000314A8B6BD63095196ED704C52EEBC0FA02A61" + "8F19FF59DF18451A11CEE43DEFD9A29B5710F63DFC671F752B1B0C6CA76C8427" diff --git a/u2f-ref-code/java/tests/com/google/u2f/key/impl/U2FKeyReferenceImplTest.java b/u2f-ref-code/java/tests/com/google/u2f/key/impl/U2FKeyReferenceImplTest.java index a6c5f27..daf6d76 100644 --- a/u2f-ref-code/java/tests/com/google/u2f/key/impl/U2FKeyReferenceImplTest.java +++ b/u2f-ref-code/java/tests/com/google/u2f/key/impl/U2FKeyReferenceImplTest.java @@ -13,12 +13,6 @@ import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import java.security.Signature; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; - import com.google.u2f.TestVectors; import com.google.u2f.key.DataStore; import com.google.u2f.key.KeyHandleGenerator; @@ -30,6 +24,12 @@ import com.google.u2f.key.messages.RegisterRequest; import com.google.u2f.key.messages.RegisterResponse; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import java.security.Signature; + public class U2FKeyReferenceImplTest extends TestVectors { @Mock KeyPairGenerator mockKeyPairGenerator; @@ -74,10 +74,10 @@ public void testRegister() throws Exception { verify(mockDataStore).storeKeyPair(KEY_HANDLE, USER_KEY_PAIR_ENROLL); assertArrayEquals(USER_PUBLIC_KEY_ENROLL_HEX, registerResponse.getUserPublicKey()); - assertEquals(VENDOR_CERTIFICATE, registerResponse.getAttestationCertificate()); + assertArrayEquals(VENDOR_CERTIFICATE, registerResponse.getAttestationCertificateChain()); assertArrayEquals(KEY_HANDLE, registerResponse.getKeyHandle()); Signature ecdsaSignature = Signature.getInstance("SHA256withECDSA"); - ecdsaSignature.initVerify(VENDOR_CERTIFICATE.getPublicKey()); + ecdsaSignature.initVerify(VENDOR_CERTIFICATE[0].getPublicKey()); ecdsaSignature.update(EXPECTED_REGISTER_SIGNED_BYTES); assertTrue(ecdsaSignature.verify(registerResponse.getSignature())); } diff --git a/u2f-ref-code/java/tests/com/google/u2f/server/impl/U2FServerReferenceImplTest.java b/u2f-ref-code/java/tests/com/google/u2f/server/impl/U2FServerReferenceImplTest.java index 983cb5b..d82f792 100644 --- a/u2f-ref-code/java/tests/com/google/u2f/server/impl/U2FServerReferenceImplTest.java +++ b/u2f-ref-code/java/tests/com/google/u2f/server/impl/U2FServerReferenceImplTest.java @@ -14,16 +14,6 @@ import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import java.security.cert.X509Certificate; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Matchers; -import org.mockito.Mock; - import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.u2f.TestVectors; @@ -43,6 +33,16 @@ import com.google.u2f.server.messages.SignResponse; import com.google.u2f.server.messages.U2fSignRequest; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Matchers; +import org.mockito.Mock; + +import java.security.cert.X509Certificate; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + public class U2FServerReferenceImplTest extends TestVectors { @Mock ChallengeGenerator mockChallengeGenerator; @Mock SessionIdGenerator mockSessionIdGenerator; @@ -56,7 +56,7 @@ public void setup() throws Exception { initMocks(this); HashSet trustedCertificates = new HashSet(); - trustedCertificates.add(VENDOR_CERTIFICATE); + trustedCertificates.add(VENDOR_CERTIFICATE[0]); when(mockChallengeGenerator.generateChallenge(ACCOUNT_NAME)) .thenReturn(SERVER_CHALLENGE_ENROLL); @@ -110,7 +110,7 @@ public void testProcessRegistrationResponse_oneTransport() throws U2FException { when(mockDataStore.getEnrollSessionData(SESSION_ID)).thenReturn( new EnrollSessionData(ACCOUNT_NAME, APP_ID_ENROLL, SERVER_CHALLENGE_ENROLL)); HashSet trustedCertificates = new HashSet(); - trustedCertificates.add(TRUSTED_CERTIFICATE_ONE_TRANSPORT); + trustedCertificates.add(TRUSTED_CERTIFICATE_ONE_TRANSPORT[0]); when(mockDataStore.getTrustedCertificates()).thenReturn(trustedCertificates); u2fServer = new U2FServerReferenceImpl(mockChallengeGenerator, mockDataStore, cryto, TRUSTED_DOMAINS); @@ -132,7 +132,7 @@ public void testProcessRegistrationResponse_multipleTransports() throws U2FExcep when(mockDataStore.getEnrollSessionData(SESSION_ID)).thenReturn( new EnrollSessionData(ACCOUNT_NAME, APP_ID_ENROLL, SERVER_CHALLENGE_ENROLL)); HashSet trustedCertificates = new HashSet(); - trustedCertificates.add(TRUSTED_CERTIFICATE_MULTIPLE_TRANSPORTS); + trustedCertificates.add(TRUSTED_CERTIFICATE_MULTIPLE_TRANSPORTS[0]); when(mockDataStore.getTrustedCertificates()).thenReturn(trustedCertificates); u2fServer = new U2FServerReferenceImpl(mockChallengeGenerator, mockDataStore, cryto, TRUSTED_DOMAINS); @@ -156,7 +156,7 @@ public void testProcessRegistrationResponse_malformedTransports() throws U2FExce when(mockDataStore.getEnrollSessionData(SESSION_ID)).thenReturn( new EnrollSessionData(ACCOUNT_NAME, APP_ID_ENROLL, SERVER_CHALLENGE_ENROLL)); HashSet trustedCertificates = new HashSet(); - trustedCertificates.add(TRUSTED_CERTIFICATE_MALFORMED_TRANSPORTS_EXTENSION); + trustedCertificates.add(TRUSTED_CERTIFICATE_MALFORMED_TRANSPORTS_EXTENSION[0]); when(mockDataStore.getTrustedCertificates()).thenReturn(trustedCertificates); u2fServer = new U2FServerReferenceImpl(mockChallengeGenerator, mockDataStore, cryto, TRUSTED_DOMAINS); @@ -176,8 +176,8 @@ public void testProcessRegistrationResponse2() throws U2FException { when(mockDataStore.getEnrollSessionData(SESSION_ID)).thenReturn( new EnrollSessionData(ACCOUNT_NAME, APP_ID_ENROLL, SERVER_CHALLENGE_ENROLL)); HashSet trustedCertificates = new HashSet(); - trustedCertificates.add(VENDOR_CERTIFICATE); - trustedCertificates.add(TRUSTED_CERTIFICATE_2); + trustedCertificates.add(VENDOR_CERTIFICATE[0]); + trustedCertificates.add(TRUSTED_CERTIFICATE_2[0]); when(mockDataStore.getTrustedCertificates()).thenReturn(trustedCertificates); u2fServer = new U2FServerReferenceImpl(mockChallengeGenerator, mockDataStore, cryto, TRUSTED_DOMAINS); @@ -215,7 +215,7 @@ public void testProcessSignResponse() throws U2FException { } @Test - public void testProcessSignResponse_badOrigin() throws U2FException { + public void testProcessSignResponse_badOrigin() { when(mockDataStore.getSignSessionData(SESSION_ID)).thenReturn( new SignSessionData(ACCOUNT_NAME, APP_ID_SIGN, SERVER_CHALLENGE_SIGN, USER_PUBLIC_KEY_SIGN_HEX)); u2fServer = new U2FServerReferenceImpl(mockChallengeGenerator, diff --git a/u2f-ref-code/java/tests/com/google/u2f/server/impl/attestation/android/AndroidKeyStoreAttestationTest.java b/u2f-ref-code/java/tests/com/google/u2f/server/impl/attestation/android/AndroidKeyStoreAttestationTest.java index 897d5a8..2cff49c 100644 --- a/u2f-ref-code/java/tests/com/google/u2f/server/impl/attestation/android/AndroidKeyStoreAttestationTest.java +++ b/u2f-ref-code/java/tests/com/google/u2f/server/impl/attestation/android/AndroidKeyStoreAttestationTest.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -20,8 +21,8 @@ public class AndroidKeyStoreAttestationTest extends TestVectors { @Test public void testValidCert() throws Exception { - AndroidKeyStoreAttestation attestation = - AndroidKeyStoreAttestation.Parse(ANDROID_KEYSTORE_ATTESTATION_CERT_CHAIN[0]); + AndroidKeyStoreAttestation attestation = AndroidKeyStoreAttestation.Parse( + ANDROID_KEYSTORE_ATTESTATION_CERT_CHAIN, ANDROID_KEYSTORE_ATTESTATION_TEST_CA); assertNotNull("Not expecting null attestation", attestation); @@ -61,14 +62,27 @@ public void testValidCert() throws Exception { // Get the TEE authorization list AuthorizationList teeAuthorizationList = attestation.getTeeAuthorizationList(); assertNotNull("Not expecting null TEE authorization list", teeAuthorizationList); - assertEquals( - "Expecting null TEE authorization list purpose", null, teeAuthorizationList.getPurposeList()); + assertEquals("Expecting null TEE authorization list purpose", null, + teeAuthorizationList.getPurposeList()); assertEquals("Expecting null TEE authorization list algorithm", null, teeAuthorizationList.getAlgorithm()); + + // Validate chain verification + assertTrue("Expecting cert chain to be validated", attestation.isChainValidated()); + } + + @Test + public void testFailedValidation() throws Exception { + AndroidKeyStoreAttestation attestation = AndroidKeyStoreAttestation.Parse( + ANDROID_KEYSTORE_ATTESTATION_CERT_CHAIN, ANDROID_KEYSTORE_ATTESTATION_FAKE_CA); + + // Validate chain verification + assertFalse("Was not expecting cert chain to be valid", attestation.isChainValidated()); } @Test(expected = CertificateParsingException.class) public void testInvalidCertNotEnoughInDescriptionTest() throws Exception { - AndroidKeyStoreAttestation.Parse(ANDROID_KEYSTORE_ATTESTATION_CERT_NO_VERSION); + AndroidKeyStoreAttestation.Parse( + ANDROID_KEYSTORE_ATTESTATION_CERT_NO_VERSION, ANDROID_KEYSTORE_ATTESTATION_TEST_CA); } } diff --git a/u2f-ref-code/java/tests/com/google/u2f/server/impl/attestation/u2f/U2fAttestationTest.java b/u2f-ref-code/java/tests/com/google/u2f/server/impl/attestation/u2f/U2fAttestationTest.java index a631246..9a06639 100644 --- a/u2f-ref-code/java/tests/com/google/u2f/server/impl/attestation/u2f/U2fAttestationTest.java +++ b/u2f-ref-code/java/tests/com/google/u2f/server/impl/attestation/u2f/U2fAttestationTest.java @@ -21,7 +21,7 @@ public class U2fAttestationTest extends TestVectors { @Test public void testValidCertOneTransport() throws Exception { - U2fAttestation attestation = U2fAttestation.Parse(TRUSTED_CERTIFICATE_ONE_TRANSPORT); + U2fAttestation attestation = U2fAttestation.Parse(TRUSTED_CERTIFICATE_ONE_TRANSPORT[0]); assertNotNull(attestation); List transports = attestation.getTransports(); @@ -32,19 +32,19 @@ public void testValidCertOneTransport() throws Exception { @Test(expected = CertificateParsingException.class) public void testMalformedCert() throws Exception { - U2fAttestation.Parse(TRUSTED_CERTIFICATE_MALFORMED_TRANSPORTS_EXTENSION); + U2fAttestation.Parse(TRUSTED_CERTIFICATE_MALFORMED_TRANSPORTS_EXTENSION[0]); } @Test public void testValidCertNoTransports() throws Exception { - U2fAttestation attestation = U2fAttestation.Parse(TRUSTED_CERTIFICATE_2); + U2fAttestation attestation = U2fAttestation.Parse(TRUSTED_CERTIFICATE_2[0]); assertNotNull(attestation); assertTrue(attestation.getTransports() == null); } @Test public void testValidCertMultipleTransports() throws Exception { - U2fAttestation attestation = U2fAttestation.Parse(TRUSTED_CERTIFICATE_MULTIPLE_TRANSPORTS); + U2fAttestation attestation = U2fAttestation.Parse(TRUSTED_CERTIFICATE_MULTIPLE_TRANSPORTS[0]); assertNotNull(attestation); List transports = attestation.getTransports();