fix(webauthn): strip transports from CTAP2 PublicKeyCredentialDescriptor#192
Open
AlfioEmanueleFresta wants to merge 2 commits intomasterfrom
Open
fix(webauthn): strip transports from CTAP2 PublicKeyCredentialDescriptor#192AlfioEmanueleFresta wants to merge 2 commits intomasterfrom
AlfioEmanueleFresta wants to merge 2 commits intomasterfrom
Conversation
Both webauthn_hid and webauthn_json_hid now exercise the regression: the descriptor carries transports=[usb], which strict authenticators reject with CTAP2_ERR_INVALID_CBOR (0x12).
The transports field is a WebAuthn-level hint for the client; sending it inside the CBOR descriptor exposes a parser bug in cbor-smol 0.4.0 (used by Solo 2 firmware) where deserialize_ignored_any does not consume the value bytes, desyncing the next map read. The bug presents as CTAP2_ERR_INVALID_CBOR (0x12) and breaks GetAssertion preflight on affected devices, which then falls through to NoCredentials. Use skip_serializing on the field so we never emit it on the wire, while still tolerating it on deserialize. Matches Chromium and libfido2. Fixes #191.
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.
Summary
Strip the WebAuthn
transportsfield from the CTAP2PublicKeyCredentialDescriptorthat we send to authenticators. Fixes #191.
Why
transportsis a WebAuthn-level hint for the client about which transport totry (WebAuthn L3 §5.8.3,
§5.8.4). The CTAP spec
hyperlinks the WebAuthn dictionary, so the field is technically permitted on the
wire, but every reference platform omits it and at least one shipping
authenticator firmware breaks when it's present.
What other clients do
Chromium strips the field and the source has an explicit comment about it:
Source: public_key_credential_descriptor.cc (HEAD as of 2026-05-09). Reached from both
ctap_get_assertion_request.cc
(allowList) and
ctap_make_credential_request.cc
(excludeList).
libfido2 physically can't include it. The descriptor encoder is hard-coded
as a definite-size-2 map:
Source: src/cbor.c (libfido2 1.17.0). Reached from
fido_dev_get_assert_tx
and fido_dev_make_cred_tx.
What goes wrong on Solo 2 specifically
Issue #191's bug log is from a Solo 2 firmware r964 (release
2.964.0); Ialso reproduced on a Solo 2 r256 (firmware family
1.0.x). Both shipfido-authenticator = 0.1.xwhich pulls in
cbor-smol = 0.4.0.That cbor-smol has a bug in
deserialize_ignored_anythat breaks when serde-derivetries to skip an unknown map key whose value is non-empty:
The function returns
visit_none()without consuming the value bytes, so thenext
next_key_seedcall reads the descriptor's leftovertransportsvalue asthe next map key. The misread fails with
DeserializeBadMajor, whichctap-typesthen maps to
CTAP2_ERR_INVALID_CBOR (0x12)and that is what the device puts onthe wire. Reproduced in isolation:
How libwebauthn was hitting this
In 0.2.x the only path that built a
Ctap2PublicKeyCredentialDescriptorwasFrom<&AttestedCredentialData>(fido.rs:73), which hard-codedtransports: None. The 0.3.0 IDL/JSON parser atidl/get.rsandidl/create.rsis the first code path that propagates a JSON-supplied
transportsinto theCTAP type, and the existing
#[serde(skip_serializing_if = "Option::is_none")]then emits it on the wire whenever the JSON included it. Firefox does include
it, which is how the bug surfaced.
Changes
proto/ctap2/model.rs: change the descriptor'stransportsfield to#[serde(skip_serializing, default)]. Never emitted on the wire; stillaccepted on deserialize, since some authenticators include it in responses
even though CTAP doesn't require it. The field stays in the in-memory type
for U2F downgrade (
make_credential.rs:620).proto/ctap2/model.rstests: keep the existing happy-pathcredential_descriptor_serialization; addcredential_descriptor_serialization_strips_transports(proves a populatedtransportsis dropped on the wire) andcredential_descriptor_deserialization_accepts_transports(proves we stillparse it from a response).
examples/webauthn_hid.rsandexamples/webauthn_json_hid.rs: the exampleGetAssertion now includes the freshly-made credential in
allowListwithtransports = [Usb]/"transports": ["usb"]. Without the fix thesereproduce Invalid CBOR errors on libwebauthn 0.3.0 #191 on a Solo 2; with the fix they succeed.
Upstream status
The bug is in
cbor-smol = 0.4.x'sdeserialize_ignored_any: it callsvisit_none()without advancing the input cursor when serde-derive skips anunknown map key, so the next
next_key_seedreads the leftover value bytesas if they were the next key and fails. Already fixed upstream in
0326d75f(Oct 2024, released as cbor-smol 0.4.1 / 0.5.0) and adopted by
fido-authenticator 0.2.0.Reach by device:
cbor-smol1.0.9(Jan 2022)2.964.0(Aug 2022)v1.8.3(May 2025)The SoloKeys project has been dormant since
2.964.0; no firmware updateappears to be forthcoming. Nitrokey 3 ships
Nitrokey/fido-authenticator@v0.1.1-nitrokey.27,which forked fido-authenticator's deps and bumped to
cbor-smol = "0.5",transitively absorbing the fix.
The libwebauthn workaround in this PR is therefore the only fix that will
ever reach Solo 2 owners, and harmless on Nitrokey 3 / Yubikey (their
parsers already discarded the field). No code change is being requested
against
cbor-smol,fido-authenticator, orctap-types; all are correctat HEAD.
Note: the parser bug affects every CTAP CBOR map the device parses where an
unknown key's value is more than zero bytes, not just
transports. Otherclients sending other unknown keys would hit the same desync on a Solo 2.
Test plan
cargo test --lib136 existing tests pass; 2 new tests addedreverted (
InvalidCbor-> preflight filters all credentials ->NoCredentials)