Le 8 avril 2026, la Commission européenne a adopté un nouveau règlement d’exécution imposant la norme ETSI TS 119 461 pour la vérification d’identité des utilisateurs du portefeuille européen d’identité numérique, l’EUDI Wallet. Derrière cet acronyme technique se cache une question très concrète pour les développeurs français et européens : comment signer numériquement un document ou un exécutable de façon à ce que la signature tienne juridiquement et techniquement la route, aujourd’hui, avec les outils qu’on a déjà sous la main ? La réponse passe presque toujours par un certificat X.509, la brique qui relie une clé cryptographique à une identité vérifiable. Ce tutoriel vous montre comment construire, de zéro, une chaîne complète de signature numérique en Node.js : autorité de certification maison, certificat de signataire, signature de documents, vérification, et une passerelle claire vers les exigences eIDAS 2.0 et le RGS de l’ANSSI pour les cas où vous devez passer à un certificat qualifié.
Pourquoi la signature numérique X.509 compte encore en 2026
La signature électronique n’est plus un sujet de niche. Le règlement (UE) 2024/1183, entré en vigueur le 20 mai 2024 et connu sous le nom d’eIDAS 2.0, impose à chacun des 27 États membres de proposer un EUDI Wallet à ses citoyens, résidents et entreprises d’ici fin décembre 2026. Treize mois plus tard, les grandes entreprises des secteurs régulés devront accepter ce portefeuille comme moyen d’authentification. Ce calendrier change la donne pour tous les systèmes qui signent des documents, du contrat PDF au firmware embarqué.
Le X.509 reste le format de certificat sous-jacent à presque toute cette infrastructure, qu’il s’agisse d’une signature électronique simple, avancée ou qualifiée au sens eIDAS. Le certificat qualifié y ajoute une couche réglementaire (émission par un prestataire de confiance qualifié, QTSP, et usage d’un dispositif QSCD) mais la mécanique cryptographique, elle, repose sur les mêmes primitives que celles que vous allez manipuler dans ce tutoriel : une paire de clés, une autorité de certification, une chaîne de confiance. Comprendre cette mécanique de bout en bout est un prérequis avant de brancher un vrai prestataire qualifié.
Ce tutoriel vous fait construire un système complet : une autorité de certification (CA) racine auto-signée, un certificat intermédiaire, un certificat de signataire, un service Node.js qui signe des documents PDF et des paquets JSON avec horodatage, et un vérificateur qui rejette toute signature invalide, expirée ou révoquée. Vous repartirez avec un projet fonctionnel, pas seulement des extraits de code isolés.
Prérequis et versions utilisées
Avant de commencer, installez les outils suivants. Les versions indiquées sont celles disponibles en septembre 2026 ; utilisez toujours la dernière version stable de votre branche si un numéro plus récent existe.
- Node.js 22 LTS ou Node.js 26 (la branche courante en 2026), avec le module
cryptointégré qui s’appuie sur OpenSSL - npm 10 ou supérieur, fourni avec Node.js
- OpenSSL 3.x en ligne de commande (vérifiez avec
openssl version) pour inspecter les certificats générés - Un éditeur de texte et un terminal Linux, macOS ou WSL2 sous Windows
- Environ 30 Mo d’espace disque libre pour les clés, certificats et fichiers signés
- Connaissances de base en JavaScript asynchrone et en ligne de commande
Le module crypto de Node.js s’appuie sur OpenSSL pour ses primitives cryptographiques et pour l’analyse des certificats X.509, ce qui inclut la vérification de chaînes de certification et l’extraction de clés publiques. Depuis Node.js 12, il gère aussi la génération de paires de clés X25519 et X448, et depuis la version 26.0.0 un paramètre de contexte pour Ed25519 a été ajouté. Vous n’aurez besoin d’aucune dépendance externe pour la partie cryptographique pure : tout est disponible nativement.
Étape 1 : initialiser le projet Node.js
Créez un dossier de travail et initialisez un projet npm classique. Nous ajouterons une seule dépendance, pdf-lib, pour manipuler des PDF ; tout le reste utilise le module crypto natif.
mkdir signature-x509-demo && cd signature-x509-demo
npm init -y
npm install pdf-lib
mkdir -p ca certs docs signed
La structure de dossiers sépare les rôles : ca/ contiendra la clé et le certificat de l’autorité de certification racine, certs/ les certificats intermédiaire et de signataire, docs/ les documents à signer, et signed/ les fichiers signés et leurs preuves.
Étape 2 : créer l’autorité de certification racine
Une chaîne de confiance X.509 commence toujours par une autorité racine. En production, cette racine serait détenue par un prestataire de confiance qualifié ; ici, nous la générons nous-mêmes avec OpenSSL pour comprendre chaque étape. Nous utilisons une clé Ed25519, plus rapide et plus compacte que RSA pour un niveau de sécurité équivalent, et déjà prise en charge nativement par OpenSSL 3.x et Node.js.
openssl genpkey -algorithm ed25519 -out ca/ca-key.pem
openssl req -x509 -new -key ca/ca-key.pem -sha256 -days 3650 \
-out ca/ca-cert.pem \
-subj "/C=FR/O=ShatteredDemo CA/OU=Cryptographie/CN=ShatteredDemo Root CA"
Le premier bloc génère une paire de clés Ed25519. Le second crée un certificat auto-signé valable dix ans (3650 jours), acceptable pour une racine de test mais bien plus long que ce qu’une autorité réelle tolérerait pour un certificat de signataire. Vérifiez le résultat avec la commande suivante.
openssl x509 -in ca/ca-cert.pem -noout -text | head -20
Vous devez voir apparaître l’algorithme de signature (ed25519), le sujet (Subject) correspondant à votre CN, et une période de validité (Validity) cohérente avec les 3650 jours demandés.
Étape 3 : générer le certificat intermédiaire
Une bonne pratique PKI (Public Key Infrastructure) consiste à ne jamais signer directement les certificats finaux avec la clé racine. On intercale un certificat intermédiaire, ce qui permet de révoquer ou de faire tourner cette couche sans toucher à la racine, dont la compromission serait catastrophique.
openssl genpkey -algorithm ed25519 -out certs/intermediate-key.pem
openssl req -new -key certs/intermediate-key.pem \
-out certs/intermediate.csr \
-subj "/C=FR/O=ShatteredDemo CA/OU=Signature/CN=ShatteredDemo Intermediate"
openssl x509 -req -in certs/intermediate.csr \
-CA ca/ca-cert.pem -CAkey ca/ca-key.pem -CAcreateserial \
-out certs/intermediate-cert.pem -days 1825 -sha256 \
-extfile <(printf "basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,cRLSign")
Le paramètre basicConstraints=CA:TRUE,pathlen:0, défini par la RFC 5280, est essentiel : il autorise ce certificat à signer d'autres certificats (c'est une CA), mais lui interdit de créer une nouvelle sous-chaîne intermédiaire (pathlen:0). Sans cette contrainte, n'importe quel certificat émis par votre intermédiaire pourrait à son tour devenir une autorité, ce qui est une faille de conception classique dans les PKI mal configurées.
Étape 4 : émettre le certificat du signataire
Le certificat de signataire est celui qui sera réellement utilisé pour signer vos documents ou votre code. Sa durée de vie doit être courte : les certificats SSL/TLS publics sont d'ailleurs en train de passer de 200 jours de validité maximale à 47 jours d'ici 2029, une tendance que l'ANSSI et le CA/Browser Forum poussent aussi pour les certificats de signature, afin de réduire la fenêtre d'exposition en cas de compromission de clé.
openssl genpkey -algorithm ed25519 -out certs/signer-key.pem
openssl req -new -key certs/signer-key.pem \
-out certs/signer.csr \
-subj "/C=FR/O=Ma Societe/OU=Signature Documents/[email protected]"
openssl x509 -req -in certs/signer.csr \
-CA certs/intermediate-cert.pem -CAkey certs/intermediate-key.pem -CAcreateserial \
-out certs/signer-cert.pem -days 90 -sha256 \
-extfile <(printf "basicConstraints=critical,CA:FALSE\nkeyUsage=critical,digitalSignature,nonRepudiation\nextendedKeyUsage=codeSigning,emailProtection")
Notez le champ keyUsage=digitalSignature,nonRepudiation, qui déclare explicitement l'usage autorisé de la clé, et extendedKeyUsage=codeSigning, qui restreint ce certificat à la signature de code et de documents (par opposition à l'authentification serveur TLS, par exemple). Un vérificateur strict doit rejeter un certificat dont l'usage déclaré ne correspond pas à l'opération demandée.
Assemblez enfin la chaîne complète, du signataire jusqu'à la racine, dans un seul fichier : elle sera nécessaire côté vérificateur pour reconstruire le chemin de confiance.
cat certs/signer-cert.pem certs/intermediate-cert.pem ca/ca-cert.pem > certs/chain.pem
Étape 5 : signer un document JSON en Node.js
Passons maintenant côté Node.js. Le module crypto expose crypto.sign() et crypto.verify(), qui gèrent nativement RSA, les courbes elliptiques (P-256 notamment) et Ed25519. Créez un fichier sign.js pour signer un document JSON représentant, par exemple, un bon de commande.
// sign.js
const crypto = require('crypto');
const fs = require('fs');
function signDocument(documentPath, privateKeyPath, certPath) {
const document = fs.readFileSync(documentPath);
const privateKey = crypto.createPrivateKey(fs.readFileSync(privateKeyPath));
const certificate = fs.readFileSync(certPath, 'utf8');
// crypto.sign gere automatiquement le hachage pour Ed25519 (pas de digest a preciser)
const signature = crypto.sign(null, document, privateKey);
const envelope = {
document: document.toString('base64'),
signature: signature.toString('base64'),
algorithm: 'ed25519',
signedAt: new Date().toISOString(),
certificate,
};
return envelope;
}
const envelope = signDocument('docs/bon-commande.json', 'certs/signer-key.pem', 'certs/signer-cert.pem');
fs.writeFileSync('signed/bon-commande.signed.json', JSON.stringify(envelope, null, 2));
console.log('Document signe avec succes ->', 'signed/bon-commande.signed.json');
Créez d'abord un document de test, puis lancez le script.
echo '{"commande":"CMD-2026-0917","montant":4590.00,"client":"ACME SARL"}' > docs/bon-commande.json
node sign.js
Sortie attendue dans le terminal :
Document signe avec succes -> signed/bon-commande.signed.json
Étape 6 : vérifier une signature et valider la chaîne de certificats
Signer sans vérifier ne sert à rien : la vérification est l'étape qui protège réellement vos utilisateurs. Un vérificateur correct doit faire trois choses distinctes : contrôler la signature cryptographique elle-même, valider que le certificat du signataire remonte bien à une racine de confiance, et vérifier que le certificat n'est ni expiré ni révoqué.
// verify.js
const crypto = require('crypto');
const { X509Certificate } = crypto;
const fs = require('fs');
function verifyEnvelope(envelopePath, trustedRootPath) {
const envelope = JSON.parse(fs.readFileSync(envelopePath, 'utf8'));
const document = Buffer.from(envelope.document, 'base64');
const signature = Buffer.from(envelope.signature, 'base64');
const cert = new X509Certificate(envelope.certificate);
const root = new X509Certificate(fs.readFileSync(trustedRootPath));
// 1. La signature correspond-elle au document et a la cle publique du certificat ?
const signatureValid = crypto.verify(null, document, cert.publicKey, signature);
// 2. Le certificat est-il dans sa periode de validite ?
const now = new Date();
const notBefore = new Date(cert.validFrom);
const notAfter = new Date(cert.validTo);
const withinValidity = now >= notBefore && now <= notAfter;
// 3. Le certificat remonte-t-il, via son emetteur, a la racine de confiance ?
// (verification simplifiee : en production, reconstruire toute la chaine)
const chainTrusted = cert.checkIssued(root) || cert.issuer === root.subject;
return { signatureValid, withinValidity, chainTrusted, signerSubject: cert.subject };
}
const result = verifyEnvelope('signed/bon-commande.signed.json', 'ca/ca-cert.pem');
console.log(result);
if (!result.signatureValid) {
console.error('ECHEC : signature invalide, document potentiellement altere');
process.exit(1);
}
console.log('Signature verifiee pour :', result.signerSubject);
Exécutez le script :
node verify.js
Résultat attendu :
{
signatureValid: true,
withinValidity: true,
chainTrusted: true,
signerSubject: 'C=FR, O=Ma Societe, OU=Signature Documents, [email protected]'
}
Signature verifiee pour : C=FR, O=Ma Societe, OU=Signature Documents, [email protected]
Modifiez un caractère dans docs/bon-commande.json, re-signez, puis altérez manuellement le champ document dans le fichier signé sans re-signer : la vérification doit alors afficher signatureValid: false et le script doit s'arrêter avec le code de sortie 1. C'est le comportement attendu d'un vérificateur qui protège réellement contre l'altération.
Étape 7 : signer un fichier PDF avec horodatage
Pour un cas d'usage plus proche de la réalité métier (contrats, factures, devis), signons un vrai PDF avec la bibliothèque pdf-lib. Ici, plutôt que d'intégrer la signature dans la structure PKCS#7 du PDF (norme PAdES, plus complexe), nous produisons une empreinte SHA-256 du fichier et signons cette empreinte, une approche courante pour les workflows internes qui n'ont pas besoin de conformité PAdES stricte.
// sign-pdf.js
const crypto = require('crypto');
const fs = require('fs');
function signPdf(pdfPath, privateKeyPath, certPath) {
const pdfBytes = fs.readFileSync(pdfPath);
const hash = crypto.createHash('sha256').update(pdfBytes).digest();
const privateKey = crypto.createPrivateKey(fs.readFileSync(privateKeyPath));
const signature = crypto.sign(null, hash, privateKey);
const proof = {
file: pdfPath,
sha256: hash.toString('hex'),
signature: signature.toString('base64'),
timestamp: new Date().toISOString(),
certificate: fs.readFileSync(certPath, 'utf8'),
};
const proofPath = pdfPath.replace('.pdf', '.proof.json');
fs.writeFileSync(proofPath, JSON.stringify(proof, null, 2));
return proofPath;
}
const proofPath = signPdf('docs/contrat.pdf', 'certs/signer-key.pem', 'certs/signer-cert.pem');
console.log('Preuve de signature ecrite dans', proofPath);
Cette approche par fichier de preuve séparé (detached signature) a un avantage pratique : elle n'exige pas de modifier le PDF original, ce qui simplifie l'archivage et évite les problèmes de compatibilité avec les lecteurs PDF standards. L'inconvénient, c'est qu'elle ne s'affiche pas nativement dans Adobe Acrobat comme le ferait une signature PAdES intégrée ; pour ce niveau de conformité, il faut passer par une bibliothèque dédiée à la norme PAdES ou par un prestataire de confiance qualifié.
Étape 8 : horodatage et non-répudiation
Un horodatage local (new Date().toISOString()) suffit pour un usage interne, mais il repose sur l'horloge de la machine qui signe, ce qui n'apporte aucune garantie face à un litige. Pour une vraie valeur probante, il faut un jeton d'horodatage RFC 3161 émis par une autorité de temps tierce indépendante. Voici comment interroger une autorité d'horodatage (TSA) compatible RFC 3161 en ligne de commande avec OpenSSL, une étape que vous intégreriez ensuite dans votre pipeline Node.js via child_process ou une bibliothèque cliente HTTP.
# Creer une requete d'horodatage pour le hash du document
openssl ts -query -data docs/contrat.pdf -sha256 -no_nonce -out docs/contrat.tsq
# Envoyer la requete a une autorite d'horodatage RFC 3161 (exemple generique)
curl -s -H "Content-Type: application/timestamp-query" \
--data-binary @docs/contrat.tsq \
https://votre-tsa-de-confiance.example/tsa > signed/contrat.tsr
Le jeton renvoyé (.tsr) prouve qu'un document avec ce hash exact existait à un instant donné, attesté par un tiers de confiance, indépendamment de l'horloge de votre serveur. C'est cette brique qui transforme une simple signature technique en preuve de non-répudiation exploitable devant un tribunal ou un auditeur.
Étape 9 : gérer la révocation avec une liste CRL
Un certificat compromis doit pouvoir être révoqué avant sa date d'expiration naturelle. Le mécanisme classique est la liste de révocation de certificats (CRL), signée par l'autorité et publiée périodiquement. Générez-en une pour votre CA de démonstration.
# Revoquer le certificat du signataire
openssl ca -config ca/openssl.cnf -revoke certs/signer-cert.pem \
-keyfile ca/ca-key.pem -cert ca/ca-cert.pem
# Regenerer la CRL a jour
openssl ca -config ca/openssl.cnf -gencrl \
-keyfile ca/ca-key.pem -cert ca/ca-cert.pem -out ca/ca.crl
Cette commande suppose un fichier de configuration OpenSSL (openssl.cnf) avec une base de données de certificats déclarée (database, serial) : c'est le format standard attendu par la sous-commande ca d'OpenSSL. Côté vérificateur Node.js, il faut alors télécharger la CRL courante (ou interroger un répondeur OCSP) avant de faire confiance à un certificat, et rejeter toute signature dont le certificat apparaît dans la liste des numéros de série révoqués.
CRL contre OCSP : quelle méthode de révocation choisir
Deux mécanismes coexistent pour vérifier qu'un certificat n'a pas été révoqué avant son expiration naturelle : la liste de révocation (CRL), que vous venez de générer, et le protocole OCSP (Online Certificate Status Protocol), qui interroge en temps réel un répondeur dédié pour un seul certificat à la fois. Le choix entre les deux dépend du volume de vérifications et de la tolérance à la latence de votre système.
| Critère | CRL | OCSP |
|---|---|---|
| Fraîcheur de l'information | Dépend de la fréquence de publication (souvent horaire ou quotidienne) | Quasi temps réel |
| Charge réseau | Téléchargement d'une liste complète, potentiellement volumineuse | Une requête légère par certificat vérifié |
| Vie privée | Aucune fuite d'information sur le certificat interrogé | Le répondeur OCSP sait quel certificat est vérifié, à quel moment |
| Disponibilité hors ligne | Une fois téléchargée, utilisable sans connexion | Nécessite une connexion active au répondeur |
| Cas d'usage typique | Vérificateurs batch, systèmes d'archivage | Vérification synchrone lors d'une transaction critique |
Dans la pratique, beaucoup d'infrastructures combinent les deux : OCSP pour les vérifications en ligne à faible latence, CRL en secours si le répondeur OCSP est indisponible. Le format de certificat X.509, normalisé par la RFC 5280 de l'IETF, prévoit d'ailleurs des extensions dédiées (CRL Distribution Points et Authority Information Access) pour indiquer où trouver l'une et l'autre source, ce qui permet à un vérificateur automatisé de les découvrir sans configuration manuelle.
Étape 10 : construire une API Express de signature en tant que service
Pour aller au-delà des scripts en ligne de commande, exposons la signature comme un service HTTP interne, utile par exemple pour un microservice de facturation qui doit signer chaque document généré.
// server.js
const express = require('express');
const crypto = require('crypto');
const fs = require('fs');
const app = express();
app.use(express.json({ limit: '5mb' }));
const privateKey = crypto.createPrivateKey(fs.readFileSync('certs/signer-key.pem'));
const certificate = fs.readFileSync('certs/signer-cert.pem', 'utf8');
app.post('/sign', (req, res) => {
if (!req.body || !req.body.payload) {
return res.status(400).json({ error: 'Champ payload manquant' });
}
const document = Buffer.from(JSON.stringify(req.body.payload));
const signature = crypto.sign(null, document, privateKey);
res.json({
signature: signature.toString('base64'),
algorithm: 'ed25519',
signedAt: new Date().toISOString(),
certificate,
});
});
app.listen(3000, () => console.log('Service de signature actif sur le port 3000'));
Ce service doit tourner derrière un reverse proxy avec authentification mutuelle (mTLS) ou une clé API stricte, puisqu'il détient l'accès à la clé privée de signature. Ne l'exposez jamais directement sur Internet.
Étape 11 : tester la robustesse avec des cas limites
Un système de signature n'a de valeur que si son vérificateur échoue proprement face aux cas anormaux. Écrivez des tests couvrant au minimum les scénarios suivants : document modifié après signature, certificat expiré, certificat révoqué, certificat émis par une CA non reconnue, et signature tronquée ou corrompue.
// test-verify.js (exemple avec node:test, integre depuis Node.js 18)
const test = require('node:test');
const assert = require('node:assert');
const { verifyEnvelope } = require('./verify-lib');
test('rejette un document altere', () => {
const result = verifyEnvelope('fixtures/document-altere.signed.json', 'ca/ca-cert.pem');
assert.strictEqual(result.signatureValid, false);
});
test('rejette un certificat expire', () => {
const result = verifyEnvelope('fixtures/cert-expire.signed.json', 'ca/ca-cert.pem');
assert.strictEqual(result.withinValidity, false);
});
test('rejette une chaine de confiance rompue', () => {
const result = verifyEnvelope('fixtures/ca-inconnue.signed.json', 'ca/ca-cert.pem');
assert.strictEqual(result.chainTrusted, false);
});
Lancez-les avec node --test. Les trois tests doivent passer au vert avant tout déploiement en production.
Étape 12 : passer d'un certificat maison à un certificat qualifié eIDAS
Votre CA de démonstration convient pour l'apprentissage et les usages internes, mais elle n'a aucune valeur légale de signature électronique qualifiée au sens du règlement eIDAS. Pour obtenir cette valeur, il faut passer par un prestataire de services de confiance qualifié (QTSP), qui émet le certificat après une vérification d'identité conforme aux actes d'exécution 2025-2026, notamment le règlement IR 2025/1566 sur la vérification d'identité et la norme ETSI TS 119 461 imposée depuis avril 2026 pour l'intégration au portefeuille EUDI.
Techniquement, le changement pour votre code Node.js est minime : vous remplacez votre clé privée locale par un accès à un module matériel de sécurité (HSM) ou à une API de signature à distance fournie par le QTSP, mais les appels crypto.sign() et crypto.verify() restent structurellement identiques. Le format de certificat, X.509, ne change pas non plus : c'est la chaîne de confiance et le processus d'émission qui deviennent réglementés.
En France, pour les usages administratifs ou les marchés publics, le Référentiel Général de Sécurité (RGS) de l'ANSSI, rattaché à cyber.gouv.fr, fixe des niveaux de garantie spécifiques pour l'authentification, la signature et les certificats utilisés par les administrations. Un certificat auto-signé généré avec les commandes de ce tutoriel n'est conforme à aucun de ces niveaux : il sert uniquement de terrain d'apprentissage.
Comparatif des algorithmes de signature disponibles en Node.js
Le choix de l'algorithme de signature influe directement sur la taille des certificats, la vitesse de signature et la compatibilité avec les systèmes existants. Voici un comparatif des options natives du module crypto.
| Algorithme | Taille de clé publique | Taille de signature | Support Node.js | Cas d'usage recommandé |
|---|---|---|---|---|
| RSA-2048 | ~270 octets | 256 octets | Natif depuis toujours | Compatibilité maximale, systèmes legacy |
| RSA-4096 | ~530 octets | 512 octets | Natif depuis toujours | Conformité RGS haute exigence |
| ECDSA P-256 | ~65 octets | ~72 octets | Natif depuis toujours | Bon compromis taille/vitesse, TLS |
| Ed25519 | 32 octets | 64 octets | Natif depuis Node.js 12 | Signature de code, documents, API |
| Ed448 | 57 octets | 114 octets | Natif depuis Node.js 12 | Marge de sécurité supplémentaire |
D'après le standard FIPS 186-5 du NIST, qui régit les algorithmes de signature numérique approuvés, EdDSA (dont Ed25519) figure désormais aux côtés de RSA et ECDSA parmi les schémas reconnus. Pour la grande majorité des cas de signature de documents et de code, Ed25519 est le choix le plus pragmatique en 2026 : signatures compactes, vérification rapide, et absence de paramètres à mal configurer (contrairement à RSA où la taille de clé et le padding doivent être choisis explicitement). Les environnements réglementaires stricts, notamment certains contextes RGS ou certains QTSP, peuvent néanmoins encore exiger RSA ou ECDSA P-256 pour des raisons de compatibilité avec des vérificateurs existants.
Calendrier eIDAS 2.0 : ce qui change et quand
Le calendrier réglementaire européen conditionne directement la feuille de route de tout projet de signature électronique visé pour la France et l'Europe. Voici les jalons confirmés à date de publication de cet article.
| Date | Échéance | Impact technique |
|---|---|---|
| 20 mai 2024 | Entrée en vigueur du règlement (UE) 2024/1183 (eIDAS 2.0) | Nouveau cadre juridique pour les services de confiance qualifiés |
| 29 avril 2026 | Nouveau modèle de listes de confiance (ETSI TS 119 612 v2.4.1) | Les trusted lists nationales intègrent de nouveaux types de services qualifiés |
| Mai 2026 | Procédures renforcées de vérification d'identité (IR 2025/1566) | Émission de certificats qualifiés soumise à des contrôles d'identité stricts |
| Décembre 2026 | Disponibilité obligatoire d'un EUDI Wallet par État membre | Nouveau canal de signature qualifiée pour les citoyens |
| ~Décembre 2027 | Signature qualifiée gratuite via l'EUDI Wallet pour usage non professionnel | Démocratisation de la QES pour les particuliers |
| ~2028 (13 mois après le déploiement des wallets) | Obligation d'acceptation du EUDI Wallet par les grandes entreprises régulées | Intégration obligatoire côté back-office |
Bonne nouvelle pour les équipes techniques : eIDAS 2.0 n'invalide pas les certificats et signatures qualifiés déjà émis sous l'ancien régime. Les certificats X.509 qualifiés existants restent pleinement conformes, ce qui évite une migration brutale des systèmes déjà en production.
5 erreurs fréquentes à éviter
Ces pièges reviennent régulièrement dans les implémentations de signature numérique, y compris dans du code déployé en production.
- Confondre chiffrement et signature. Une signature ne rend pas un document confidentiel : elle prouve son intégrité et son origine. Un document signé mais non chiffré reste lisible par n'importe qui.
- Vérifier uniquement la signature cryptographique sans valider la chaîne de certificats. Un attaquant qui génère sa propre paire de clés peut produire une signature mathématiquement valide ; seule la vérification de la chaîne jusqu'à une racine de confiance protège contre ce scénario.
- Ignorer la date d'expiration et la révocation. Un certificat compromis mais non vérifié auprès d'une CRL ou d'un répondeur OCSP continue de produire des signatures acceptées par un vérificateur naïf.
- Stocker la clé privée de signature en clair sur le serveur applicatif. Utilisez un HSM, un coffre-fort de secrets (Vault, AWS KMS, Azure Key Vault) ou au minimum un chiffrement au repos avec rotation des accès.
- Réutiliser la même paire de clés pour le chiffrement et pour la signature. Les extensions
keyUsageetextendedKeyUsagedu certificat existent précisément pour cloisonner les usages ; les ignorer expose à des attaques croisées entre les deux fonctions.
Dépannage : 8 problèmes courants et leurs solutions
Voici les erreurs les plus fréquentes rencontrées lors de la mise en place de ce type de système, avec leur cause probable et la correction associée.
- Erreur "error:0480006C:PEM routines::no start line". Le fichier de clé ou de certificat n'est pas au format PEM attendu, ou le chemin pointe vers un fichier vide. Vérifiez avec
openssl pkey -in fichier.pem -noout -text. - crypto.sign() lève "Unsupported state or unable to authenticate data". L'algorithme de la clé ne correspond pas au premier paramètre passé à
sign(). Pour Ed25519, ce paramètre doit resternull; pour RSA-SHA256, utilisez'sha256'. - La vérification échoue alors que rien n'a changé dans le document. Vérifiez l'encodage utilisé lors de la lecture du fichier : un document lu en UTF-8 puis re-signé après une conversion de fin de ligne (CRLF vs LF) ne produira pas le même hash.
- "unable to get local issuer certificate" lors de la vérification de chaîne avec OpenSSL. Le certificat intermédiaire n'est pas inclus dans la chaîne fournie au vérificateur. Utilisez le fichier
chain.pemcomplet, pas seulement le certificat du signataire. - Le certificat généré affiche une date de validité incohérente. L'horloge système du serveur est désynchronisée. Vérifiez avec
timedatectlsous Linux et synchronisez via NTP. - openssl ca échoue avec "TXT_DB error number 2". Un certificat avec le même numéro de série existe déjà dans la base de la CA. Supprimez l'entrée obsolète dans le fichier
index.txtou incrémentez le compteur de série. - Le service Express plante au démarrage avec "ENOENT: no such file or directory, open 'certs/signer-key.pem'". Le chemin relatif est résolu depuis le répertoire d'exécution du processus, pas depuis l'emplacement du script. Utilisez
path.join(__dirname, ...). - La signature Ed25519 générée par Node.js n'est pas reconnue par un vérificateur externe en Java ou Python. Vérifiez que l'autre système utilise bien l'encodage brut de la signature (64 octets) et non un format DER, qui diffère pour Ed25519 par rapport à ECDSA.
Conseils avancés pour un déploiement en production
Une fois le prototype validé, plusieurs ajustements sont recommandés avant un déploiement en environnement réel. Premièrement, ne laissez jamais une clé privée de signature résider sur un disque non chiffré ; un module HSM cloud comme AWS CloudHSM ou une offre équivalente chez un fournisseur souverain limite l'exposition même en cas de compromission du serveur applicatif.
Deuxièmement, mettez en place une rotation automatique des certificats de signataire avant leur date d'expiration, avec une alerte déclenchée au moins 15 jours avant l'échéance : le raccourcissement de la durée de vie des certificats (vers 47 jours pour le TLS public d'ici 2029) est un signal fort que l'industrie s'oriente vers une automatisation systématique de ce renouvellement, y compris pour les certificats de signature.
Troisièmement, journalisez chaque opération de signature avec un identifiant unique, l'empreinte du document, l'identité du certificat utilisé et l'horodatage, dans un registre en append-only. Cela facilite les audits de conformité et permet de reconstituer l'historique complet d'un document en cas de litige.
Enfin, si votre organisation doit anticiper la transition post-quantique, gardez à l'esprit que les schémas de signature post-quantiques comme ML-DSA sont encore en cours de normalisation côté profils de certificats X.509 et normes ETSI, et ne sont pas encore obligatoires dans le cadre eIDAS 2.0 en 2026. Documentez cette dépendance dans votre registre de risques technique plutôt que de migrer prématurément vers un algorithme dont l'intégration X.509 n'est pas encore stabilisée.
Structure complète du projet final
À l'issue de ce tutoriel, votre dossier de projet doit ressembler à ceci :
signature-x509-demo/
├── ca/
│ ├── ca-key.pem
│ ├── ca-cert.pem
│ └── ca.crl
├── certs/
│ ├── intermediate-key.pem
│ ├── intermediate-cert.pem
│ ├── signer-key.pem
│ ├── signer-cert.pem
│ └── chain.pem
├── docs/
│ ├── bon-commande.json
│ └── contrat.pdf
├── signed/
│ ├── bon-commande.signed.json
│ └── contrat.proof.json
├── sign.js
├── verify.js
├── sign-pdf.js
├── server.js
├── test-verify.js
├── package.json
└── node_modules/
Ce projet fonctionnel couvre l'intégralité du cycle de vie d'une signature numérique X.509 : génération de la chaîne de confiance, signature de documents structurés et de fichiers binaires, horodatage, révocation, exposition en service HTTP et tests de non-régression. Il constitue une base solide pour évaluer ensuite un vrai prestataire de confiance qualifié eIDAS avant une mise en production.
Questions fréquentes
Quelle est la différence entre une signature électronique simple, avancée et qualifiée ?
Sous eIDAS, une signature simple n'a aucune exigence technique particulière (un scan de signature manuscrite suffit). Une signature avancée doit être liée de façon unique au signataire, permettre de l'identifier et détecter toute modification ultérieure du document : c'est le niveau que ce tutoriel vous permet de construire techniquement. Une signature qualifiée (QES) ajoute une exigence réglementaire : le certificat doit être émis par un prestataire de confiance qualifié (QTSP) et la clé doit résider dans un dispositif QSCD certifié. Seule la QES a une valeur juridique strictement équivalente à une signature manuscrite dans toute l'UE.
Peut-on utiliser Ed25519 pour un certificat X.509 conforme au RGS ?
Techniquement, Ed25519 est un algorithme de signature reconnu et pris en charge par OpenSSL 3.x et Node.js. Sa reconnaissance formelle dans les référentiels de conformité RGS ou par un prestataire qualifié dépend cependant de leurs propres listes d'algorithmes acceptés, qui privilégient encore souvent RSA et ECDSA sur courbes NIST pour des raisons d'interopérabilité historique. Vérifiez toujours les exigences précises de votre QTSP avant de figer un choix d'algorithme en production.
Faut-il chiffrer un document en plus de le signer ?
Cela dépend du besoin. La signature garantit l'intégrité et l'authenticité, pas la confidentialité. Si le document contient des données sensibles qui ne doivent pas être lisibles par un tiers non autorisé, il faut le chiffrer séparément, par exemple avec AES-256-GCM, en plus de le signer.
Comment gérer la clé privée de signature en production sans risque de fuite ?
La meilleure pratique consiste à ne jamais exposer la clé privée brute au code applicatif. Utilisez un module matériel de sécurité (HSM), un service cloud de gestion de clés (AWS KMS, Azure Key Vault, Google Cloud KMS) ou un coffre-fort logiciel comme HashiCorp Vault, qui effectue l'opération de signature sans jamais révéler la clé elle-même à l'application appelante.
Le certificat auto-signé de ce tutoriel a-t-il une valeur légale ?
Non. Un certificat auto-signé, généré comme dans ce tutoriel, n'a de valeur légale de signature électronique qualifiée dans aucun État membre de l'UE. Il sert uniquement à des fins pédagogiques ou pour des usages internes où la valeur probante juridique n'est pas requise, par exemple la signature d'artefacts de build en interne.
Quelle est la durée de validité recommandée pour un certificat de signataire ?
Plus la durée est courte, plus la fenêtre d'exposition en cas de compromission de clé est réduite. La tendance observée sur les certificats SSL/TLS publics, qui passent de 200 jours à 47 jours de validité maximale d'ici 2029, illustre cette logique. Pour un certificat de signataire interne, une durée de 90 jours avec renouvellement automatisé est un point de départ raisonnable.
Que se passe-t-il si le certificat de l'autorité racine est compromis ?
C'est le scénario le plus grave dans une PKI, car toute la chaîne de confiance en dépend. Toutes les signatures émises par les certificats descendants de cette racine, même valides cryptographiquement, doivent être considérées comme non fiables. C'est précisément pour limiter ce risque que l'on utilise un certificat intermédiaire : il permet de révoquer et de régénérer la couche de signature quotidienne sans devoir redistribuer une nouvelle racine à tous les vérificateurs.
L'EUDI Wallet remplacera-t-il les certificats X.509 classiques ?
Non, il s'ajoute comme un nouveau canal plutôt que de remplacer l'infrastructure existante. Le EUDI Wallet devient obligatoire pour chaque État membre d'ici décembre 2026 et permettra, à terme, de créer des signatures qualifiées directement depuis le portefeuille des citoyens. Mais la mécanique sous-jacente reste basée sur des certificats X.509 et des chaînes de confiance PKI, les mêmes concepts que ceux mis en œuvre dans ce tutoriel.




