diff --git a/NEWS b/NEWS index 3c25c95bf..09cc93952 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,8 @@ -== Version 1.1.1 (unreleased) == +== Version 1.2.0 (unreleased) == + +New features: + +* RSA keys are now supported. Bug fixes: 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 21243a7b8..1c6e50e7e 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 @@ -38,11 +38,17 @@ import com.yubico.webauthn.data.ByteArray; import com.yubico.webauthn.data.COSEAlgorithmIdentifier; import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import org.bouncycastle.jce.provider.BouncyCastleProvider; public final class WebAuthnCodecs { @@ -123,16 +129,25 @@ public static ByteArray ecPublicKeyToCose(ECPublicKey key) { return rawEcdaKeyToCose(ecPublicKeyToRaw(key)); } - public static PublicKey importCosePublicKey(ByteArray key) throws CoseException, IOException { + public static PublicKey importCosePublicKey(ByteArray key) throws CoseException, IOException, InvalidKeySpecException, NoSuchAlgorithmException { CBORObject cose = CBORObject.DecodeFromBytes(key.getBytes()); final int kty = cose.get(CBORObject.FromObject(1)).AsInt32(); switch (kty) { case 2: return importCoseP256PublicKey(cose); + case 3: return importCoseRsaPublicKey(cose); default: throw new IllegalArgumentException("Unsupported key type: " + kty); } } + private static PublicKey importCoseRsaPublicKey(CBORObject cose) throws NoSuchAlgorithmException, InvalidKeySpecException { + RSAPublicKeySpec spec = new RSAPublicKeySpec( + new BigInteger(1, cose.get(CBORObject.FromObject(-1)).GetByteString()), + new BigInteger(1, cose.get(CBORObject.FromObject(-2)).GetByteString()) + ); + return KeyFactory.getInstance("RSA", new BouncyCastleProvider()).generatePublic(spec); + } + private static ECPublicKey importCoseP256PublicKey(CBORObject cose) throws CoseException, IOException { return new COSE.ECPublicKey(new OneKey(cose)); } 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 705ac9698..5c029e2b6 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 @@ -26,6 +26,7 @@ import COSE.CoseException; import com.fasterxml.jackson.databind.JsonNode; +import com.yubico.internal.util.ExceptionUtil; import com.yubico.internal.util.WebAuthnCodecs; import com.yubico.webauthn.data.AttestationObject; import com.yubico.webauthn.data.AttestationType; @@ -33,11 +34,13 @@ import com.yubico.webauthn.data.ByteArray; import java.io.IOException; import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.interfaces.ECPublicKey; import java.security.spec.ECParameterSpec; +import java.security.spec.InvalidKeySpecException; import java.util.Objects; import java.util.Optional; import lombok.extern.slf4j.Slf4j; @@ -83,7 +86,12 @@ 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 PublicKey pubkey; + try { + pubkey = WebAuthnCodecs.importCosePublicKey(pubkeyCose); + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + throw ExceptionUtil.wrapAndLog(log, "Failed to decode public key: " + pubkeyCose.getHex(), e); + } final ECPublicKey ecPubkey; try { 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 a08df2c37..1c5fc3a71 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 @@ -37,7 +37,9 @@ import com.yubico.webauthn.exception.InvalidSignatureCountException; import com.yubico.webauthn.extension.appid.AppId; import java.io.IOException; +import java.security.NoSuchAlgorithmException; import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -547,7 +549,7 @@ public void validate() { try { key = WebAuthnCodecs.importCosePublicKey(cose); - } catch (CoseException | IOException e) { + } catch (CoseException | IOException | InvalidKeySpecException e) { throw new IllegalArgumentException( String.format( "Failed to decode public key: Credential ID: %s COSE: %s", @@ -556,6 +558,8 @@ public void validate() { ), e ); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); } if (! 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 60daeffe8..a100579b8 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 @@ -46,6 +46,7 @@ import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import java.util.HashSet; import java.util.Locale; @@ -101,12 +102,14 @@ private boolean verifySelfAttestationSignature(AttestationObject attestationObje pubkey = WebAuthnCodecs.importCosePublicKey( attestationObject.getAuthenticatorData().getAttestedCredentialData().get().getCredentialPublicKey() ); - } catch (IOException | CoseException e) { + } catch (IOException | CoseException | InvalidKeySpecException e) { throw ExceptionUtil.wrapAndLog( log, String.format("Failed to parse public key from attestation data %s", attestationObject.getAuthenticatorData().getAttestedCredentialData()), e ); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); } final Long keyAlgId = CBORObject.DecodeFromBytes(attestationObject.getAuthenticatorData().getAttestedCredentialData().get().getCredentialPublicKey().getBytes())