AAGUID attestation: restricting which authenticators count
An employee enrols a personal phone as a second factor on a production admin account. The registration succeeds, the policy document forbids it, and nothing in the system noticed the difference.
How do you enforce a corporate hardware-key policy?
With attestation, verified rather than read. WebAuthn accepts any conforming authenticator by default, so a deployment that never requests attestation has no authenticator policy at all — regardless of what the policy document says.
- Attestation is opt-in. Registration succeeds without it, so a deployment that never asks has no authenticator policy at all.
- The AAGUID identifies the authenticator model, not the device or the user, and is only trustworthy when the attestation statement verifies to a known root.
- Distinctness — knowing two credentials are on different hardware — matters more than brand restriction for multi-approver controls.
Part of Privileged identity and account recovery
What you get when you do not ask
A registration ceremony with attestation: "none" — the default in most libraries — returns a public key and a credential identifier. The AAGUID is present but zeroed, and nothing about the authenticator's provenance is asserted.
That is an appropriate default for consumer services, where authenticator choice belongs to the user. It is the wrong default for an enterprise that has written a hardware policy.
| Attestation setting | What the server learns |
|---|---|
| none | A credential exists. Nothing about what holds it. |
| indirect | An attestation statement, possibly anonymised by the client |
| direct | The authenticator's attestation statement, including its AAGUID and certificate chain |
| enterprise | Additionally, an individually identifying attestation — requires platform configuration and explicit policy |
Verifying, not just reading
The mistake that makes attestation worthless is reading the AAGUID out of the authenticator data without verifying the attestation statement that vouches for it.
# WRONG — the AAGUID is self-asserted data in a structure
# the client supplied.
aaguid = auth_data[37:53]
if aaguid in APPROVED_MODELS:
accept()
# RIGHT — verify the statement first, then trust the AAGUID
# it covers.
stmt = response.attestation_statement
verify_signature(stmt, over=auth_data + client_data_hash)
chain = verify_chain(stmt.x5c, roots=fido_metadata_roots)
require(chain.valid)
meta = metadata_for(auth_data.aaguid) # FIDO MDS entry
require(meta is not None)
require(meta.status not in REVOKED_STATUSES)
require(auth_data.aaguid in APPROVED_MODELS)
require(auth_data.flags.uv) # user verification performed
accept()
Without the chain verification, the check is a string comparison against a value the client controls.
The metadata service and why status matters
The FIDO Metadata Service publishes entries for certified authenticator models, including their attestation roots and certification status. Statuses change: models get revoked, attestation keys get compromised, firmware issues get disclosed.
A deployment that pins an allow-list of AAGUIDs and never refreshes metadata will keep accepting a model after its attestation root has been compromised. Refreshing the metadata blob on a schedule is unglamorous operational work that most implementations skip.
The property that matters more than brand
Enterprises usually reach for attestation to enforce "approved models only". That is a reasonable procurement control and a weak security control, because an approved model in the wrong hands is still an approved model.
The stronger use is distinctness. Where a control requires two approvers, you want assurance that two signatures came from two separate pieces of hardware held by two people — not two credentials synced into one person's account.
| Control | What attestation contributes |
|---|---|
| Approved hardware only | Model-level restriction; procurement enforcement |
| Two distinct approvers | Evidence the credentials are on separate, non-syncing authenticators |
| Hardware-bound key material | Confirmation the key cannot be exported or synced |
| Revoked model exclusion | Rejection of models whose attestation is no longer trusted |
The second and third rows are what make a dual-approval control meaningful. Without them, "two signatures" can mean one person with two synced passkeys.
The privacy and practicality limits
- AAGUID is model-level. It identifies a product line, not a device. Millions of authenticators share one AAGUID, which is the intended privacy property.
- Enterprise attestation is restricted. Individually identifying attestation requires platform policy configuration and is deliberately hard to obtain, because it is a tracking vector.
- Platform authenticators vary. Attestation behaviour differs across operating systems and versions, and an allow-list that works on one platform may block legitimate users on another.
- Allow-lists are maintenance. New models ship, old ones are discontinued, and an unmaintained list becomes a support burden.
A workable policy
Tier it by what the credential can authorise, rather than applying one rule everywhere.
- General workforce sign-in: attestation none or indirect. Let people use what they have; the goal is coverage.
- Elevated access: direct attestation, verified chain, hardware-bound key material required.
- Money movement and dual approval: direct attestation plus distinctness checks between approvers.
- Everything: refresh FIDO metadata on a schedule and reject revoked models.
The last bullet is the one to implement first, because it is cheap and its absence silently degrades everything above it.
The property that matters more than brand
| Use | Value |
|---|---|
| Approved models only | Procurement enforcement; weak as a security control |
| Distinctness between approvers | Makes a two-approver control mean something |
| Hardware-bound key material | Confirms the key cannot be exported or synced |
| Revoked model exclusion | Rejects models whose attestation is no longer trusted |
Without the second row, ‘two signatures’ can mean one person holding two synced passkeys. That is the failure a dual-approval control is specifically meant to prevent.
Objections and honest limits
“Attestation should be required everywhere.” It adds friction and platform inconsistency. Tier it: none for general sign-in, direct attestation for elevated access and dual-approval controls.
“An allow-list is maintenance we do not want.” Then start with the metadata refresh alone, which is cheap and whose absence silently degrades every other check.
Attestation that actually works
- Verify the attestation statement before trusting the AAGUID. Signature, then chain to a known root.
- Refresh FIDO metadata on a schedule. Cheapest step, and the one most often skipped.
- Reject revoked models. Status changes; an unrefreshed allow-list keeps accepting them.
- Use distinctness for dual approval. So two signatures cannot be two synced credentials.
Terms used here
- AAGUID
- An identifier for an authenticator model, not a device or a person — which is a deliberate privacy property.
- Attestation statement
- The authenticator's signed claim about itself, which must be verified to a known root before its contents mean anything.
- Distinctness
- Evidence that two signatures came from separate hardware rather than two credentials in one person's hands.
Frequently asked questions
Does AAGUID identify a specific device? No. It identifies an authenticator model. Millions of devices share one AAGUID, which is a deliberate privacy property.
Can the AAGUID be faked? The value in authenticator data is client-supplied. It is only trustworthy when the attestation statement covering it verifies to a known root.
Should we require attestation everywhere? No. It adds friction and platform inconsistency. Tier it: none for general sign-in, direct attestation for elevated access and dual-approval controls.
Why does distinctness matter? A two-approver control is only meaningful if the two signatures came from separate hardware. Synced credentials can put both in one person's hands.
Does AAGUID identify a device? No. It identifies a model. Millions of devices share one, which is the intended privacy property.
What should be implemented first? Refreshing FIDO metadata on a schedule and rejecting revoked models. It is cheap and its absence degrades everything else.
Where this fits in Manav
Manav requires a fresh assertion bound to the specific action, from a credential under the person's sole control and verified against a published key.