Skip to content

Fix RP ID validation to process PSL wildcard and exception rules#280

Merged
dainnilsson merged 1 commit intoYubico:mainfrom
dorakemon:fix/psl-wildcard
Mar 10, 2026
Merged

Fix RP ID validation to process PSL wildcard and exception rules#280
dainnilsson merged 1 commit intoYubico:mainfrom
dorakemon:fix/psl-wildcard

Conversation

@dorakemon
Copy link
Contributor

Summary

verify_rp_id() in fido2/rpid.py is intended to prevent public suffixes from being used as RP IDs. However, it does not process the 160 wildcard rules or 8 exception rules in the bundled public suffix list.

Bug

The PSL contains entries like *.bd (meaning any X.bd is a public suffix) and !www.ck (exception: www.ck is NOT a public suffix despite the *.ck wildcard). The current code loads these as literal strings and only checks exact membership:

# "*.bd" is in suffixes, but "example.bd" is not
# So rp_id="example.bd" passes the check incorrectly
if host and host.endswith("." + rp_id) and rp_id not in suffixes:
    return True

Impact

An attacker controlling evil.example.bd can set RP ID to example.bd (a public suffix under the *.bd rule). This is equivalent to using com as an RP ID — it allows one tenant of a shared domain space to claim the entire parent domain.

Affected: 160 wildcard TLDs including .bd, .ck, .er, .fk, .jm, .np, .mm, .pg, .nom.br, .sch.uk, and 7 Japanese city domains (*.kawasaki.jp, *.yokohama.jp, *.kobe.jp, *.nagoya.jp, *.sapporo.jp, *.sendai.jp, *.kitakyushu.jp).

Fix

Parse wildcard (*.X) and exception (!X) entries into separate sets, and implement the standard PSL matching algorithm:

def _is_public_suffix(domain: str) -> bool:
    if domain in _exception_set:
        return False
    if domain in _suffix_set:
        return True
    parts = domain.split(".", 1)
    if len(parts) == 2 and parts[1] in _wildcard_set:
        return True
    return False

The suffixes list is preserved for backward compatibility.

PoC

from fido2.rpid import verify_rp_id

# Before fix: all return True (BUG - should be False)
# After fix: all return False (CORRECT)
print(verify_rp_id("example.bd", "https://evil.example.bd"))
print(verify_rp_id("example.ck", "https://evil.example.ck"))
print(verify_rp_id("example.np", "https://evil.example.np"))

# Exception rule: www.ck is NOT a public suffix (should return True)
print(verify_rp_id("www.ck", "https://sub.www.ck"))  # True (correct)

Spec Reference

@dorakemon
Copy link
Contributor Author

dorakemon commented Mar 5, 2026

I found this library cannot parse these kinds of psl so I fixed it.

@dainnilsson dainnilsson merged commit ccf6224 into Yubico:main Mar 10, 2026
22 of 25 checks passed
@dainnilsson
Copy link
Member

Thanks!

@dorakemon dorakemon deleted the fix/psl-wildcard branch March 10, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants