Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a515d73
Add support for Ed25519 and Ed448
fdennis Aug 12, 2025
2db0255
Link to RFC for Ed25519 and Ed448
fdennis Sep 3, 2025
262f831
Add Ed448 to PublicKeyCredentialParameters and preferredPubkeyParams
fdennis Sep 5, 2025
7468157
Add test for Ed448 in RelyingPartyV2RegistrationSpec
fdennis Sep 5, 2025
6ae9923
Add Ed448 to WebAuthnCodecs
fdennis Sep 9, 2025
96d3860
Add test for Ed448 in RelyingPartyRegistrationSpec
fdennis Sep 9, 2025
05fcfab
Merge branch 'main' into cose-alg-id
fdennis Sep 22, 2025
f04c0a5
Add and generate test data for Ed448
fdennis Sep 22, 2025
ae4cd66
Improve Ed448 logic in WebAuthnCodecs
fdennis Sep 22, 2025
c9ac33c
Fix indentation
fdennis Sep 22, 2025
da9cb88
Add Ed448 test to RelyingPartyV2AssertionSpec.scala
fdennis Sep 22, 2025
a161f43
Add Ed448 test to RelyingPartyAssertionSpec.scala
fdennis Oct 15, 2025
241b81c
Make EdDSA COSE encoding more robust for Ed25519/Ed448
fdennis Oct 15, 2025
9d42ad8
Generate new test data for Ed448
fdennis Oct 15, 2025
9cb7678
gradle spotlessApply
fdennis Oct 15, 2025
14fa04a
Test that WebAuthnCodecs.getJavaAlgorithmName accepts every defined v…
emlun Oct 28, 2025
48464e4
Test that no warnings about unknown algorithm are logged for defined …
emlun Oct 28, 2025
070d895
Set COSE alg dynamically in eddsaPublicKeyToCose
emlun Oct 24, 2025
3e06a2a
Regenerate test case BasicAttestationEd448
emlun Oct 28, 2025
03a2109
Construct coseKey variable later in eddsaPublicKeyToCose
emlun Oct 27, 2025
cfdf55e
Use ED25519_ALG_ID and Ed448_ALD_ID in WebAuthnTestCodecs
emlun Oct 27, 2025
f16f5e8
Merge generateEd448Keypair into generateEddsaKeypair
emlun Oct 27, 2025
cc2bea2
Merge importCose{Ed25519,Ed448}PublicKey into importCoseEdDsaPublicKey
emlun Oct 27, 2025
1f2c6b3
Use JCA identifiers "Ed25519" and "Ed448" instead of "EdDSA"
emlun Oct 28, 2025
b0f68cf
Fix JavaDoc link to COSEAlgorithmIdentifier#EdDSA
emlun Oct 28, 2025
d8f634d
Make COSEAlgorithmIdentifier match statements exhaustive
emlun Oct 28, 2025
7ffd904
Rename variable keyBytes to keyBytesLength
emlun Oct 28, 2025
f379165
Inline variables keyBytes{Ed25519,Ed448}
emlun Oct 28, 2025
e9ecf11
Flip order of alg and crv in WebAuthnTestCodecs.eddsaPublicKeyToCose
emlun Oct 28, 2025
3f62966
Extract WebAuthnCodecs.COSE_CRV_* constants
emlun Oct 28, 2025
40acf21
Extract variable algOid in WebAuthnTestCodecs.eddsaPublicKeyToCose
emlun Oct 28, 2025
5e6177c
Add Ed448 and COSEAlgorithmIdentifier.Ed25519 to NEWS
emlun Oct 28, 2025
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
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
New features:

