Skip to content

fix(webauthn): authorize appid and appidExclude against caller origin #252

@AlfioEmanueleFresta

Description

@AlfioEmanueleFresta

Problem

The appid and appidExclude FIDO extensions are accepted and acted on without any FacetID/AppID authorization. The AppID URL is never compared to the caller origin or rp.id, so a caller from https://example.org can pass an AppID like https://example.net/u2f.

  • appid (auth). validate_appid in libwebauthn/src/ops/webauthn/get_assertion.rs only rejects empty strings and non-https:// URLs, and never sees the caller origin. The value feeds GetAssertionRequest::try_downgrade, which signs each credential under SHA-256(appid) on the U2F downgrade path. A cross-site AppID yields assertions under an unrelated site's legacy application parameter.
  • appidExclude (registration). Not validated at all. MakeCredentialRequest::from_idl_model passes extensions through verbatim. The value reaches ctap2_preflight_with_appid (libwebauthn/src/proto/ctap2/preflight.rs) via make_credential_fido2 (libwebauthn/src/webauthn.rs) and probes each excludeList credential under the AppID, enabling enumeration under another site's AppID.
  • appidExclude output missing. MakeCredentialResponse::build_client_extension_results never emits it. The field is commented out and AuthenticationExtensionsClientOutputsJSON in idl/response.rs has no appidExclude member. (The appid auth output is emitted correctly.)

Why it matters

WebAuthn L3 §10.1.1 (appid) and §10.1.2 (appidExclude) require the client to run the FacetID authorization check and return SecurityError when the AppID is not authorized. For web callers this is a same-site host comparison between the AppID and the caller origin. Without it, a site can borrow another site's legacy AppID to get assertions or enumerate excluded credentials.

What needs doing

  • Add a same-site check for appid: the AppID host must be a registrable-domain suffix of, or equal to, the caller origin host. Reuse is_registrable_domain_suffix_or_equal / rp_id_authorised, and wire the caller origin and settings into validate_appid.
  • Apply the same check to appidExclude in the make-credential path. Add an InvalidAppId variant to MakeCredentialPrepareError.
  • Map a rejected AppID to SecurityError at the WebAuthn boundary.
  • Emit the appidExclude Boolean output: add the field to AuthenticationExtensionsClientOutputsJSON and populate it in build_client_extension_results.
  • Tests: reject a cross-site AppID (caller https://example.org, AppID https://example.net/...) for both extensions, accept a same-site one (https://www.example.org/...), and assert appidExclude: true when a credential is excluded.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions