feat(crypto): support Ed25519/EdDSA in COSE parser for WebAuthn#10081
Open
feat(crypto): support Ed25519/EdDSA in COSE parser for WebAuthn#10081
Conversation
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>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends the IC’s WebAuthn COSE public-key parsing/verification pipeline to accept Ed25519 (COSE kty=OKP, alg=EdDSA, crv=Ed25519) in addition to the existing ECDSA-P256 and RSA-SHA256 support, enabling authenticators that emit Ed25519 COSE keys (e.g., NitroKey 3A).
Changes:
- Add Ed25519/EdDSA parsing to the COSE key parser and return RFC 8410 SPKI DER for the embedded key.
- Thread a new COSE-wrapped Ed25519 key content type through
ic-crypto-standalone-sig-verifierandic-validatorto route such keys through the WebAuthn verification path. - Add end-to-end tests for parsing and verifying Ed25519 WebAuthn signatures, plus negative cases for unsupported curves/malformed keys/signatures.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| rs/crypto/internal/crypto_lib/basic_sig/cose/src/lib.rs | Add COSE OKP/EdDSA/Ed25519 parsing and map it to AlgorithmId::Ed25519 + RFC 8410 DER output. |
| rs/crypto/internal/crypto_lib/basic_sig/cose/tests/tests.rs | Add COSE Ed25519 parsing + verification tests (including NitroKey capture and malformed/unsupported inputs). |
| rs/crypto/internal/crypto_lib/basic_sig/cose/Cargo.toml | Add ic-ed25519 dependency for Ed25519 key decoding/DER serialization. |
| rs/crypto/internal/crypto_lib/basic_sig/cose/BUILD.bazel | Add Bazel dependency on //packages/ic-ed25519. |
| rs/crypto/standalone-sig-verifier/src/sign_utils.rs | Introduce KeyBytesContentType::Ed25519PublicKeyDerWrappedCose and map Ed25519 COSE-unwrapped keys to it. |
| rs/validator/src/ingress_validation.rs | Treat Ed25519 COSE-wrapped keys as WebAuthn keys in ingress/delegation validation. |
| rs/validator/src/webauthn.rs | Accept Ed25519 in WebAuthn signature handling and add validator-level Ed25519 WebAuthn tests. |
| Cargo.lock | Record the additional ic-ed25519 dependency in the lockfile. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address Copilot review on PR #10081: - `webauthn_sig.signature()` already returns an owned cloned `Blob`, so `.0.clone()` was an extra Vec clone. Move the Vec out instead with `webauthn_sig.signature().0`. - WebAuthn requires Ed25519 signatures to be exactly 64 bytes (W3C WebAuthn-2 §6.5.6). Reject other lengths early in the validator with a clear error rather than relying on the crypto verifier's generic "Invalid length" message. Tests: tighten the malformed-signature test to assert the new error message text instead of just `is_err()`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Author
|
Addressed Copilot review (99ee480):
|
eichhorl
reviewed
May 5, 2026
Contributor
There was a problem hiding this comment.
Thanks @sea-snake, is there a corresponding interface spec PR?
Some ideas for more tests:
- Extend
rs/validator/src/ingress_validation/tests.rswith a test validating Ed25519 - Incorrect public key should be rejected in
rs/validator/src/webauthn.rs - Incorrect signature with correct length should be rejected in
rs/validator/src/webauthn.rs - Parsing of the new
Ed25519PublicKeyDerWrappedCosevariant inrs/crypto/tests/request_id_signatures.rs - Extend the
rs/tests/crypto/ingress_verification_test.rssystem test with the new scheme
| } | ||
|
|
||
| fn basic_sig_from_webauthn_sig( | ||
| webauthn_sig: &&WebAuthnSignature, |
Contributor
There was a problem hiding this comment.
Not your change, but does this work?
Suggested change
| webauthn_sig: &WebAuthnSignature, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The IC's COSE parser at
rs/crypto/internal/crypto_lib/basic_sig/cose/src/lib.rsonly accepts ECDSA-P256 (kty=EC2,alg=ES256) and RSA-PKCS1-SHA256 (kty=RSA,alg=RS256) keys. WebAuthn authenticators that producekty=OKP,alg=EdDSA,crv=Ed25519keys (e.g. NitroKey 3A) are rejected with the misleading error:The "Unspecified" comes from the wrapper at
parse_cose_public_key, which hardcodesAlgorithmId::Unspecifiedfor anyAlgorithmNotSupportedoutcome — so the actual EdDSAalgvalue 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 theed25519_algorithm_identifier()path inuser_public_key_from_bytesand are verified viaverify_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 thecose_algorithm_identifier()branch, which callsparse_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_opsor other extension fields), confirming the root cause is the parser's algorithm allowlist, not extra entries.Changes
cosecrateCosePublicKey::Ed25519(Vec<u8>)variant.parse_eddsa_ed25519helper that decodeskty=OKP / alg=EdDSA / crv=Ed25519 / x=<32 bytes>and returns the RFC 8410 SPKI DER viaic_ed25519::PublicKey::deserialize_raw().serialize_rfc8410_der().CosePublicKey::from_cbor.ic-ed25519to the crate'sCargo.tomlandBUILD.bazel.ic-crypto-standalone-sig-verifierKeyBytesContentType::Ed25519PublicKeyDerWrappedCose.AlgorithmId::Ed25519to it incose_key_bytes_content_type.ic-validatoringress_validation.rs: includeEd25519PublicKeyDerWrappedCosein the WebAuthn signature path (both invalidate_signed_requestandvalidate_signed_delegation).webauthn.rs: add anAlgorithmId::Ed25519arm tobasic_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 ofcrv=Ed448and short-x malformed keys.validator/src/webauthn.rs: a newed25519test module mirroring the existingecdsaandrsamodules, with a valid-signature, wrong-message, and malformed-signature test. Updates the existingshould_return_error_if_algorithm_id_is_not_supportedtest (which previously asserted Ed25519 was unsupported) to useAlgorithmId::EcdsaSecp256k1instead.Verification
All clean locally. Bazel build/test was not run in the development environment — please verify in CI.
Related