Lion Cipher Lab
Verify FIDO2/WebAuthn attestation and enforce an authenticator allowlist with the FIDO Metadata Service
- Identity & Access Management
- Advanced
- ~90 min
- Free · no account required
Why it matters
The predecessor lab, gs-verify-webauthn-assertions-from-scratch, built a WebAuthn relying party that verifies an ASSERTION -- a login -- and proved it rejects replay, wrong-origin phishing, and cloned authenticators. That lab deliberately used attestation fmt='none', because a login does not need to know which device you hold. This lab tackles the distinct and harder REGISTRATION-time question: not 'is this the key that registered?' but 'WHICH authenticator model made this credential, and is that model one my organization allows to enroll at all?'. That is the ATTESTATION problem, and it is…
What you'll learn
- the difference between the two WebAuthn ceremonies that matters here: the predecessor lab (gs-verify-webauthn-assertions-from-scratch) verified an ASSERTION (login); this lab verifies the REGISTRATION-time ATTESTATION STATEMENT that proves WHICH authenticator model minted a credential
- parsing a packed attestationObject from scratch with a hand-rolled CBOR decoder: fmt='packed', the attStmt map (alg=-7, sig, x5c certificate chain), and authData
- verifying a packed FULL attestation: reconstruct the signed bytes as authenticatorData || SHA-256(clientDataJSON), verify attStmt.sig with the PUBLIC KEY in the x5c leaf certificate (not the credential key), and confirm alg is ES256
- reading the 16-byte AAGUID out of attestedCredentialData (authData byte 37..52) and formatting it as the UUID that keys the FIDO Metadata Service
- minting real X.509 certificates from scratch in Node -- a tiny ASN.1 DER encoder plus node:crypto signing -- so the whole attestation chain (vendor root -> attestation leaf) is reproducible offline with no library and no hardware
- building and verifying a signed FIDO Metadata Service BLOB (a compact JWS whose x5c header chains to the FIDO root), then indexing its entries by AAGUID -- exactly the shape of the real MDS3 BLOB
- enforcing an enterprise allowlist: permit registration only if the AAGUID is approved AND its MDS statusReports are acceptable AND the attestation leaf chains to a root MDS lists for that AAGUID
- reproducing the four named rejections yourself: fmt='none' (no vendor proof), an all-zero AAGUID (no model identity), a spoofed attestation whose leaf does not chain to the MDS-listed root, and an AAGUID carrying a REVOKED status report
- the judgment: attestation is an ENTERPRISE control, not a consumer one -- why most relying parties request attestation 'none' for privacy and synced passkeys, the basic-attestation batching tradeoff, and why production uses a conformant server plus a maintained MDS client
Steps in this lab
- The model: registration attestation vs. the login assertion you already verified
- Two codecs from scratch: a CBOR decoder and an ASN.1 DER encoder
- Mint a vendor CA and a software authenticator that emits a PACKED attestation
- Verify the packed attestation signature and read the AAGUID
- Build a signed FIDO Metadata Service BLOB (the shape of the real MDS3)
- Verify the BLOB against the pinned FIDO root, then index it by AAGUID
- Enforce the allowlist: internal validity + MDS trust + approved model
- Prove the four rejections: none, all-zero AAGUID, spoofed chain, revoked
- Optional: fetch the REAL MDS3 BLOB and confirm it is the same shape
- The judgment: when to require attestation, and when NOT to