Skip to content

refactor(webauthn): use RequestOrigin instead of RelyingPartyId in WebAuthnIDL (fix #185)#188

Merged
AlfioEmanueleFresta merged 5 commits into
masterfrom
origin-parsing
May 12, 2026
Merged

refactor(webauthn): use RequestOrigin instead of RelyingPartyId in WebAuthnIDL (fix #185)#188
AlfioEmanueleFresta merged 5 commits into
masterfrom
origin-parsing

Conversation

@AlfioEmanueleFresta
Copy link
Copy Markdown
Member

@AlfioEmanueleFresta AlfioEmanueleFresta commented May 9, 2026

Motivation

Closes #185.

WebAuthnIDL::from_json currently takes a &RelyingPartyId, which forces callers (e.g. credentialsd) to override request.origin and request.cross_origin after parsing because the bare host is not a valid origin string and we have no place to record the top-level origin. This replaces that parameter with a RequestOrigin that carries the actual origin context, so the parsed request comes out correct without a post-parse fixup.

What changes

  • New Origin and RequestOrigin types in libwebauthn::ops::webauthn. Origin is a struct with host: OriginHost and port: Option<u16>; RequestOrigin wraps it with an optional top_origin. Convenience constructors: RequestOrigin::new(origin), RequestOrigin::new_cross_origin(origin, top_origin), RequestOrigin::try_from(&str | String) for one-shot parsing of "https://host[:port]".
  • Host validation goes through url::Host so we follow the WHATWG URL Standard host parser (domain / IPv4 / bracketed IPv6). Errors are wrapped into a local HostParseError / OriginParseError so the url crate's error type does not leak into the public API.
  • WebAuthnIDL::from_json and FromIdlModel::from_idl_model now take &RequestOrigin. The parsed request.origin is the full URL string ("https://example.org", no longer the bare host), and request.top_origin: Option<String> replaces the old cross_origin: Option<bool>.
  • ClientData drops cross_origin: Option<bool> and derives crossOrigin in the JSON from top_origin.is_some(). topOrigin is now emitted when present.
  • Bumps libwebauthn to 0.4.0 since the public from_json signature and the request struct fields are breaking changes.

Intentional non-changes

Test plan

  • cargo build -p libwebauthn and cargo build -p libwebauthn --features virt
  • cargo build --workspace --all-targets --all-features
  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace (149 tests, 14 new origin parser tests)
  • cargo publish --dry-run -p libwebauthn (no working-tree hacks)

@AlfioEmanueleFresta AlfioEmanueleFresta changed the base branch from master to release-v0.3.1 May 9, 2026 00:17
@AlfioEmanueleFresta AlfioEmanueleFresta changed the title Use RequestOrigin instead of RelyingPartyId in WebAuthnIDL (fix #185) refactor(webauthn): use RequestOrigin instead of RelyingPartyId in WebAuthnIDL (fix #185) May 9, 2026
@AlfioEmanueleFresta AlfioEmanueleFresta marked this pull request as ready for review May 9, 2026 01:14
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.

I could imagine http:// being used in some self-hosting env, but I'm ok leaving it out for now.

I'm in general feeling a bit uneasy about writing all the parsing ourselves, but if there is nothing suitable available (e.g. by Mozilla/servo), then so be it.

One inline comment needs clarification, otherwise LGTM.

Comment thread libwebauthn/src/ops/webauthn/idl/origin.rs Outdated
@swissbit-jpthiers
Copy link
Copy Markdown

I could imagine http:// being used in some self-hosting env, but I'm ok leaving it out for now.

Usage of http:// for connection to relying party probably breaks the origin binding and thereby fishing-resistance due to missing origin verification capability.

@iinuwa
Copy link
Copy Markdown
Member

iinuwa commented May 11, 2026

I could imagine http:// being used in some self-hosting env, but I'm ok leaving it out for now.

Yeah, that's a good callout. http://localhost (and any port) is considered a secure context, so we should probably also allow it. I'm fine to follow this up in a later PR though.

Usage of http:// for connection to relying party probably breaks the origin binding and thereby fishing-resistance due to missing origin verification capability.

The RP should still be able to detect the mismatched origin, so phishing resistance would be preservezd. But I agree, this should be restricted to localhost for defense in depth.

Base automatically changed from release-v0.3.1 to master May 12, 2026 16:34
@AlfioEmanueleFresta
Copy link
Copy Markdown
Member Author

Thank you for your feedback!

  • Using Url::Parse now, and allowing http://localhost and its IPv4/6 variants. This also removes index slicing/panics.
  • Also normalising away default ports (https + 443, http + 80).

@AlfioEmanueleFresta AlfioEmanueleFresta merged commit 17e91f6 into master May 12, 2026
4 checks passed
@AlfioEmanueleFresta AlfioEmanueleFresta deleted the origin-parsing branch May 12, 2026 17:02
AlfioEmanueleFresta added a commit that referenced this pull request May 12, 2026
… (#190)

Closes #187. Stacked on #188.

## What

Replaces the strict-equality check between the JSON request's `rp.id`
and the origin's effective domain with the spec-correct "registrable
domain suffix of or equal to" relation from HTML §6.5 (referenced by
WebAuthn L3 §5.1.3 step 7 and §5.1.7 step 9).

Net effect: `rp.id = "example.org"` now works against an origin of
`https://login.example.org`, while `rp.id = "co.uk"` against
`https://example.co.uk` is still correctly rejected because `co.uk` is a
public suffix.

## PSL strategy

Per the design discussion in #173 (libwebauthn should not bundle and
manage its own PSL):

- Adds a `PublicSuffixList` trait with sync `registrable_domain` /
`public_suffix` methods.
- Provides a `DatFilePublicSuffixList` impl that reads a Public Suffix
List `.dat` file at construction time. `from_system_file()` reads the
standard `/usr/share/publicsuffix/public_suffix_list.dat`, kept current
by the system package manager.
- No PSL data is bundled in the libwebauthn crate.
- `publicsuffix = "1.5"`. Same crate as #173, just used offline.

## Plumbing into the parsing API

`WebAuthnIDL::from_json` and `FromIdlModel::from_idl_model` now take an
additional `psl: &dyn PublicSuffixList` parameter. This is the simplest
shape we discussed - explicit, no hidden state on `RequestOrigin`. Yes,
it's another breaking signature change on top of #188; the 0.4.0 release
is already breaking, so we eat the cost once.

Origin parsing (`Origin::from_str`, `RequestOrigin::try_from`) stays
purely syntactic. PSL is only consulted at validation time.

## Commits

1. Add `PublicSuffixList` trait and `DatFilePublicSuffixList` impl.
2. Use `PublicSuffixList` for the registrable-suffix check in
`from_idl_model`.

## Tests

- Unit tests on the suffix-check helper using a small
`MockPublicSuffixList` (recognises `com`, `co.uk`, `org`, `net`).
- Unit tests on the mock itself.
- 2 new positive integration tests on `from_json` for both
`MakeCredentialRequest` and `GetAssertionRequest`:
rp.id-as-parent-registrable-suffix accepted, rp.id-as-eTLD rejected.

## Test plan

- [x] `cargo build --workspace --all-targets --all-features`
- [x] `cargo fmt --all -- --check`
- [x] `cargo clippy --workspace --all-targets --all-features -- -D
warnings`
- [x] `cargo test --workspace` (151 tests)
- [x] `cargo publish --dry-run -p libwebauthn`
AlfioEmanueleFresta added a commit that referenced this pull request May 12, 2026
- GetAssertionRequest.cross_origin renamed to top_origin (#188)
- Assertion no longer carries large_blob_key (#198); thread it
  through from Ctap2GetAssertionResponse instead
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.

WebAuthnIDL to use Origin rather than RelyingPartyId

4 participants