WebAuthn attestation: packed, TPM, android-key, and why it matters

WebAuthn registration produces two things: a public key credential and an attestation statement. Most WebAuthn tutorials stop at the public key — that's the part that enables passwordless authentication. The attestation statement is the part that answers a harder question: what device generated this credential, and can I trust the device itself? For consumer applications, attestation often does not matter. For enterprise deployments with device compliance requirements, it is the mechanism that enforces "only corporate-managed devices can register."

What attestation proves

An attestation statement is a cryptographic assertion from the authenticator manufacturer (or device platform) that the credential was generated by a genuine, unmodified authenticator of a specific model. The statement is signed by an attestation key that is either baked into the device hardware at manufacturing time or certified by a per-model CA.

Importantly, attestation answers a different question than authentication. Authentication proves the user has the private key. Attestation proves the private key lives inside a specific type of hardware — a YubiKey 5 NFC, a MacBook's Secure Enclave, a Windows 11 PC's TPM 2.0 chip, or an Android 9+ device with StrongBox Keymaster.

Attestation format types

packed

The most common attestation format for security keys and platform authenticators. The authenticator signs the attestation with either a device-specific certificate chain (full attestation) or a self-signed certificate (surrogate / self attestation). Full packed attestation chains up to the FIDO Alliance's Metadata Service (MDS), allowing the relying party to verify the certificate is from a known manufacturer.

tpm

Generated by Windows Hello when backed by the device's TPM 2.0 chip. The TPM generates a key pair certified by a certificate from the TPM manufacturer (Infineon, STMicroelectronics, etc.). Verifying TPM attestation requires checking the certificate chain against the TPM manufacturer's CA and verifying that the key parameters match what the TPM claims.

android-key

Generated by Android 7+ devices using Android Keystore. The attestation certificate is rooted in Google's hardware attestation root CA, providing strong evidence that the key was generated by a genuine Android device with hardware-backed keystore support. StrongBox (Android 9+) provides even stronger isolation using a dedicated secure element.

apple

Generated by Apple's platform authenticator (Face ID, Touch ID on iOS/macOS). The attestation root is Apple's FIDO2 CA. Note that passkeys synchronized via iCloud Keychain use a different attestation mechanism because the key is not bound to a single device.

none

The authenticator either does not support attestation or the user's browser/platform stripped it for privacy reasons. Chrome strips attestation by default unless the relying party explicitly sets attestation: "direct" in the registration options. This is a privacy protection: attestation can fingerprint the specific device model.

Requesting attestation in registration

// Server-side: generate registration options requesting attestation
import { generateRegistrationOptions } from '@simplewebauthn/server';

const options = await generateRegistrationOptions({
  rpName: 'YourApp',
  rpID: 'yourapp.com',
  userID: userId,
  userName: userEmail,
  attestationType: 'direct',   // 'none' | 'indirect' | 'direct'
  // 'direct' = authenticator sends full attestation
  // 'indirect' = platform may anonymize
  // 'none' = no attestation required
  authenticatorSelection: {
    authenticatorAttachment: 'platform',  // or 'cross-platform' for security keys
    residentKey: 'preferred',
    userVerification: 'required',
  },
  excludeCredentials: existingCredentials,
});
// Server-side: verify registration response and extract attestation info
import { verifyRegistrationResponse } from '@simplewebauthn/server';

const verification = await verifyRegistrationResponse({
  response: registrationResponse,
  expectedChallenge,
  expectedOrigin: 'https://yourapp.com',
  expectedRPID: 'yourapp.com',
  requireUserVerification: true,
});

const { verified, registrationInfo } = verification;

if (verified) {
  const {
    credential,
    credentialDeviceType,     // 'singleDevice' | 'multiDevice'
    credentialBackedUp,       // synced passkey vs device-bound
    aaguid,                   // authenticator model identifier
    fmt,                      // attestation format: 'packed', 'tpm', etc.
  } = registrationInfo;

  // Store credential with attestation metadata
  await db.query(
    `INSERT INTO credentials
     (id, user_id, public_key, counter, aaguid, fmt, device_type, backed_up)
     VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
    [credential.id, userId, credential.publicKey, credential.counter,
     aaguid, fmt, credentialDeviceType, credentialBackedUp]
  );
}

AAGUID and the FIDO MDS

The AAGUID (Authenticator Attestation Global Unique Identifier) is a 16-byte UUID that identifies the authenticator model. Every YubiKey 5 NFC has the same AAGUID; every Windows Hello TPM device has the same AAGUID. By cross-referencing the AAGUID against the FIDO Alliance Metadata Service (MDS3), you can determine the exact authenticator model and its security certification level.

// Fetch and cache the FIDO MDS to look up authenticator metadata
const MDS_URL = 'https://mds3.fidoalliance.org/';

async function getAuthenticatorMetadata(aaguid) {
  // MDS blob is a JWT signed by the FIDO Alliance
  // Cache this — it updates monthly
  const metadata = await fetchMDSBlob();

  const entry = metadata.entries.find(e => e.aaguid === aaguid);
  if (!entry) return null;

  return {
    description: entry.metadataStatement.description,
    // e.g., "Yubico Security Key NFC by Yubico"
    authenticatorVersion: entry.metadataStatement.authenticatorVersion,
    certificationLevel: entry.statusReports[0]?.certificationLevel,
    // FIDO_CERTIFIED_L1, FIDO_CERTIFIED_L2, FIDO_CERTIFIED_L3
  };
}
FIDO certification levels matter for high-security deployments. L1 requires security claims via software analysis only. L2 requires penetration testing. L3 requires physical security testing (tamper resistance). Government and financial services applications often require L2 or L3 certified authenticators.

Enterprise device policy enforcement

For enterprise customers that need to restrict authentication to company-managed devices, attestation is the mechanism. The policy: only credentials attested by TPM authenticators on managed Windows devices, or Apple's platform authenticator on MDM-enrolled macOS/iOS devices, are accepted during registration. Credentials registered from personal devices are rejected.

// Enterprise policy: only allow specific authenticator models
const ALLOWED_AAGUIDS = new Set([
  '08987058-cadc-4b81-b6e1-30de50dcbe96',  // Windows Hello Hardware Authenticator
  'adce0002-35bc-c60a-648b-0b25f1f05503',  // Chrome Touch ID on Mac
  'fbfc3007-154e-4ecc-8c0b-6e020557d7bd',  // Windows Hello Software Authenticator
  // ... add YubiKey AAGUIDs for hardware key deployments
]);

async function validateRegistrationForEnterprise(aaguid, fmt, orgId) {
  const org = await getOrg(orgId);
  if (!org.require_attested_devices) return true;  // policy not enabled

  if (fmt === 'none') {
    throw new Error('This organization requires attested authenticators. '
      + 'Platform authenticators on Chrome may need to enable enterprise attestation.');
  }

  if (!ALLOWED_AAGUIDS.has(aaguid)) {
    const meta = await getAuthenticatorMetadata(aaguid);
    throw new Error(
      `Authenticator not approved for this organization: ${meta?.description || aaguid}`
    );
  }

  return true;
}

When to skip attestation

For consumer applications, requiring attestation creates friction without meaningful security benefit. Most consumer devices support attestation technically, but Chrome strips it by default for privacy, Safari does not always expose it, and users on older devices or less common platforms may have authenticators not in the MDS. Setting attestationType: 'none' maximizes registration success rates and is the right default for consumer-facing WebAuthn. Add attestation requirements as an enterprise tier feature, not a baseline requirement.

← Back to blog Try Bastionary free →