Aller au contenu
ecdsa.com

FIDO2 vs WebAuthn vs CTAP : les standards des clés d’accès

Ce ne sont pas des alternatives. WebAuthn est l’API du W3C qu’appellent les sites web, CTAP est le protocole FIDO entre l’appareil et une clé de sécurité ou un téléphone, et FIDO2 désigne les deux ensemble.

FIDO2 vs WebAuthn est une question de couches plutôt qu’un choix. WebAuthn (W3C Web Authentication) définit l’API JavaScript qu’appellent les parties de confiance (relying parties, RP) et les formats de données signées qu’elles vérifient. CTAP (FIDO Client to Authenticator Protocol) définit la façon dont un navigateur ou un système d’exploitation communique avec un authentificateur externe par USB, NFC, Bluetooth ou le transport hybride par code QR. FIDO2 est le nom générique, introduit en 2018 par la FIDO Alliance, qui désigne WebAuthn plus CTAP 2. Les clés d’accès (passkeys) sont des identifiants FIDO créés au moyen de ces standards.

Vue d’ensemble

WebAuthnCTAPFIDO2
De quoi il s’agitAPI de navigateur et formats de donnéesProtocole de communication avec les authentificateurs itinérantsTerme générique qui couvre les deux
ÉditeurW3CFIDO AllianceFIDO Alliance
Version actuelleLevel 3, recommandation du W3C du 25 août 2026CTAP 2.3, Proposed Standard du 26 février 2026 (2.2 : juillet 2025)WebAuthn Level 3 avec CTAP 2.3
Versions antérieuresLevel 1 (mars 2019), Level 2 (avril 2021)CTAP 2.0, 2.1 (2021) ; CTAP1 correspond à U2FU2F (2014) en est le prédécesseur
Qui l’implémenteNavigateurs et plateformes (API), parties de confiance (vérifications côté serveur)Navigateurs, systèmes d’exploitation, clés de sécurité, téléphonesTous les maillons de la chaîne
EntrePage web et navigateur ; serveur et données d’authentificateurPlateforme cliente et authentificateurDe la partie de confiance à l’authentificateur
EncodageObjets JavaScript, clientDataJSON, objets d’attestation CBORCommandes CTAP2 en CBOR canonique, comme authenticatorGetAssertion (0x02)Les deux
TransportsSans objetUSB HID, NFC, Bluetooth Low Energy, hybride (code QR, vérification de proximité, tunnel)Les deux
Principaux points d’entréenavigator.credentials.create() et .get()authenticatorMakeCredential (0x01), authenticatorGetInfo (0x04)Sans objet

Qui fait quoi lors d’une connexion

  1. Le site web appelle navigator.credentials.get() avec un challenge et l’ID de sa partie de confiance (RP ID). Cette étape relève de WebAuthn.
  2. Le navigateur construit clientDataJSON (type, challenge, origine, indicateur d’origine croisée), le hache et demande à un authentificateur de signer. Pour un authentificateur de plateforme intégré (Touch ID, Windows Hello, Android), cela passe par les API du système d’exploitation ; pour une clé de sécurité ou un téléphone, cela passe par CTAP, sous la forme d’une commande authenticatorGetAssertion en CBOR.
  3. L’authentificateur contrôle la présence de l’utilisateur et, sur demande, effectue la vérification de l’utilisateur, puis signe ses données d’authentificateur concaténées au condensat des données client. La réponse revient par CTAP ou par l’API de la plateforme.
  4. Le navigateur renvoie les octets à la page, et le serveur les vérifie comme le spécifie le §7.2 de WebAuthn.

Le script ci-dessous analyse le vecteur de test officiel §16.2 de WebAuthn Level 3, qui contient un enregistrement et une authentification pour un identifiant ES256, puis vérifie la signature (Node.js 26, cbor-x 1.6.0). Les octets authData et la signature ont été produits par l’authentificateur ; clientDataJSON l’a été par le navigateur.

Analyser et vérifier le vecteur de test §16.2 de WebAuthn Level 3
import { createHash, createPublicKey, verify } from "node:crypto";
import { decode } from "cbor-x";

// WebAuthn Level 3 §16.2 test vector: ES256 credential, "none" attestation
const hex = (s) => Buffer.from(s, "hex");
const attestationObject = hex("a363666d74646e6f6e656761747453746d74a068617574684461746158a4bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b559000000008446ccb9ab1db374750b2367ff6f3a1f0020f91f391db4c9b2fde0ea70189cba3fb63f579ba6122b33ad94ff3ec330084be4a5010203262001215820afefa16f97ca9b2d23eb86ccb64098d20db90856062eb249c33a9b672f26df61225820930a56b87a2fca66334b03458abf879717c12cc68ed73290af2e2664796b9220");
const authenticatorData = hex("bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b51900000000");
const clientDataJSON = hex("7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a224f63446e55685158756c5455506f334a5558543049393770767a7a59425039745a63685879617630314167222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73657d");
const signature = hex("3046022100f50a4e2e4409249c4a853ba361282f09841df4dd4547a13a87780218deffcd380221008480ac0f0b93538174f575bf11a1dd5d78c6e486013f937295ea13653e331e87");

const sha256 = (b) => createHash("sha256").update(b).digest();
const flagNames = { 0x01: "UP", 0x04: "UV", 0x08: "BE", 0x10: "BS", 0x40: "AT", 0x80: "ED" };
const flags = (b) => Object.entries(flagNames).filter(([bit]) => b & bit).map(([, n]) => n).join(" ");

// Registration: authenticator data produced by the authenticator (CTAP authenticatorMakeCredential)
const { fmt, authData } = decode(attestationObject);
const credIdLen = authData.readUInt16BE(53);
const coseKey = decode(authData.subarray(55 + credIdLen));
console.log("attestation format:", fmt);
console.log("rpIdHash == SHA-256('example.org'):", authData.subarray(0, 32).equals(sha256("example.org")));
console.log("flags:", flags(authData[32]), "| signCount:", authData.readUInt32BE(33));
console.log("AAGUID:", authData.subarray(37, 53).toString("hex"), "| credential ID bytes:", credIdLen);
console.log("COSE key: kty", coseKey[1], "alg", coseKey[3], "crv", coseKey[-1]);

// Authentication: verify the assertion with the registered public key
const publicKey = createPublicKey({ key: { kty: "EC", crv: "P-256",
  x: Buffer.from(coseKey[-2]).toString("base64url"), y: Buffer.from(coseKey[-3]).toString("base64url") }, format: "jwk" });
console.log("\nclientDataJSON (built by the browser):", clientDataJSON.toString());
console.log("assertion flags:", flags(authenticatorData[32]));
const ok = verify("sha256", Buffer.concat([authenticatorData, sha256(clientDataJSON)]), publicKey, signature);
console.log("signature over authenticatorData || SHA-256(clientDataJSON):", ok ? "valid" : "invalid");
Sortie
attestation format: none
rpIdHash == SHA-256('example.org'): true
flags: UP BE BS AT | signCount: 0
AAGUID: 8446ccb9ab1db374750b2367ff6f3a1f | credential ID bytes: 32
COSE key: kty 2 alg -7 crv 1

clientDataJSON (built by the browser): {"type":"webauthn.get","challenge":"OcDnUhQXulTUPo3JUXT0I97pvzzYBP9tZchXyav01Ag","origin":"https://example.org","crossOrigin":false}
assertion flags: UP BE BS
signature over authenticatorData || SHA-256(clientDataJSON): valid

Les indicateurs montrent la présence de l’utilisateur (UP), un identifiant éligible à la sauvegarde et sauvegardé (BE, BS : une clé d’accès synchronisée) et, à l’enregistrement, des données d’identifiant attestées (AT). La clé COSE (type de clé 2 = EC2, algorithme −7 = ES256, courbe 1 = P-256) est le format commun aux deux spécifications ; l’inspecteur WebAuthn décode les mêmes structures à partir d’une vraie réponse de navigateur.

De U2F aux clés d’accès

FIDO U2F (2014) a doté les clés de sécurité d’un protocole simple de second facteur, aujourd’hui appelé CTAP1, et d’une API de navigateur. WebAuthn a remplacé cette API entre 2018 et 2019 (Chrome 67, Firefox 60, Safari 13), et CTAP 2 a ajouté les codes PIN, la vérification de l’utilisateur intégrée et les identifiants détectables, si bien qu’une clé pouvait remplacer le mot de passe au lieu de seulement le compléter. Chrome a retiré l’ancienne API JavaScript U2F dans sa version 104 (2022) ; les identifiants enregistrés par son intermédiaire fonctionnent toujours dans WebAuthn grâce à l’extension appid.

L’annonce des clés d’accès synchronisées par Apple, Google et Microsoft en 2022 n’a exigé aucun nouveau protocole : une clé d’accès est un identifiant détectable dont les indicateurs de sauvegarde sont activés, créé et utilisé via la même API WebAuthn. CTAP 2.2 (juillet 2025) a normalisé le transport hybride, qui permet à un téléphone de servir d’authentificateur à un ordinateur proche après la lecture d’un code QR, ainsi que des politiques de complexité du PIN ; CTAP 2.3 (février 2026) est une révision incrémentale, qui permet par exemple aux authentificateurs d’indiquer quelles commandes de configuration ils prennent en charge.

Attestation et certification

À l’enregistrement, un authentificateur peut inclure une déclaration d’attestation signée par une clé partagée par tout un lot d’appareils, ainsi qu’un AAGUID qui identifie le modèle. WebAuthn définit les formats de déclaration (packed, tpm, android-key, apple, fido-u2f, none) ; la FIDO Alliance certifie les authentificateurs et publie leurs métadonnées, dont les racines d’attestation et les niveaux de certification, dans le service de métadonnées FIDO (FIDO Metadata Service). La plupart des sites grand public devraient ne demander aucune attestation et accepter n’importe quel authentificateur ; les entreprises qui doivent n’autoriser que des clés matérielles certifiées demandent une attestation direct ou enterprise et la confrontent aux métadonnées, comme l’illustrent le vérificateur d’attestation YubiKey et la recherche d’AAGUID.

Ce qu’il faut implémenter

  • Site web ou application web : WebAuthn dans le navigateur et une bibliothèque côté serveur qui implémente les étapes de vérification du §7.
  • Application native iOS ou Android : les API de clés d’accès de la plateforme (AuthenticationServices, Credential Manager), qui renvoient des données au format WebAuthn liées à votre domaine web par les domaines associés (associated domains) ou par Digital Asset Links.
  • Entreprise qui n’autorise que des clés matérielles : WebAuthn avec attestation, confrontée aux entrées du service de métadonnées FIDO.
  • Fabricant de clés de sécurité, de téléphones ou de systèmes d’exploitation : CTAP pour les authentificateurs itinérants, certification FIDO pour le produit.
  • Achats : demandez le niveau de certification FIDO, la version de CTAP et les transports pris en charge, ainsi que les fonctionnalités WebAuthn dont vous dépendez, plutôt qu’une simple « prise en charge de FIDO2 ».

Notes de migration

  1. Remplacer les enregistrements U2F : continuez à envoyer l’ancien App ID via l’extension appid à la connexion, et enregistrez un nouvel identifiant WebAuthn lors de la prochaine connexion des utilisateurs.
  2. Adopter les clés d’accès : demandez residentKey: "required" et userVerification: "preferred" ou "required", et stockez les indicateurs de sauvegarde.
  3. Passer à WebAuthn Level 3 fonctionnalité par fonctionnalité : la création conditionnelle, getClientCapabilities(), les méthodes de signal et les origines associées se dégradent toutes proprement lorsqu’elles ne sont pas prises en charge.
  4. Tests : avant le lancement, éprouvez les authentificateurs de plateforme, une clé USB ou NFC et le parcours hybride depuis un téléphone dans le bac à sable des clés d’accès.

Références

Tous les comparatifs