Skip to content

fix(webauthn): refuse U2F downgrade when credProtect enforce or largeBlob required#201

Open
AlfioEmanueleFresta wants to merge 2 commits into
masterfrom
fix/u2f-downgrade-credprotect-largeblob
Open

fix(webauthn): refuse U2F downgrade when credProtect enforce or largeBlob required#201
AlfioEmanueleFresta wants to merge 2 commits into
masterfrom
fix/u2f-downgrade-credprotect-largeblob

Conversation

@AlfioEmanueleFresta
Copy link
Copy Markdown
Member

@AlfioEmanueleFresta AlfioEmanueleFresta commented May 10, 2026

When the platform negotiates CTAP1/U2F (because the authenticator does not speak CTAP2), is_downgradable() was permitting the downgrade even for requests that asked for capabilities U2F cannot provide: an RP that set enforceCredentialProtectionPolicy=true with userVerificationRequired would end up with an unprotected U2F credential, and an RP that asked for largeBlob: required would get a credential with no blob storage. Both are silent capability strips with no signal to the RP.

This change makes is_downgradable() refuse the downgrade for those two cases, and surfaces the same enforcement for largeBlob: required on the FIDO2 path when the authenticator does not advertise the extension (previously a warn!-only path), matching the existing credProtect handling immediately above it.

Changes

  • MakeCredentialRequest::is_downgradable() returns false when extensions.cred_protect.enforce_policy is set with a policy stricter than UserVerificationOptional.
  • MakeCredentialRequest::is_downgradable() returns false when extensions.large_blob.support == Required.
  • Ctap2MakeCredentialsRequestExtensions::from_webauthn_request returns Err(CtapError::UnsupportedExtension) for largeBlob: required against devices without the largeBlobs option.

References

@AlfioEmanueleFresta AlfioEmanueleFresta force-pushed the fix/u2f-downgrade-credprotect-largeblob branch from be87913 to 596d733 Compare May 10, 2026 20:29
…eBlob required

MakeCredentialRequest::is_downgradable() previously rejected only when ES256
was unsupported, resident_key was Required, or user_verification was Required.
Requests carrying credProtect with enforce_policy or largeBlob: required were
silently downgraded into U2F credentials that could not honour those
guarantees.

Extend the predicate to also refuse the downgrade when:
- extensions.cred_protect.enforce_policy is true and policy is stricter than
  UserVerificationOptional;
- extensions.large_blob.support == Required.

Both checks mirror the FIDO2 path's existing enforcement in
Ctap2MakeCredentialsRequestExtensions::from_webauthn_request.

Refs: WebAuthn L3 section 10.1.5; CTAP 2.1 sections 10.2 and 12.1.
…lacks largeBlobs

Previously the FIDO2 path emitted a warn! when extensions.largeBlob.support
== Required and the device did not advertise the largeBlobs option, then
forwarded the request as if largeBlob were preferred. The device returned
success but ignored the extension, leaving the caller with no signal that
the requested extension was dropped.

Return Err(CtapError::UnsupportedExtension) in this case, mirroring the
existing credProtect enforcement immediately above.

Also re-export MakeCredentialLargeBlobExtensionInput from ops::webauthn so
the type required to construct MakeCredentialsRequestExtensions::large_blob
is part of the public API surface.

Refs: WebAuthn L3 section 10.1.5; CTAP 2.1 section 12.1.
@AlfioEmanueleFresta AlfioEmanueleFresta force-pushed the fix/u2f-downgrade-credprotect-largeblob branch from 596d733 to 690e326 Compare May 12, 2026 18:38
@AlfioEmanueleFresta AlfioEmanueleFresta marked this pull request as ready for review May 12, 2026 18:38
Copy link
Copy Markdown
Collaborator

@msirringhaus msirringhaus left a comment

Choose a reason for hiding this comment

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

LGTM

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.

2 participants