Signatur
Beschreibung
Die schreibgeschützte Eigenschaft attestationObject des Interfaces AuthenticatorAttestationResponse enthält das Ergebnis einer WebAuthn-Registrierung, also eines Aufrufs von navigator.credentials.create({ publicKey }). Es handelt sich um einen ArrayBuffer mit CBOR-kodierten Daten, den der Client unverändert an den Server (die Relying Party) schickt.
Das dekodierte Objekt hat drei Felder:
fmt: das Attestation-Format, z. B."none","packed","tpm","android-key","apple"oder"fido-u2f".attStmt: die Attestation-Aussage (Signatur und ggf. Zertifikatskette). Ihr Aufbau hängt vonfmtab.authData: die Authenticator-Daten. Sie enthalten den SHA-256-Hash der RP-ID, Flags, den Signaturzähler und die attestedCredentialData (AAGUID, Credential-ID und den öffentlichen Schlüssel im COSE-Format).
Die Prüfung der Attestation und das Auslesen des öffentlichen Schlüssels gehören auf den Server. Wer im Client nur den Schlüssel braucht, nimmt besser die Hilfsmethoden getPublicKey(), getPublicKeyAlgorithm() und getAuthenticatorData(), dann muss man CBOR nicht selbst parsen.
Rückgabewert
fmt, attStmt und authData.Beispiele
Registrierung durchführen und attestationObject an den Server senden
// Hilfsfunktion: ArrayBuffer -> Base64URL
const toBase64Url = (buf) =>
btoa(String.fromCharCode(...new Uint8Array(buf)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const register = async () => {
const credential = await navigator.credentials.create({
publicKey: {
challenge: crypto.getRandomValues(new Uint8Array(32)), // in der Praxis vom Server
rp: { name: 'Beispiel GmbH', id: location.hostname },
user: {
id: new TextEncoder().encode('user-42'),
name: 'anna@example.com',
displayName: 'Anna'
},
pubKeyCredParams: [
{ type: 'public-key', alg: -7 }, // ES256
{ type: 'public-key', alg: -257 } // RS256
],
attestation: 'direct'
}
});
const { attestationObject, clientDataJSON } = credential.response;
console.log(attestationObject instanceof ArrayBuffer, attestationObject.byteLength);
await fetch('/webauthn/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: credential.id,
attestationObject: toBase64Url(attestationObject),
clientDataJSON: toBase64Url(clientDataJSON)
})
});
};
register().catch(console.error);
attestationObject mit einer CBOR-Bibliothek dekodieren
// z. B. mit dem npm-Paket "cbor-x"
import { decode } from 'cbor-x';
const inspectAttestation = (attestationObject) => {
const { fmt, attStmt, authData } = decode(new Uint8Array(attestationObject));
const flags = authData[32];
console.log('Format:', fmt);
console.log('User Present:', Boolean(flags & 0x01));
console.log('User Verified:', Boolean(flags & 0x04));
console.log('Attested Credential Data:', Boolean(flags & 0x40));
console.log('attStmt-Felder:', Object.keys(attStmt));
};
// inspectAttestation(credential.response.attestationObject);
Ohne CBOR-Parser: Hilfsmethoden verwenden
const credential = await navigator.credentials.create({ publicKey: options });
const res = credential.response;
// Dieselben Informationen, die auch in attestationObject stecken
const authData = res.getAuthenticatorData(); // ArrayBuffer
const spki = res.getPublicKey(); // ArrayBuffer (SPKI) oder null
const alg = res.getPublicKeyAlgorithm(); // COSE-Algorithmus, z. B. -7
console.log(authData.byteLength > 37, alg);
if (spki) {
const key = await crypto.subtle.importKey(
'spki', spki, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify']
);
console.log(key.type);
}
// Wichtig · Fallstricke
- Nur ein Container: Der Browser liefert die Bytes, prüft sie aber nicht. Challenge, Origin (aus
clientDataJSON), RP-ID-Hash, Flags und Attestation-Signatur muss der Server prüfen, am besten mit einer etablierten Bibliothek wie SimpleWebAuthn, py_webauthn oder webauthn4j. - Attestation-Format hängt vom Wunsch ab: Ohne
attestation: 'direct'oder'enterprise'istfmtmeist"none"undattStmtleer. Viele Plattform-Authenticatoren und Passkey-Anbieter, die Schlüssel geräteübergreifend synchronisieren, liefern grundsätzlich keine Attestation. Die Registrierung ist dann trotzdem gültig. - Datenschutz: Die Attestation-Zertifikatskette kann das Gerätemodell verraten (AAGUID). Browser können deshalb nachfragen oder die Daten anonymisieren.
- Serialisierung:
ArrayBufferlässt sich nicht direkt mitJSON.stringifyumwandeln, das Ergebnis wäre{}. Vorher nach Base64URL kodieren odercredential.toJSON()nutzen, das es in neueren Browsern gibt. - Voraussetzungen: Funktioniert nur in einem sicheren Kontext (HTTPS oder
localhost). Das Credential gibt es nur als Ergebnis voncredentials.create(). Beicredentials.get()kommt eineAuthenticatorAssertionResponseohne diese Eigenschaft zurück. - Kompatibilität: Die Eigenschaft funktioniert in allen aktuellen Browsern (Chrome, Edge, Firefox, Safari). Die Hilfsmethoden
getPublicKey()und Co. gibt es erst in neueren Versionen.