Understanding Packed Attestation Format
Packed is the FIDO2 default and the format most likely to reach a relying party that asked for direct attestation. This page covers its three variants, the four checks a correct verification performs, and the AAGUID cross-check that is skipped in most implementations. It sits under Attestation vs Assertion Explained, which establishes why attestation is a registration-time policy input rather than part of authentication.
Statement Reference
| Field | Present when | Meaning |
|---|---|---|
alg |
always | the algorithm the attestation signature uses |
sig |
always | signature over authData || clientDataHash |
x5c |
full basic attestation | the certificate chain, leaf first |
x5c absent |
self attestation | signed by the credential key itself |
The presence or absence of the chain is the whole distinction between the two variants, and it changes what the statement is worth.
Root Cause Analysis
1. Signing input assembled wrongly. The attestation signature covers authenticatorData concatenated with the SHA-256 of clientDataJSON — the same construction as an assertion signature, over the same bytes. Implementations that hash the concatenation, or that concatenate the parsed forms, produce a verification failure that looks like a bad certificate.
2. Chain not anchored. Verifying the leaf certificate’s signature over the statement proves internal consistency. Without building a path to a root you pinned in advance, nothing about the manufacturer has been established.
3. Certificate constraints unchecked. An attestation certificate has requirements: a specific version, a basic-constraints extension marking it as an end-entity certificate, and an organisational unit identifying it as an authenticator attestation certificate. Skipping these accepts certificates that were issued for something else entirely.
4. AAGUID cross-check skipped. The certificate may carry an extension naming the AAGUID it was issued for. When present, it must equal the AAGUID in authenticatorData. Without this check, a valid certificate from one model can vouch for a claimed AAGUID from another.
5. Self attestation treated as basic. A statement with no chain is signed by the credential key. It verifies, which is why it slips through — but it establishes nothing beyond what the assertion already would.
Step-by-Step Resolution
Step 1 — dispatch on the presence of a chain
Branch immediately: chain present means full basic, chain absent means self. The two paths share only the signature check.
Step 2 — rebuild the signed input from raw bytes
Concatenate the untouched authenticatorData with the SHA-256 of the untouched clientDataJSON. Verify the statement signature over that buffer.
Step 3 — build and validate the certificate path
Anchor to a root you pinned. Check validity dates, and consult revocation information where the chain provides it.
Step 4 — enforce the certificate constraints
Version, basic constraints, and the attestation-certificate organisational unit. A certificate failing any of these was not issued for this purpose.
Step 5 — cross-check the AAGUID
If the certificate carries the AAGUID extension, compare it byte-for-byte with the value in authenticatorData. A mismatch is a hard rejection.
Step 6 — treat self attestation as no device claim
Accept the registration if your policy accepts unattested credentials; record that no model claim was made.
Verification and Testing
Keep a real packed attestation from a certified hardware key as a fixture, together with the trust anchor that validates it. Assert that it verifies, and assert that a single altered byte in authData makes it fail — that second assertion is what proves the signing input is assembled from the right bytes.
Add a fixture whose certificate does not chain to your pinned root. It must be rejected at the path-building step rather than accepted on the strength of a valid leaf signature.
Add a self-attested fixture and assert that your code classifies it as carrying no device claim rather than routing it down the basic-attestation path.
Finally, add a fixture where the certificate’s AAGUID extension disagrees with authenticatorData. If it passes, the cross-check is missing.
Pitfalls
1. Hashing the concatenation. The signature is over authData plus the client-data hash, not over a hash of both.
2. Accepting a leaf without a path. A self-consistent signature proves nothing about provenance.
3. Letting an expired batch certificate fail registrations silently. Vendor certificates expire; monitor for a rise in rejections concentrated on one vendor.
Frequently Asked Questions
Why is packed attestation called “packed”?
Because the statement is a compact CBOR map rather than a format inherited from an earlier protocol. It was designed for FIDO2 specifically, which is why it is the default for authenticators certified against it.
Do all hardware keys use packed?
Most certified ones do, but not all. Older devices may use formats inherited from U2F, and platform authenticators use their own vendor formats. A relying party that accepts direct attestation should implement the formats it actually receives and treat the rest as carrying no device claim.
Should self attestation be rejected?
Only if your policy rejects unattested credentials generally, since that is effectively what it is. Rejecting it specifically — while accepting registrations with no statement at all — is an inconsistency that will be hard to defend in a review.
What does the batch certificate actually identify?
A production batch of a model, not an individual device. Thousands of units share one certificate, which is deliberate: it means attestation identifies a product without identifying a person. That property is what makes direct attestation acceptable from a privacy standpoint.
How do I get the vendor roots?
From the metadata service, which publishes them per model, or directly from vendors for hardware you procure yourself. Either way they must be pinned in your deployment rather than taken from the statement being verified.
Does the attestation signature use the same algorithm as the credential?
Not necessarily. The alg field in the statement describes the attestation signature specifically, and it can differ from the algorithm of the credential key. Dispatch on the statement’s own field rather than reusing the credential’s.
What should be stored after verification?
The format name, the AAGUID, the verification outcome and the time — not the raw statement. The statement is only meaningful together with the trust context that validated it, and that context changes as roots rotate.
How do expired batch certificates show up in production?
As a rise in registration failures concentrated on one vendor, beginning on a specific date and affecting devices that worked perfectly the day before. Because the fault is a date rather than a defect, it is easy to misdiagnose as a service problem or as a change in the authenticator. The signature to look for is that every affected registration comes from the same model, and that the same payload verifies when the expiry check is relaxed. Monitoring rejections by vendor rather than in aggregate is what makes the pattern visible; without that split, a few hundred failures a day look like ordinary noise until somebody complains.
The same split is worth keeping for successful verifications, not only failures. Knowing which vendors your certified population actually comes from turns an abstract policy discussion about acceptable models into a concrete one about the hardware your users are holding today, and it makes the cost of tightening an allow-list something you can estimate rather than guess at.
Is it worth implementing packed if I only expect platform authenticators?
Probably not on its own, but the dispatch table that routes to it costs almost nothing and means a security key registering against your service does not fall into the unknown-format path. The realistic posture for a general-purpose relying party is to implement packed, implement the platform format for whichever platforms dominate your traffic, and treat everything else as carrying no device claim rather than as an error.
Related
- Attestation vs Assertion Explained — the parent topic area and the distinction between the two ceremonies
- Apple and Android Attestation Formats — the platform formats you will meet alongside packed
- Authenticator Metadata and MDS3 — where the trust anchors and model facts come from
- Validating Attestation Statements on the Server — the surrounding verification pipeline