Skip to content

fix(ctap1): emit Le on U2F APDUs to support case 4#200

Open
AlfioEmanueleFresta wants to merge 2 commits into
masterfrom
fix/ctap1-apdu-le
Open

fix(ctap1): emit Le on U2F APDUs to support case 4#200
AlfioEmanueleFresta wants to merge 2 commits into
masterfrom
fix/ctap1-apdu-le

Conversation

@AlfioEmanueleFresta
Copy link
Copy Markdown
Member

@AlfioEmanueleFresta AlfioEmanueleFresta commented May 10, 2026

U2F REGISTER and AUTHENTICATE produce a response payload, so per FIDO U2F Raw Message Formats v1.2 section 3.1 the request APDU must include Le (omitting Le is reserved for instructions not expected to yield response bytes). libwebauthn was setting response_max_length on the ApduRequest but both encoders silently dropped it:

  • ApduRequest::raw_long stopped after the Lc + data field. With response_max_length = Some(0x100) the byte stream came out as a Case 3 extended APDU (no Le).
  • The NFC From<&ApduRequest> for Command used Command::new_with_payload, which does not carry Le at all.

Strict authenticators reject Case 3 register/authenticate. The in-tree virtual fido-authenticator is lenient about it, so CI never caught the gap.

Changes

  • ApduRequest::raw_long now appends a 2-byte big-endian Le when response_max_length is Some(_), matching the extended-length encoding Le1 Le2 from FIDO U2F Raw Message Formats v1.2 section 3.1.3. Values >= 65536 collapse to 0x0000 (the spec's wildcard for Ne = 65536). This is the path used by HID, BLE, and caBLE, all of which mandate extended-length encoding (FIDO U2F HID v1.2 section 2.4; FIDO U2F BT v1.2 sections 5.1 and 5.2).
  • NFC From<&ApduRequest> for Command switches to Command::new_with_payload_le, so response_max_length is propagated through apdu-core. With longer_payloads disabled (the default in this crate), apdu-core writes Le as a single byte; for the typical request with response_max_length = 256 this yields Le = 0x00, which is short-form Case 4 and valid under FIDO U2F NFC v1.2 section 3 ("either short or extended length APDU encoding is allowed").
  • Unit tests cover the Case 3 / Case 4 encoding for both raw_long and the NFC encoder, including the wildcard Le = 0x0000.

Spec references

U2F REGISTER and AUTHENTICATE are Case 4 APDUs per FIDO U2F Raw
Message Formats v1.2 sections 3 and 4. The encoder previously stopped
after the Lc + data field, silently dropping the response_max_length
that callers were setting. Strict authenticators reject these as
Case 3 (no response expected) requests.

Append a 2-byte big-endian Le when response_max_length is Some, with
values >= 65536 encoded as the 0x0000 wildcard.
The From<&ApduRequest> for Command impl was calling
Command::new_with_payload, which produced a Case 3 APDU and dropped
the response_max_length field. U2F REGISTER and AUTHENTICATE require
a Case 4 APDU per FIDO U2F NFC section 3.1.

Switch to new_with_payload_le so Le is included when the request asks
for a response. For the typical short-form request (le=256), apdu-core
encodes Le as a single 0x00 byte.
@AlfioEmanueleFresta AlfioEmanueleFresta marked this pull request as ready for review May 12, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant