Aller au contenu
ecdsa.com
Node.js · WebCrypto

SimpleWebAuthn

SimpleWebAuthn de Matthew Miller, la bibliothèque de clés d’accès la plus utilisée en TypeScript : des générateurs d’options et des vérificateurs de réponses qui exigent par défaut la vérification de l’utilisateur, n’acceptent que des signatures d’assertion DER et refusent les régressions du compteur.
Licence
MIT
Version vérifiée
14.0.3 ·
Code source
github.com/MasterKale/SimpleWebAuthn
Documentation
simplewebauthn.dev/docs/

Compatibilité en un coup d’œil

WebAuthn

SimpleWebAuthn répartit WebAuthn entre deux paquets : @simplewebauthn/browser encapsule navigator.credentials, et @simplewebauthn/server génère les options qu’envoie une partie de confiance (relying party, RP) et vérifie ce qui revient (formats d’attestation, clés COSE, compteurs et signatures). Les essais ont piloté le paquet serveur avec un authentificateur logiciel produisant du JSON d’enregistrement et d’assertion standard, les mêmes octets que pour les pages Python, Go et Java.

Le bug d’intégration le plus courant est un décalage entre ce que demandent les options et ce qu’exige la vérification. generateRegistrationOptions() utilise par défaut userVerification: "preferred", mais les deux vérificateurs utilisent par défaut requireUserVerification: true : une assertion provenant d’un authentificateur qui n’avait prouvé que la présence de l’utilisateur (une clé de sécurité sans PIN) a été refusée avec User verification required, but user could not be verified. Tranchez une fois pour toutes et faites en sorte que les deux côtés disent la même chose, comme le fait l’exemple.

Les signatures d’assertion ES256 doivent être en ASN.1 DER, comme l’exige la spécification WebAuthn. Une signature r‖s brute de 64 octets est quand même transmise à l’analyseur ASN.1 et échoue avec un message qui dépend des octets de la signature et ne mentionne jamais l’encodage ; sur 60 identifiants neufs, les plus fréquents étaient Too big integer, End of input reached before message was fully decoded et Data does not match to ECDSASigValue ASN1 schema. Les authentificateurs et les fournisseurs de clés d’accès produisent du DER : des signatures brutes trahissent donc en général un client écrit à la main ou un banc de test qui a appelé directement le sign() de WebCrypto. Convertissez-les avec le convertisseur DER ⇄ brut. Une signature DER high-S a été vérifiée.

Un compteur de signatures inférieur à la valeur enregistrée est une erreur, pas un avertissement : la vérification a levé Response counter value 3 was lower than expected 5, et un compteur à 0 après un 5 enregistré a échoué de la même façon. Seul un identifiant qui a toujours renvoyé 0, comme le font les clés d’accès synchronisées, passe avec 0. Enregistrez le compteur reçu et décidez vous-même si une régression bloque l’identifiant ou déclenche seulement une alerte. La version 14 place ML-DSA-44 (COSE -48) en tête des pubKeyCredParams par défaut, avant Ed25519 (-8), ES256 (-7) et RS256 (-257), et l’identifiant Ed25519 a été vérifié de bout en bout.

Décodez ce qu’un navigateur a réellement envoyé avec l’inspecteur WebAuthn, essayez les parcours sans véritable authentificateur dans le bac à sable des clés d’accès, et identifiez les modèles d’authentificateurs par leur AAGUID avec la recherche d’AAGUID.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/simplewebauthn/run.sh a exécuté SimpleWebAuthn 14.0.3 sur Node.js 24.22.0 dans l’image de conteneur node:24.22.0-bookworm-slim le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Algorithmes COSE par défaut

    -48-8-7-257

    generateRegistrationOptions() liste les algorithmes COSE -48 (ML-DSA-44), -8 (Ed25519), -7 (ES256) et -257 (RS256), dans cet ordre.

    Relevé par la vérification
    fact webauthn-default-algorithms = -48, -8, -7, -257
  • Attestation par défaut

    none

    La transmission de l’attestation vaut none par défaut.

    Relevé par la vérification
    fact webauthn-attestation = none
  • Vérification de l’utilisateur par défaut

    preferred

    authenticatorSelection.userVerification vaut preferred par défaut.

    Relevé par la vérification
    fact webauthn-user-verification = preferred
  • Clé résidente par défaut

    preferred

    authenticatorSelection.residentKey vaut preferred par défaut.

    Relevé par la vérification
    fact webauthn-resident-key = preferred
  • Vérification de l’utilisateur exigée par défaut

    yes

    Les deux vérificateurs exigent l’indicateur UV, sauf si requireUserVerification: false est passé : un enregistrement et une assertion où seul UP était positionné ont été refusés avec User verification was required, but user could not be verified et User verification required, but user could not be verified.

    Relevé par la vérification
    fact webauthn-uv-enforced-by-default = yes
    evidence webauthn-uv-enforced-by-default: registration with the UV flag clear: throws User verification was required, but user could not be verified
  • Encodage de la signature d’assertion

    der

    Une assertion ES256 correcte avec une signature ASN.1 DER a été vérifiée.

    Relevé par la vérification
    fact webauthn-assertion-encoding = der
  • Signature d’assertion r‖s brute

    rejected

    La même assertion avec une signature r‖s brute de 64 octets a levé une erreur d’analyse ASN.1 dont le libellé dépend des octets de la signature.

    Relevé par la vérification
    fact webauthn-raw-signature = rejected
  • Signature d’assertion high-S

    accepted

    Une signature DER dont s a été remplacé par n − s a été vérifiée.

    Relevé par la vérification
    fact webauthn-high-s = accepted
  • Compteur de signatures qui recule

    rejected

    Un compteur inférieur à la valeur enregistrée a levé Response counter value 3 was lower than expected 5 ; un compteur à 0 après un 5 enregistré a lui aussi été refusé, tandis que 0 après un 0 enregistré a passé la vérification.

    Relevé par la vérification
    fact webauthn-counter-regression = rejected
    evidence webauthn-counter-regression: counter 0 after a stored 5: throws Response counter value 0 was lower than expected 5; counter 0 after a stored 0: verified
  • Identifiants Ed25519

    supported

    Un identifiant Ed25519 (COSE -8) a été enregistré et authentifié avec succès.

    Relevé par la vérification
    fact webauthn-ed25519 = supported
    evidence webauthn-ed25519: verified

Utilisation

Générer des options d’enregistrement et d’authentification qui demandent la même politique de vérification de l’utilisateur que celle qu’appliquent les vérificateurs par défaut.
import { generateAuthenticationOptions, generateRegistrationOptions } from "@simplewebauthn/server";

const rpID = "example.com";

// Registration: the options ask for UV only as "preferred"...
const registration = await generateRegistrationOptions({
  rpName: "Example",
  rpID,
  userName: "user@example.com",
  authenticatorSelection: { residentKey: "required", userVerification: "required" },
});

// ...while verifyRegistrationResponse() and verifyAuthenticationResponse()
// require the UV flag by default. Ask for "required" up front (as here), or pass
// requireUserVerification: false when a security key without a PIN is acceptable.
const authentication = await generateAuthenticationOptions({ rpID, userVerification: "required" });

console.log("algorithms:", registration.pubKeyCredParams.map((param) => param.alg).join(", "));
console.log("uv:", registration.authenticatorSelection?.userVerification, authentication.userVerification);
console.log("challenge bytes:", Buffer.from(authentication.challenge, "base64url").length);

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur node:24.22.0-bookworm-slim, et l’exécution échoue si la sortie ne correspond pas.

Mise en place
npm init -y >/dev/null && npm install --no-audit --no-fund @simplewebauthn/server@14.0.3
Commande
node usage.mts
La sortie doit correspondre à cette expression régulière
algorithms: -48, -8, -7, -257\nuv: required required\nchallenge bytes: 32

Bibliothèques