* Added JavaDoc to `COSEAlgorithmIdentifier` constants.
* Added support for Ed448 signatures.
* New constants `COSEAlgorithmIdentifier.Ed25519`,
`COSEAlgorithmIdentifier.Ed448` and `PublicKeyCredentialParameters.Ed448`
* (Experimental) Added a new suite of interfaces, starting with
`CredentialRepositoryV2`. `RelyingParty` can now be configured with a
`CredentialRepositoryV2` instance instead of a `CredentialRepository`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ public class RelyingParty {
* <li>{@link com.yubico.webauthn.data.PublicKeyCredentialParameters#EdDSA EdDSA}
* <li>{@link com.yubico.webauthn.data.PublicKeyCredentialParameters#ES384 ES384}
* <li>{@link com.yubico.webauthn.data.PublicKeyCredentialParameters#ES512 ES512}
* <li>{@link com.yubico.webauthn.data.PublicKeyCredentialParameters#Ed448 Ed448}
* <li>{@link com.yubico.webauthn.data.PublicKeyCredentialParameters#RS256 RS256}
* <li>{@link com.yubico.webauthn.data.PublicKeyCredentialParameters#RS384 RS384}
* <li>{@link com.yubico.webauthn.data.PublicKeyCredentialParameters#RS512 RS512}
Expand All @@ -230,6 +231,7 @@ public class RelyingParty {
PublicKeyCredentialParameters.EdDSA,
PublicKeyCredentialParameters.ES384,
PublicKeyCredentialParameters.ES512,
PublicKeyCredentialParameters.Ed448,
PublicKeyCredentialParameters.RS256,
PublicKeyCredentialParameters.RS384,
PublicKeyCredentialParameters.RS512));
Expand Down Expand Up @@ -425,7 +427,12 @@ static List<PublicKeyCredentialParameters> filterAvailableAlgorithms(
try {
switch (param.getAlg()) {
case EdDSA:
KeyFactory.getInstance("EdDSA");
case Ed25519:
KeyFactory.getInstance("Ed25519");
break;

case Ed448:
KeyFactory.getInstance("Ed448");
break;

case ES256:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ public class RelyingPartyV2<C extends CredentialRecord> {
* <li>{@link PublicKeyCredentialParameters#EdDSA EdDSA}
* <li>{@link PublicKeyCredentialParameters#ES384 ES384}
* <li>{@link PublicKeyCredentialParameters#ES512 ES512}
* <li>{@link PublicKeyCredentialParameters#Ed448 Ed448}
* <li>{@link PublicKeyCredentialParameters#RS256 RS256}
* <li>{@link PublicKeyCredentialParameters#RS384 RS384}
* <li>{@link PublicKeyCredentialParameters#RS512 RS512}
Expand All @@ -243,6 +244,7 @@ public class RelyingPartyV2<C extends CredentialRecord> {
PublicKeyCredentialParameters.EdDSA,
PublicKeyCredentialParameters.ES384,
PublicKeyCredentialParameters.ES512,
PublicKeyCredentialParameters.Ed448,
PublicKeyCredentialParameters.RS256,
PublicKeyCredentialParameters.RS384,
PublicKeyCredentialParameters.RS512));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ final class WebAuthnCodecs {
private static final ByteArray P512_CURVE_OID =
new ByteArray(new byte[] {0x2B, (byte) 0x81, 0x04, 0, 35}); // OID 1.3.132.0.35

private static final ByteArray ED25519_ALG_ID =
static final ByteArray ED25519_ALG_ID =
new ByteArray(
new byte[] {
// SEQUENCE (5 bytes)
Expand All @@ -74,6 +74,28 @@ final class WebAuthnCodecs {
112
});

static final ByteArray ED448_ALG_ID =
new ByteArray(
new byte[] {
// SEQUENCE (5 bytes)
0x30,
5,
// OID (3 bytes)
0x06,
3,
// OID 1.3.101.113
0x2B,
101,
113
});

// See: https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves
static final int COSE_CRV_P256 = 1;
static final int COSE_CRV_P384 = 2;
static final int COSE_CRV_P521 = 3;
static final int COSE_CRV_ED25519 = 6;
static final int COSE_CRV_ED448 = 7;

static ByteArray ecPublicKeyToRaw(ECPublicKey key) {

final int fieldSizeBytes =
Expand Down Expand Up @@ -120,15 +142,15 @@ static ByteArray rawEcKeyToCose(ByteArray key) {
switch (len - start) {
case 64:
coseAlg = COSEAlgorithmIdentifier.ES256;
coseCrv = 1;
coseCrv = COSE_CRV_P256;
break;
case 96:
coseAlg = COSEAlgorithmIdentifier.ES384;
coseCrv = 2;
coseCrv = COSE_CRV_P384;
break;
case 132:
coseAlg = COSEAlgorithmIdentifier.ES512;
coseCrv = 3;
coseCrv = COSE_CRV_P521;
break;
default:
throw new RuntimeException(
Expand Down Expand Up @@ -178,15 +200,15 @@ private static PublicKey importCoseEcdsaPublicKey(CBORObject cose)

final byte[] curveOid;
switch (crv) {
case 1:
case COSE_CRV_P256:
curveOid = P256_CURVE_OID.getBytes();
break;

case 2:
case COSE_CRV_P384:
curveOid = P384_CURVE_OID.getBytes();
break;

case 3:
case COSE_CRV_P521:
curveOid = P512_CURVE_OID.getBytes();
break;

Expand Down Expand Up @@ -214,30 +236,40 @@ private static PublicKey importCoseEcdsaPublicKey(CBORObject cose)

private static PublicKey importCoseEdDsaPublicKey(CBORObject cose)
throws InvalidKeySpecException, NoSuchAlgorithmException {
final int alg = cose.get(CBORObject.FromObject(3)).AsInt32();
final int curveId = cose.get(CBORObject.FromObject(-1)).AsInt32();
switch (curveId) {
case 6:
return importCoseEd25519PublicKey(cose);
default:
throw new IllegalArgumentException("Unsupported EdDSA curve: " + curveId);
}
}

private static PublicKey importCoseEd25519PublicKey(CBORObject cose)
throws InvalidKeySpecException, NoSuchAlgorithmException {
final ByteArray algorithmOid = coseCurveToEddsaAlgorithmOid(curveId);
final byte[] rawKey = cose.get(CBORObject.FromObject(-2)).GetByteString();
final byte[] x509Key =
BinaryUtil.encodeDerSequence(
ED25519_ALG_ID.getBytes(), BinaryUtil.encodeDerBitStringWithZeroUnused(rawKey));
algorithmOid.getBytes(), BinaryUtil.encodeDerBitStringWithZeroUnused(rawKey));

KeyFactory kFact = KeyFactory.getInstance("EdDSA");
KeyFactory kFact =
KeyFactory.getInstance(
getJavaAlgorithmName(
COSEAlgorithmIdentifier.fromId(alg)
.orElseThrow(() -> new IllegalArgumentException("Unknown algorithm: " + alg))));
return kFact.generatePublic(new X509EncodedKeySpec(x509Key));
}

private static ByteArray coseCurveToEddsaAlgorithmOid(int curveId) {
switch (curveId) {
case COSE_CRV_ED25519:
return ED25519_ALG_ID;
case COSE_CRV_ED448:
return ED448_ALG_ID;
default:
throw new IllegalArgumentException("Unsupported EdDSA curve: " + curveId);
}
}

static String getJavaAlgorithmName(COSEAlgorithmIdentifier alg) {
switch (alg) {
case EdDSA:
return "EDDSA";
case Ed25519:
return "Ed25519";
case Ed448:
return "Ed448";
case ES256:
return "SHA256withECDSA";
case ES384:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,40 @@ public enum COSEAlgorithmIdentifier {
*/
EdDSA(-8),

/**
* The signature scheme Ed25519 as defined in <a href="https://www.rfc-editor.org/rfc/rfc8032">RFC
* 8032</a>.
*
* <p>This value is NOT RECOMMENDED, see the <a
* href="https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-pubkeycredparams">documentation
* of <code>pubKeyCredParams</code></a>. Use {@link #EdDSA} instead or in addition.
*
* @see <a href="https://www.iana.org/assignments/cose/cose.xhtml#algorithms">COSE Algorithms
* registry</a>
* @see <a
* href="https://www.ietf.org/archive/id/draft-ietf-jose-fully-specified-algorithms-13.html#name-edwards-curve-digital-signa">Fully-Specified
* Algorithms for JOSE and COSE</a>
* @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-alg-identifier">WebAuthn
* §5.8.5. Cryptographic Algorithm Identifier (typedef <code>COSEAlgorithmIdentifier</code>
* )</a>
*/
Ed25519(-19),

/**
* The signature scheme Ed448 as defined in <a href="https://www.rfc-editor.org/rfc/rfc8032">RFC
* 8032</a>.
*
* @see <a href="https://www.iana.org/assignments/cose/cose.xhtml#algorithms">COSE Algorithms
* registry</a>
* @see <a
* href="https://www.ietf.org/archive/id/draft-ietf-jose-fully-specified-algorithms-13.html#name-edwards-curve-digital-signa">Fully-Specified
* Algorithms for JOSE and COSE</a>
* @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-alg-identifier">WebAuthn
* §5.8.5. Cryptographic Algorithm Identifier (typedef <code>COSEAlgorithmIdentifier</code>
* )</a>
*/
Ed448(-53),

/**
* ECDSA with SHA-256 on the NIST P-256 curve.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,12 @@ private static List<PublicKeyCredentialParameters> filterAvailableAlgorithms(
try {
switch (param.getAlg()) {
case EdDSA:
KeyFactory.getInstance("EdDSA");
case Ed25519:
KeyFactory.getInstance("Ed25519");
break;

case Ed448:
KeyFactory.getInstance("Ed448");
break;

case ES256:
Expand Down Expand Up @@ -552,7 +557,12 @@ private static List<PublicKeyCredentialParameters> filterAvailableAlgorithms(
try {
switch (param.getAlg()) {
case EdDSA:
Signature.getInstance("EDDSA");
case Ed25519:
Signature.getInstance("Ed25519");
break;

case Ed448:
Signature.getInstance("Ed448");
break;

case ES256:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ private PublicKeyCredentialParameters(
public static final PublicKeyCredentialParameters EdDSA =
builder().alg(COSEAlgorithmIdentifier.EdDSA).build();

/**
* Algorithm {@link COSEAlgorithmIdentifier#Ed448} and type {@link
* PublicKeyCredentialType#PUBLIC_KEY}.
*/
public static final PublicKeyCredentialParameters Ed448 =
builder().alg(COSEAlgorithmIdentifier.Ed448).build();

/**
* Algorithm {@link COSEAlgorithmIdentifier#ES256} and type {@link
* PublicKeyCredentialType#PUBLIC_KEY}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.yubico.webauthn.attestation.AttestationTrustSource;
import com.yubico.webauthn.data.AttestationConveyancePreference;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.COSEAlgorithmIdentifier;
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
import com.yubico.webauthn.data.PublicKeyCredentialParameters;
Expand All @@ -18,6 +19,7 @@
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
Expand All @@ -29,6 +31,7 @@
import org.junit.Before;
import org.junit.Test;
import uk.org.lidalia.slf4jext.Level;
import uk.org.lidalia.slf4jtest.LoggingEvent;
import uk.org.lidalia.slf4jtest.TestLogger;
import uk.org.lidalia.slf4jtest.TestLoggerFactory;

Expand Down Expand Up @@ -306,6 +309,29 @@ public void doesNotLogWarningIfAllAlgorithmsAvailable() {
assertEquals(0, testLog.getAllLoggingEvents().size());
}

@Test
public void doesNotLogUnknownAlgorithmWarningForDefinedAlgorithms() {
RelyingParty.builder()
.identity(RelyingPartyIdentity.builder().id("localhost").name("Test").build())
.credentialRepository(unimplementedCredentialRepository())
.preferredPubkeyParams(
Arrays.stream(COSEAlgorithmIdentifier.values())
.map(alg -> PublicKeyCredentialParameters.builder().alg(alg).build())
.collect(Collectors.toList()))
.build();

for (LoggingEvent event : testLog.getLoggingEvents()) {
final String msg = event.getMessage().toLowerCase();
if (msg.contains("bug") || msg.contains("unknown")) {
fail(
String.format(
"Expected no log messages mentioning \"bug\" or \"unknown\", found: %s [arguments: %s]",
event.getMessage(), event.getArguments()));
}
}
;
}

private static CredentialRepository unimplementedCredentialRepository() {
return new CredentialRepository() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.yubico.webauthn.data.exception.HexException;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
Expand All @@ -15,6 +17,7 @@
import org.junit.Before;
import org.junit.Test;
import uk.org.lidalia.slf4jext.Level;
import uk.org.lidalia.slf4jtest.LoggingEvent;
import uk.org.lidalia.slf4jtest.TestLogger;
import uk.org.lidalia.slf4jtest.TestLoggerFactory;

Expand Down Expand Up @@ -156,4 +159,33 @@ public void doesNotLogWarningIfAllAlgorithmsAvailable() throws HexException {

assertEquals(0, testLog.getAllLoggingEvents().size());
}

@Test
public void doesNotLogUnknownAlgorithmWarningForDefinedAlgorithms() throws HexException {
PublicKeyCredentialCreationOptions.builder()
.rp(RelyingPartyIdentity.builder().id("localhost").name("Test").build())
.user(
UserIdentity.builder()
.name("foo")
.displayName("Foo User")
.id(ByteArray.fromHex("00010203"))
.build())
.challenge(ByteArray.fromHex("04050607"))
.pubKeyCredParams(
Arrays.stream(COSEAlgorithmIdentifier.values())
.map(alg -> PublicKeyCredentialParameters.builder().alg(alg).build())
.collect(Collectors.toList()))
.build();

for (LoggingEvent event : testLog.getLoggingEvents()) {
final String msg = event.getMessage().toLowerCase();
if (msg.contains("bug") || msg.contains("unknown")) {
fail(
String.format(
"Expected no log messages mentioning \"bug\" or \"unknown\", found: %s [arguments: %s]",
event.getMessage(), event.getArguments()));
}
}
;
}
}
Loading