From 181f9696778b2735880cae56f841740d4e287657 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Wed, 17 Apr 2019 23:03:49 +0200 Subject: [PATCH] Extract function WebAuthnCodecs.importCosePublicKey --- .../yubico/internal/util/WebAuthnCodecs.java | 14 +++++++-- .../yubico/webauthn/BouncyCastleCrypto.java | 27 +++++++++++----- .../FidoU2fAttestationStatementVerifier.java | 31 ++++++++++++------- .../yubico/webauthn/FinishAssertionSteps.java | 2 +- .../PackedAttestationStatementVerifier.java | 2 +- .../yubico/webauthn/TestAuthenticator.scala | 2 +- .../yubico/webauthn/WebAuthnCodecsSpec.scala | 4 +-- .../webauthn/data/AuthenticatorDataSpec.scala | 4 ++- .../scala/com/yubico/webauthn/test/Test.scala | 2 +- 9 files changed, 60 insertions(+), 28 deletions(-) diff --git a/webauthn-server-core/src/main/java/com/yubico/internal/util/WebAuthnCodecs.java b/webauthn-server-core/src/main/java/com/yubico/internal/util/WebAuthnCodecs.java index 061dd3fe5..21243a7b8 100644 --- a/webauthn-server-core/src/main/java/com/yubico/internal/util/WebAuthnCodecs.java +++ b/webauthn-server-core/src/main/java/com/yubico/internal/util/WebAuthnCodecs.java @@ -123,8 +123,18 @@ public static ByteArray ecPublicKeyToCose(ECPublicKey key) { return rawEcdaKeyToCose(ecPublicKeyToRaw(key)); } - public static ECPublicKey importCoseP256PublicKey(ByteArray key) throws CoseException, IOException { - return new COSE.ECPublicKey(new OneKey(CBORObject.DecodeFromBytes(key.getBytes()))); + public static PublicKey importCosePublicKey(ByteArray key) throws CoseException, IOException { + CBORObject cose = CBORObject.DecodeFromBytes(key.getBytes()); + final int kty = cose.get(CBORObject.FromObject(1)).AsInt32(); + switch (kty) { + case 2: return importCoseP256PublicKey(cose); + default: + throw new IllegalArgumentException("Unsupported key type: " + kty); + } + } + + private static ECPublicKey importCoseP256PublicKey(CBORObject cose) throws CoseException, IOException { + return new COSE.ECPublicKey(new OneKey(cose)); } public static String getSignatureAlgorithmName(PublicKey key) { diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/BouncyCastleCrypto.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/BouncyCastleCrypto.java index 8b582ced3..6934c596a 100755 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/BouncyCastleCrypto.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/BouncyCastleCrypto.java @@ -57,19 +57,32 @@ public boolean verifySignature(X509Certificate attestationCertificate, ByteArray return verifySignature(attestationCertificate.getPublicKey(), signedBytes, signature); } - public boolean verifySignature(PublicKey publicKey, ByteArray signedBytes, ByteArray signature) { + public boolean verifySignature(PublicKey publicKey, ByteArray signedBytes, ByteArray signatureBytes) { try { - Signature ecdsaSignature = Signature.getInstance("SHA256withECDSA", provider); - ecdsaSignature.initVerify(publicKey); - ecdsaSignature.update(signedBytes.getBytes()); - return ecdsaSignature.verify(signature.getBytes()); - } catch (GeneralSecurityException e) { + final String algName; + switch (publicKey.getAlgorithm()) { + case "EC": + algName = "SHA256withECDSA"; + break; + + case "RSA": + algName = "SHA256withRSA"; + break; + + default: + throw new IllegalArgumentException("Unsupported public key algorithm: " + publicKey); + } + Signature signature = Signature.getInstance(algName, provider); + signature.initVerify(publicKey); + signature.update(signedBytes.getBytes()); + return signature.verify(signatureBytes.getBytes()); + } catch (GeneralSecurityException | IllegalArgumentException e) { throw new RuntimeException( String.format( "Failed to verify signature. This could be a problem with your JVM environment, or a bug in webauthn-server-core. Public key: %s, signed data: %s , signature: %s", publicKey, signedBytes.getBase64Url(), - signature.getBase64Url() + signatureBytes.getBase64Url() ), e ); diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/FidoU2fAttestationStatementVerifier.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/FidoU2fAttestationStatementVerifier.java index 6e8cf7680..705ac9698 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/FidoU2fAttestationStatementVerifier.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/FidoU2fAttestationStatementVerifier.java @@ -27,12 +27,13 @@ import COSE.CoseException; import com.fasterxml.jackson.databind.JsonNode; import com.yubico.internal.util.WebAuthnCodecs; -import com.yubico.webauthn.data.AttestedCredentialData; import com.yubico.webauthn.data.AttestationObject; import com.yubico.webauthn.data.AttestationType; +import com.yubico.webauthn.data.AttestedCredentialData; import com.yubico.webauthn.data.ByteArray; import java.io.IOException; import java.math.BigInteger; +import java.security.PublicKey; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.interfaces.ECPublicKey; @@ -80,17 +81,27 @@ private static boolean validSelfSignature(X509Certificate cert) { } } + private static ByteArray getRawUserPublicKey(AttestationObject attestationObject) throws IOException, CoseException { + final ByteArray pubkeyCose = attestationObject.getAuthenticatorData().getAttestedCredentialData().get().getCredentialPublicKey(); + final PublicKey pubkey = WebAuthnCodecs.importCosePublicKey(pubkeyCose); + + final ECPublicKey ecPubkey; + try { + ecPubkey = (ECPublicKey) pubkey; + } catch (ClassCastException e) { + throw new RuntimeException( "U2F supports only EC keys, was: " + pubkey); + } + + return WebAuthnCodecs.ecPublicKeyToRaw(ecPubkey); + } + @Override public AttestationType getAttestationType(AttestationObject attestationObject) throws CoseException, IOException, CertificateException { X509Certificate attestationCertificate = getAttestationCertificate(attestationObject); if (attestationCertificate.getPublicKey() instanceof ECPublicKey && validSelfSignature(attestationCertificate) - && WebAuthnCodecs.ecPublicKeyToRaw( - WebAuthnCodecs.importCoseP256PublicKey( - attestationObject.getAuthenticatorData().getAttestedCredentialData().get().getCredentialPublicKey() - ) - ) + && getRawUserPublicKey(attestationObject) .equals( WebAuthnCodecs.ecPublicKeyToRaw((ECPublicKey) attestationCertificate.getPublicKey()) ) @@ -128,14 +139,10 @@ && isP256(((ECPublicKey) attestationCertificate.getPublicKey()).getParams()) } if (signature.isBinary()) { - ByteArray userPublicKey; + final ByteArray userPublicKey; try { - userPublicKey = WebAuthnCodecs.ecPublicKeyToRaw( - WebAuthnCodecs.importCoseP256PublicKey( - attestedCredentialData.getCredentialPublicKey() - ) - ); + userPublicKey = getRawUserPublicKey(attestationObject); } catch (IOException | CoseException e) { RuntimeException err = new RuntimeException(String.format("Failed to parse public key from attestation data %s", attestedCredentialData)); log.error(err.getMessage(), err); diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java index 4c83c2b51..a08df2c37 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java @@ -546,7 +546,7 @@ public void validate() { final PublicKey key; try { - key = WebAuthnCodecs.importCoseP256PublicKey(cose); + key = WebAuthnCodecs.importCosePublicKey(cose); } catch (CoseException | IOException e) { throw new IllegalArgumentException( String.format( diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/PackedAttestationStatementVerifier.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/PackedAttestationStatementVerifier.java index bc168faec..60daeffe8 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/PackedAttestationStatementVerifier.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/PackedAttestationStatementVerifier.java @@ -98,7 +98,7 @@ private boolean verifyEcdaaSignature(AttestationObject attestationObject, ByteAr private boolean verifySelfAttestationSignature(AttestationObject attestationObject, ByteArray clientDataJsonHash) { final PublicKey pubkey; try { - pubkey = WebAuthnCodecs.importCoseP256PublicKey( + pubkey = WebAuthnCodecs.importCosePublicKey( attestationObject.getAuthenticatorData().getAttestedCredentialData().get().getCredentialPublicKey() ); } catch (IOException | CoseException e) { diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/TestAuthenticator.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/TestAuthenticator.scala index 0aa05c468..c2dbb2d50 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/TestAuthenticator.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/TestAuthenticator.scala @@ -399,7 +399,7 @@ object TestAuthenticator { authData.getRpIdHash, clientDataJson, authData.getAttestedCredentialData.get.getCredentialId, - WebAuthnCodecs.ecPublicKeyToRaw(WebAuthnCodecs.importCoseP256PublicKey(authData.getAttestedCredentialData.get.getCredentialPublicKey)) + WebAuthnCodecs.ecPublicKeyToRaw(WebAuthnCodecs.importCosePublicKey(authData.getAttestedCredentialData.get.getCredentialPublicKey).asInstanceOf[ECPublicKey]) ) val f = JsonNodeFactory.instance diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnCodecsSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnCodecsSpec.scala index 93f7dc0f5..0c0b1e6c5 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnCodecsSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnCodecsSpec.scala @@ -83,7 +83,7 @@ class WebAuthnCodecsSpec extends FunSpec with Matchers with GeneratorDrivenProp val coseKey = WebAuthnCodecs.rawEcdaKeyToCose(rawKey) - val importedPubkey: ECPublicKey = WebAuthnCodecs.importCoseP256PublicKey(coseKey) + val importedPubkey: ECPublicKey = WebAuthnCodecs.importCosePublicKey(coseKey).asInstanceOf[ECPublicKey] val rawImportedPubkey = WebAuthnCodecs.ecPublicKeyToRaw(importedPubkey) rawImportedPubkey should equal (rawKey) @@ -100,7 +100,7 @@ class WebAuthnCodecsSpec extends FunSpec with Matchers with GeneratorDrivenProp val coseKey = WebAuthnCodecs.ecPublicKeyToCose(originalPubkey) - val importedPubkey: ECPublicKey = WebAuthnCodecs.importCoseP256PublicKey(coseKey) + val importedPubkey: ECPublicKey = WebAuthnCodecs.importCosePublicKey(coseKey).asInstanceOf[ECPublicKey] val rawImportedPubkey = WebAuthnCodecs.ecPublicKeyToRaw(importedPubkey) rawImportedPubkey should equal (rawKey) diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/AuthenticatorDataSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/AuthenticatorDataSpec.scala index 087c5115f..bc3e9dd91 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/AuthenticatorDataSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/AuthenticatorDataSpec.scala @@ -24,6 +24,8 @@ package com.yubico.webauthn.data +import java.security.interfaces.ECPublicKey + import com.upokecenter.cbor.CBORObject import com.yubico.internal.util.WebAuthnCodecs import com.yubico.internal.util.scala.JavaConverters._ @@ -71,7 +73,7 @@ class AuthenticatorDataSpec extends FunSpec with Matchers { authData.getAttestedCredentialData.get.getAaguid.getHex should equal ("000102030405060708090a0b0c0d0e0f") authData.getAttestedCredentialData.get.getCredentialId.getHex should equal ("7137c4e57894dce742723f9966c1e71c7c966f14e9429d5b2a2098a68416deec") - val pubkey: ByteArray = WebAuthnCodecs.ecPublicKeyToRaw(WebAuthnCodecs.importCoseP256PublicKey(authData.getAttestedCredentialData.get.getCredentialPublicKey)) + val pubkey: ByteArray = WebAuthnCodecs.ecPublicKeyToRaw(WebAuthnCodecs.importCosePublicKey(authData.getAttestedCredentialData.get.getCredentialPublicKey).asInstanceOf[ECPublicKey]) pubkey should equal (ByteArray.fromHex("04DAFE0DE5312BA080A5CCDF6B483B10EF19A2454D1E17A8350311A0B7FF0566EF8EC6324D2C81398D2E80BC985B910B26970A0F408C9DE19BECCF39899A41674D")) } } diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/Test.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/Test.scala index 9b422ace1..82abeaa08 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/Test.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/Test.scala @@ -84,7 +84,7 @@ object Test extends App { val parsedAttObj = new AttestationObject(attestationObject) println(parsedAttObj) println(parsedAttObj.getAuthenticatorData.getBytes.getHex) - println(WebAuthnCodecs.importCoseP256PublicKey(parsedAttObj.getAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey)) + println(WebAuthnCodecs.importCosePublicKey(parsedAttObj.getAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey)) val attestationObjectCbor = WebAuthnCodecs.cbor.readTree(attestationObject.getBytes) println(attestationObjectCbor)