feat(webauthn): authenticatorLargeBlobs + LargeBlobStorage trait#206
Draft
AlfioEmanueleFresta wants to merge 4 commits into
Draft
feat(webauthn): authenticatorLargeBlobs + LargeBlobStorage trait#206AlfioEmanueleFresta wants to merge 4 commits into
AlfioEmanueleFresta wants to merge 4 commits into
Conversation
4f484fb to
63fd2d2
Compare
1129c2d to
5a50879
Compare
AlfioEmanueleFresta
added a commit
that referenced
this pull request
May 12, 2026
Per review on #198: keep the per-credential largeBlobKey only on the CTAP-level Ctap2GetAssertionResponse. Surfacing it on the public Assertion struct gives callers a foot-gun to forward straight to the RP, which is exactly the disclosure this PR is meant to prevent. The follow-up authenticatorLargeBlobs PR (#206) can read the key directly off the CTAP response.
63fd2d2 to
34571aa
Compare
AlfioEmanueleFresta
added a commit
that referenced
this pull request
May 12, 2026
Per review on #198: keep the per-credential largeBlobKey only on the CTAP-level Ctap2GetAssertionResponse. Surfacing it on the public Assertion struct gives callers a foot-gun to forward straight to the RP, which is exactly the disclosure this PR is meant to prevent. The follow-up authenticatorLargeBlobs PR (#206) can read the key directly off the CTAP response.
34571aa to
5388273
Compare
AlfioEmanueleFresta
added a commit
that referenced
this pull request
May 12, 2026
Per [WebAuthn L3 sec. 10.1.5 (largeBlob extension)](https://www.w3.org/TR/webauthn-3/#sctn-large-blob-extension), the relying party expects the `blob` output to be the decrypted plaintext blob payload, fetched by the platform via the CTAP [`authenticatorLargeBlobs`](https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorLargeBlobs) command using the per-credential `largeBlobKey` as an AES-256-GCM key. The library was instead writing the raw `largeBlobKey` into `blob` and never calling `authenticatorLargeBlobs`. That means an RP receives the AES key itself (not the blob), and if the RP can also read the device's `largeBlobArray` (publicly readable region of the authenticator over CTAP), it can decrypt and forge entries. Until `authenticatorLargeBlobs` is wired up (follow-up PR #206), the safe behaviour is to set `large_blob.blob = None`. The CTAP-level model keeps the field so the follow-up can use it. ## Changes - Stop routing `largeBlobKey` into the WebAuthn `large_blob.blob` output. - The CTAP-level `Ctap2GetAssertionResponse.large_blob_key` remains so the next PR can use it. - Regression test asserts the WebAuthn response no longer contains the key. ## References - [WebAuthn L3 sec. 10.1.5: Large blob storage extension (`largeBlob`)](https://www.w3.org/TR/webauthn-3/#sctn-large-blob-extension) - [CTAP 2.1 sec. 6.10: `authenticatorLargeBlobs` (0x0C)](https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorLargeBlobs)
Implements the wire-level model and protocol method for CTAP 2.1 `authenticatorLargeBlobs` (command code 0x0C, spec §6.10). This is the device-side primitive the platform uses to fetch and update the authenticator's serialized largeBlobArray. Includes only the `get` request shape so far; `set` is reserved for a follow-up that will also handle the pinUvAuthParam binding required for writes. Refs: CTAP 2.2 §6.10.
…ckends Adds the public `LargeBlobStorage` async trait alongside two bundled implementations: - `MemoryLargeBlobStorage`: a HashMap-backed store, primarily for tests. - `AuthenticatorLargeBlobStorage<'_, C>`: drives the CTAP 2.1 `authenticatorLargeBlobs(get)` command, parses the serialized largeBlobArray, locates the entry matching the supplied per-credential `largeBlobKey` (AES-256-GCM authenticated decryption), and decompresses the deflated plaintext. Only the read path is implemented in this PR. `LargeBlobStorage::write` returns `Unsupported` in both bundled backends; the chunked write path with `pinUvAuthParam` binding is reserved for a follow-up. Includes 14 unit tests covering: in-memory round-trip, AEAD round-trip, wrong-key rejection, multi-entry array selection, corrupted/truncated array rejection, empty array handling, and a MockChannel-backed end-to-end test of the authenticator read flow. Refs: WebAuthn L3 §10.5, CTAP 2.2 §6.10 / §6.10.4 / §11.4.
When the WebAuthn `largeBlob: { read: true }` extension is requested and
the authenticator returns a per-credential `largeBlobKey`, libwebauthn
now runs `authenticatorLargeBlobs(get)` to fetch the on-device serialized
array, decrypts the matching entry, and exposes the plaintext via the
WebAuthn response's `unsigned_extensions_output.large_blob.blob` field.
The read flow uses a per-assertion `AuthenticatorLargeBlobStorage` handle
(introduced in the previous commit), so each credential is read against
its own `largeBlobKey`. Failures are non-fatal: per WebAuthn L3 §10.5 the
`blob` output is optional on success.
Combined with the earlier fix that removed the key-disclosure bug, this
completes the read half of the WebAuthn `largeBlob` extension.
Refs: WebAuthn L3 §10.5, CTAP 2.2 §6.10.
5a50879 to
33be166
Compare
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.
Implements the read half of the WebAuthn L3
largeBlobextension end-to-end. Stacked on top of #198 (the safe-drop fix); read this PR's diff against that branch.Why
largeBloblets a relying party read or write a small per-credential payload that lives on the authenticator. The CTAP 2.1authenticatorLargeBlobscommand (0x0C) exposes a single device-wide serialized array; per-credential entries within that array are encrypted under thelargeBlobKeyreturned byauthenticatorGetAssertion. Until this PR, libwebauthn could only ask the authenticator to surface alargeBlobKey; we had no way to actually fetch and decrypt the blob, so the WebAuthnunsigned_extensions_output.large_blob.blobfield stayedNone.What this PR does
src/proto/ctap2/model/large_blobs.rs,src/proto/ctap2/protocol.rs): addsCtap2CommandCode::AuthenticatorLargeBlobs = 0x0C, request/response models, and actap2_large_blobsmethod on theCtap2trait.LargeBlobStoragetrait (src/ops/webauthn/large_blob.rs): asyncread/write, so callers can plug in alternative backends. Two backends ship:MemoryLargeBlobStorage: in-memoryHashMap, useful for tests.AuthenticatorLargeBlobStorage<'_, C>: drives paginatedauthenticatorLargeBlobs(get), AES-256-GCM-authenticates each entry under the suppliedlargeBlobKey, and RFC 1951 raw-deflate-decompresses the plaintext.src/webauthn.rs): after_webauthn_get_assertion_fido2collects assertions, iflargeBlob.read = truewas requested and the device returned alargeBlobKey, libwebauthn now runs the CTAP fetch-and-decrypt and populatesunsigned_extensions_output.large_blob.blob. Failures here are non-fatal: per WebAuthn L3 §10.1.5 theblobfield is absent when the read cannot complete.What is deferred
The write half is intentionally a follow-up PR. It will:
authenticatorLargeBlobs(set)withpinUvAuthParambinding per CTAP 2.1 §6.10.2 / §6.10.5 (compute the SHA-256 trailer, paginate the upload, sign each chunk with the platform-derivedpinUvAuthParam).LargeBlobStorage::writeonAuthenticatorLargeBlobStorage(currentlyLargeBlobError::Unsupported).largeBlob.writeextension behaviour on top ofwebauthn_make_credential/webauthn_get_assertion.libwebauthn-testscrate).Tests
The PR adds 16 unit tests, including a
MockChannel-driven end-to-end test ofwebauthn_get_assertionwithlargeBlob.read = true: the test builds a real serialized largeBlobArray (AES-256-GCM ciphertext + nonce + origSize + SHA-256 trailer), responds to the GetInfo / GetAssertion / authenticatorLargeBlobs(get) sequence, callswebauthn_get_assertion, and asserts that the returnedunsigned_extensions_output.large_blob.blobmatches the original plaintext. That covers the entire read pipeline: CTAP exchange, array parsing, AES-256-GCM decrypt, deflate decompress, trait surface, and WebAuthn JSON output.Other tests cover:
MemoryLargeBlobStorageround-trip / missing-credential / multi-credential; AES-256-GCM round-trip and wrong-key skip; multi-entry array selection; corrupted-trailer and truncated-array rejection; empty-array handling;Ctap2LargeBlobsRequestcanonical CBOR encoding; anAuthenticatorLargeBlobStorage::readintegration test against a hand-crafted ciphertext viaMockChannel; andAuthenticatorLargeBlobStorage::writereturningUnsupported.Dependencies
aes-gcm = "0.10"(AES-256-GCM AEAD)flate2 = "1.0"(RFC 1951 raw deflate)Spec references
largeBlobextension): https://www.w3.org/TR/webauthn-3/#sctn-large-blob-extensionauthenticatorLargeBlobs): https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorLargeBlobs