Skip to content

feat(crypto): support Ed25519/EdDSA in COSE parser for WebAuthn#10080

Closed
sea-snake wants to merge 4 commits intodfinity:masterfrom
sea-snake:sea-snake/cose-ed25519
Closed

feat(crypto): support Ed25519/EdDSA in COSE parser for WebAuthn#10080
sea-snake wants to merge 4 commits intodfinity:masterfrom
sea-snake:sea-snake/cose-ed25519

Conversation

@sea-snake
Copy link
Copy Markdown

@sea-snake sea-snake commented May 4, 2026

Problem

The IC's COSE parser at rs/crypto/internal/crypto_lib/basic_sig/cose/src/lib.rs only accepts ECDSA-P256 (kty=EC2, alg=ES256) and RSA-PKCS1-SHA256 (kty=RSA, alg=RS256) keys. WebAuthn authenticators that produce kty=OKP, alg=EdDSA, crv=Ed25519 keys (e.g. NitroKey 3A) are rejected with the misleading error:

Invalid delegation: Invalid public key: Algorithm Unspecified not supported: Algorithm not supported in COSE parser

The "Unspecified" comes from the wrapper at parse_cose_public_key, which hardcodes AlgorithmId::Unspecified for any AlgorithmNotSupported outcome — so the actual EdDSA alg value never surfaces.

Why Ed25519 recovery phrases work but Ed25519 WebAuthn keys don't

The IC already has full Ed25519 support for non-WebAuthn keys. Recovery phrases in Internet Identity generate raw Ed25519 keys that are DER-encoded with the standard Ed25519 algorithm identifier (RFC 8410, OID 1.3.101.112). These take the ed25519_algorithm_identifier() path in user_public_key_from_bytes and are verified via verify_basic_sig_by_public_key(AlgorithmId::Ed25519, ...) with a plain signature — no WebAuthn envelope, no COSE parsing.

WebAuthn authenticators like the NitroKey 3A take a different code path: the public key is COSE-encoded and DER-wrapped with the IC's COSE OID (1.3.6.1.4.1.56387.1.1). This hits the cose_algorithm_identifier() branch, which calls parse_cose_public_key — and that parser only knew ECDSA-P256 and RSA-SHA256. The Ed25519 cryptographic primitives were always there; the COSE parser just never learned to unwrap Ed25519/EdDSA keys from the COSE map format that WebAuthn produces.

This was reported via dfinity/internet-identity#3835 with a captured COSE key from a NitroKey 3A. The captured key has only the standard 4 COSE map entries (no key_ops or other extension fields), confirming the root cause is the parser's algorithm allowlist, not extra entries.

Changes

cose crate

  • Add CosePublicKey::Ed25519(Vec<u8>) variant.
  • Add parse_eddsa_ed25519 helper that decodes kty=OKP / alg=EdDSA / crv=Ed25519 / x=<32 bytes> and returns the RFC 8410 SPKI DER via ic_ed25519::PublicKey::deserialize_raw().serialize_rfc8410_der().
  • Wire the new branch into CosePublicKey::from_cbor.
  • Add ic-ed25519 to the crate's Cargo.toml and BUILD.bazel.

ic-crypto-standalone-sig-verifier

  • Add KeyBytesContentType::Ed25519PublicKeyDerWrappedCose.
  • Map AlgorithmId::Ed25519 to it in cose_key_bytes_content_type.

ic-validator

  • ingress_validation.rs: include Ed25519PublicKeyDerWrappedCose in the WebAuthn signature path (both in validate_signed_request and validate_signed_delegation).
  • webauthn.rs: add an AlgorithmId::Ed25519 arm to basic_sig_from_webauthn_sig. Per WebAuthn §6.5.6, EdDSA WebAuthn signatures are 64 raw bytes (no DER wrapping) — they pass through unchanged.

Tests

  • cose/tests/tests.rs: parsing of the RFC 8032 TEST 1 keypair as a COSE key, end-to-end signature verification through the parsed DER, parsing of the captured NitroKey 3A WebAuthn key, rejection of crv=Ed448 and short-x malformed keys.
  • validator/src/webauthn.rs: a new ed25519 test module mirroring the existing ecdsa and rsa modules, with a valid-signature, wrong-message, and malformed-signature test. Updates the existing should_return_error_if_algorithm_id_is_not_supported test (which previously asserted Ed25519 was unsupported) to use AlgorithmId::EcdsaSecp256k1 instead.

Verification

cargo test -p ic-crypto-internal-basic-sig-cose -p ic-crypto-standalone-sig-verifier -p ic-validator
cargo clippy --all-features --tests -p ic-crypto-internal-basic-sig-cose -p ic-crypto-standalone-sig-verifier -p ic-validator -- -D warnings -D clippy::all

All clean locally. Bazel build/test was not run in the development environment — please verify in CI.

Related

sea-snake and others added 4 commits May 4, 2026 15:23
The COSE parser only accepted ECDSA-P256 (kty=EC2, alg=ES256) and
RSA-PKCS1-SHA256 (kty=RSA, alg=RS256). WebAuthn authenticators that
produce kty=OKP, alg=EdDSA, crv=Ed25519 keys (e.g. NitroKey 3A) were
rejected with the misleading error "Algorithm Unspecified not supported"
- the wrapper hardcodes AlgorithmId::Unspecified for any unsupported
algorithm, so the actual Ed25519 alg never surfaces.

Add an Ed25519/EdDSA branch to CosePublicKey::from_cbor and a
parse_eddsa_ed25519 helper that produces an RFC 8410 SPKI DER. Wire
Ed25519PublicKeyDerWrappedCose through the standalone sig verifier and
the ingress validator's WebAuthn signature path. Per WebAuthn §6.5.6,
EdDSA WebAuthn signatures are 64 raw bytes (no DER wrapping), which
basic_sig_from_webauthn_sig now passes through unchanged.

Tests cover: parsing the canonical RFC 8032 TEST 1 keypair as a COSE
key, end-to-end signature verification through the parsed DER, parsing
a captured NitroKey 3A WebAuthn registration, and rejection of
Ed448 (crv=7) and short-x malformed keys. The validator's webauthn
module gets matching Ed25519 fixtures.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sea-snake sea-snake requested a review from Copilot May 4, 2026 13:35
@github-actions github-actions Bot added the feat label May 4, 2026
@sea-snake sea-snake marked this pull request as ready for review May 4, 2026 13:37
@sea-snake sea-snake requested a review from a team as a code owner May 4, 2026 13:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the IC WebAuthn/COSE public key handling to accept Ed25519 (COSE kty=OKP, alg=EdDSA, crv=Ed25519) keys, enabling WebAuthn authenticators that emit EdDSA keys to be validated end-to-end through the existing signature verification pipeline.

Changes:

  • Add Ed25519 support to the COSE parser, emitting RFC 8410 SPKI DER for Ed25519 public keys.
  • Propagate the new COSE-wrapped Ed25519 key content type through the standalone sig verifier and ingress validation WebAuthn path.
  • Add/extend tests for COSE Ed25519 parsing and WebAuthn Ed25519 signature verification.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
rs/validator/src/webauthn.rs Accept raw Ed25519 WebAuthn signatures and add Ed25519 validation tests.
rs/validator/src/ingress_validation.rs Treat COSE-wrapped Ed25519 keys as WebAuthn keys for request/delegation validation.
rs/crypto/standalone-sig-verifier/src/sign_utils.rs Introduce Ed25519PublicKeyDerWrappedCose and map AlgorithmId::Ed25519 accordingly.
rs/crypto/internal/crypto_lib/basic_sig/cose/tests/tests.rs Add COSE Ed25519 parsing/verifying tests and negative cases (unsupported curve / short x).
rs/crypto/internal/crypto_lib/basic_sig/cose/src/lib.rs Implement COSE OKP/EdDSA/Ed25519 parsing that returns RFC 8410 DER.
rs/crypto/internal/crypto_lib/basic_sig/cose/Cargo.toml Add ic-ed25519 dependency to the COSE crate.
rs/crypto/internal/crypto_lib/basic_sig/cose/BUILD.bazel Add Bazel dependency on //packages/ic-ed25519.
Cargo.lock Record the added ic-ed25519 dependency in the lockfile.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +165 to +168
/// Parse a COSE EdDSA / Ed25519 key
fn parse_eddsa_ed25519(fields: &CborMap) -> Result<Self, CosePublicKeyParseError> {
Self::verify_key_ops(fields)?;

Comment on lines +363 to +364
Blob(sig.authenticator_data().0),
Blob(sig.client_data_json().0),
@sea-snake
Copy link
Copy Markdown
Author

Superseded by #10081 (same changes, branch on dfinity/ic instead of fork).

@sea-snake sea-snake closed this May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants