Démontrer qu'une autorité d'horodatage (TSA) conforme à la RFC 3161, adossée à une PKI et à un module cryptographique, tient dans une pile reproductible que l'on démarre en une commande. Le prototype vise la crédibilité technique auprès de financeurs, pas encore la qualification eIDAS : les écarts au référentiel sont listés en section 8.
┌──────────────────────────────────────────────────┐
client │ Open eIDAS │
(curl, │ │
openssl ts, │ ┌───────────────┐ PKCS#11 ┌─────────────┐ │
Sign*) │ │ tsa-server │─────────▶│ SoftHSM2 │ │
│ RFC 3161 │ │ (Rust) │ │ (→ HSM FIPS │ │
├──────────▶│ │ │ │ en prod) │ │
│ :8318 │ └──┬─────────┬──┘ └─────────────┘ │
│ │ │ │ POST /api/v1/enroll │
│ │ │ ▼ │
│ │ │ ┌───────────────┐ ┌─────────────┐ │
│ │ │ │ ca-server │───│ PostgreSQL │ │
│ OCSP │ │ │ (Rust) │ │ (registre) │ │
├──────────▶│ ┌──┴──┤ racine + │ └─────────────┘ │
│ :8319 │ │ocsp │ CA émettrice │ │
│ │ │(Rust│ :8320 │──▶ PKCS#11 (2 tokens) │
│ │ └─────┴───────┬───────┘ │
│ │ CRL │ /download/<CA>.crl, .cer │
└─────────────────┼────────────────────────────────┘
│ NTP │
▼ ▼
UTC(OP) · UTC(PTB) tiers qui vérifient un certificat
Quatre services, tous écrits et compris par l'équipe :
| Composant | Rôle | Image / langage |
|---|---|---|
tsa-server |
Service RFC 3161, signature des jetons | Rust, binaire unique |
ca-server |
Autorité de certification et d'enregistrement : cérémonie de clé, émission depuis une CSR, approbation RA, publication de la CRL et du certificat de la CA | Rust, binaire unique |
ocsp-responder |
Répondeur OCSP (RFC 6960) pour la CA émettrice | Rust, binaire unique |
| SoftHSM2 | Conservation des clés privées (racine, CA émettrice, TSU, répondeur OCSP — un token par rôle) | softhsm2 (Debian), PKCS#11 |
| PostgreSQL | Registre de la CA : autorités, certificats émis, demandes d'enrôlement, historique des CRL | postgres:17-alpine |
tsa-server et ocsp-responder s'enrôlent auprès de ca-server par la même
API (POST /api/v1/enroll, authentifiée par un secret HMAC partagé), en
demandant chacun son profil. Le répondeur OCSP consulte ensuite la CRL publiée
par la CA (/download), rafraîchie périodiquement — plutôt qu'un accès direct
au registre : il ne voit ainsi que ce qu'un tiers pourrait voir lui-même. Voir
crates/oe-ocsp-core et CA.md.
Rust pour les trois services. Binaire unique sans runtime ni ramasse-
miettes, surface d'attaque réduite, unsafe_code = "forbid" sur tout le
workspace sauf la seule crate d'accès au HSM (crates/oe-hsm, bindings
PKCS#11 via cryptoki) — la garantie de sécurité mémoire qu'exige un
composant qui manipule des clés de signature est ainsi vérifiable
mécaniquement, pas seulement déclarée. Le projet a d'abord été écrit en
Go, puis intégralement reporté en Rust pour cette raison ; l'ASN.1/X.509
est porté à la main sur der/x509-cert (RustCrypto), faute de
bibliothèque RFC 3161/6960 clé en main compatible à l'époque du portage.
PKCS#11 dès le prototype. La clé privée de la TSU ne quitte jamais le
module cryptographique : tsa-server ne manipule qu'un SigningToken dont
chaque signature est déléguée au token. SoftHSM2 parle exactement le même
protocole qu'un HSM certifié FIPS 140-2 niveau 3 ou Critères Communs. Le
passage en production se fait en changeant OPENEIDAS_PKCS11_MODULE et le
label du token — aucune ligne de code applicatif à modifier.
Une autorité de certification écrite en propre. Le certificat de la TSU
doit être émis par une CA distincte, avec un profil contraint et un cycle de
vie auditable. Le projet a d'abord intégré OpenXPKI Community, puis l'a
remplacé par bin/ca-server : l'intégration avait exigé, à plusieurs
reprises, de rétro-ingénierier des comportements internes non documentés,
portant précisément sur les contrôles qu'un audit vient vérifier — validité du
profil émis, effectivité de l'approbation RA. L'arbitrage complet est dans
INDEPENDANCE.md.
Le périmètre réellement couvert est étroit et le reste : émettre depuis une CSR selon un profil compilé, publier une CRL, révoquer, et une machine à états d'approbation à un seul workflow. En contrepartie, l'équipe peut expliquer chaque règle ligne à ligne à un auditeur, et chaque exigence normative est portée par un test exécutable.
Les règles ETSI définies une seule fois. crates/oe-conformance porte
chaque exigence technique applicable sous forme de vérification appelable,
utilisée à trois endroits qui ne peuvent pas diverger : les tests unitaires,
les gardes d'exécution (le certificat produit est relu depuis son DER et
re-contrôlé avant d'être délivré), et le rapport ca-server conformance qui
alimente CONFORMITE-ETSI.md. Une exigence déclarée
couverte sans mécanisme ni test nommé fait échouer la CI.
Séparation enrôlement / service. Le binaire expose deux sous-commandes :
enroll obtient le certificat, serve signe les jetons. Le service refuse de
démarrer si le certificat ne correspond pas à la clé du HSM ou ne porte pas
l'usage étendu id-kp-timeStamping. Cette séparation permet, en production, de
confier l'enrôlement à un opérateur habilité et de ne donner au service qu'un
accès en lecture au certificat.
scripts/bootstrap.shgénère et persiste le secret HMAC d'enrôlement.- PostgreSQL démarre ;
ca-serverapplique ses migrations, exécute la cérémonie de clé (idempotente : deux bi-clés RSA-4096 dans deux tokens PKCS#11 distincts, racine puis CA émettrice, procès-verbal consigné au journal d'audit) et publie une première CRL. Son/healthzne passe qu'une fois cet état servable. - Les conteneurs TSA et OCSP initialisent leur propre token SoftHSM au premier lancement.
tsa-server enrollgénère une bi-clé RSA-3072 dans le token, produit une CSR signée par cette clé et la soumet àPOST /api/v1/enrollavec son authentifiant HMAC. La demande atterrit en attente d'approbation : aucun chemin du code ne mène à l'émission sans décision d'un opérateur identifié.ocsp-responder enrollfait de même avec son profil.- Un opérateur RA approuve (
ca-server ra approve <transaction> <opérateur>). En démonstration et en CI, cette approbation est automatisée sous une identité technique — écart assumé, tracé comme tel au journal (voir CA.md). - À sa scrutation suivante, chaque service reçoit son certificat et sa chaîne, écrits sur son volume d'état. Le certificat est relu et re-contrôlé côté CA avant d'être délivré.
tsa-server servecharge le certificat, vérifie sa cohérence avec la clé du token et sa conformité au profil ETSI, puis écoute sur le port 8318.
L'enrôlement est idempotent de bout en bout : la CA reconnaît une CSR déjà
soumise à son empreinte et retrouve la demande existante, et le service
conserve au redémarrage un certificat encore valide et apparié à la clé du
HSM. Le renouvellement se déclenche automatiquement dans les 30 jours
précédant l'expiration (OPENEIDAS_RENEW_BEFORE) et révoque le certificat
précédent avec le motif superseded.
Le jeton est une TimeStampResp DER contenant un CMS SignedData :
TSTInfoporte la politique d'horodatage, lemessageImprintsoumis, un numéro de série unique,genTimeen UTC et la précision annoncée ;- l'attribut signé
signingCertificateV2(RFC 5035) lie le jeton au certificat exact de la TSU, comme l'exige ETSI EN 319 422 ; - le nonce du client est repris tel quel lorsqu'il est présent ;
- le certificat de la TSU et sa chaîne sont inclus si le client les demande
(
certReq).
Empreintes acceptées : SHA-256, SHA-384, SHA-512. SHA-1 est refusé avec
badAlg, conformément à ETSI TS 119 312.
Un jeton d'horodatage ne vaut que ce que vaut l'horloge qui l'a produit. ETSI EN 319 421 impose que l'heure soit traçable jusqu'à UTC et que la TSA cesse d'émettre dès qu'elle ne peut plus garantir la précision qu'elle annonce.
Le service interroge donc périodiquement plusieurs serveurs NTP de laboratoires de métrologie — par défaut l'Observatoire de Paris (UTC(OP)) et la PTB (UTC(PTB)) — et recoupe leurs réponses. L'heure est jugée traçable lorsque les quatre conditions suivantes sont réunies :
- le quorum de sources est joignable (
OPENEIDAS_TIME_MIN_SOURCES, 2 par défaut) ; - la dérive mesurée reste sous le seuil (
OPENEIDAS_TIME_MAX_OFFSET, 500 ms) ; - les sources s'accordent entre elles à l'intérieur du même seuil ;
- la dernière mesure n'est pas périmée (
OPENEIDAS_TIME_MAX_AGE, 1 h).
Dès qu'une condition tombe, la politique enforce fait refuser chaque
demande avec le failureInfo timeNotAvailable — une réponse RFC 3161
parfaitement valide — et /healthz bascule en 503. Le service ne produit
jamais de jeton dont il ne peut pas défendre la date, ce qui est précisément
ce qu'un auditeur vient vérifier.
L'état complet des mesures (écart par source, dispersion, strate, temps
d'aller-retour, horodatage de la dernière synchronisation) est publié sur
/healthz et /api/v1/policy, et journalisé à chaque cycle.
Deux politiques dégradées existent pour le développement : monitor
journalise l'écart sans bloquer l'émission, disabled désactive la
surveillance. Aucune des deux n'est admissible en production.
Un auditeur ne vérifie pas seulement qu'un jeton est correct : il vérifie que le service était sous contrôle au moment où il l'a émis, et que la trace de cet instant n'a pas été retouchée depuis.
Le service tient donc un journal en JSON Lines dont chaque enregistrement porte l'empreinte SHA-256 du précédent. Modifier une ligne, en supprimer une ou en intercaler une rompt la chaîne, et la rupture est détectable par quiconque relit le fichier — y compris sans accès au service :
docker compose exec tsa tsa-server verify-auditY sont consignés l'ouverture du journal, chaque jeton émis (numéro de série,
genTime, politique, empreinte soumise, présence d'un nonce), chaque refus
avec son failureInfo, chaque mesure de temps avec l'écart par source, et
chaque enrôlement de certificat. Le jeton émis est relu avant d'être
consigné : le journal enregistre ce que contient réellement le jeton, pas ce
que le service croit y avoir mis.
Deux propriétés rendent le dispositif exploitable :
- Une écriture ratée annule l'émission. Si le journal ne peut pas être écrit, la requête échoue. Un jeton non tracé ne sort jamais du service.
- Un journal altéré empêche le démarrage. La chaîne est vérifiée intégralement à l'ouverture.
La tête de chaîne est scellée périodiquement
(OPENEIDAS_AUDIT_SEAL_INTERVAL, une heure par défaut) : la TSU horodate sa
propre empreinte de tête et le jeton obtenu est inscrit au journal, ce qui
date son contenu.
Le scellement ci-dessus reste auto-référentiel : il ne prouve l'antériorité à
un tiers que si l'on fait déjà confiance à la TSU elle-même. Le service
soumet donc la même tête de chaîne à une ou plusieurs TSA publiques
indépendantes (OPENEIDAS_CROSS_TSA_URLS, par défaut FreeTSA.org et
DigiCert), via le protocole RFC 3161 standard, et consigne chaque attestation
obtenue (log.cross_sealed) : émetteur, date, numéro de série et jeton
complet en base64.
Un auditeur n'a besoin de rien d'Open eIDAS pour vérifier une attestation : le certificat de la TSA tierce est public, et les outils standards suffisent —
openssl ts -query -digest <tête-de-chaîne> -sha256 -no_nonce -out head.tsq
openssl ts -verify -in <jeton-décodé> -queryfile head.tsq \
-CAfile <CA-de-la-TSA-tierce> -untrusted <certificat-de-la-TSA-tierce>— une réponse Verification: OK établit que la tête de chaîne, donc tout le
journal qu'elle couvre par construction, existait à la date attestée par une
autorité qui n'a aucun lien avec Open eIDAS.
L'indisponibilité d'une TSA tierce est journalisée mais non bloquante : le scellement propre au service continue, et les autres TSA configurées prennent le relais.
Un journal chaîné et contresigné ne protège que contre l'altération — pas
contre la perte de l'instance elle-même (panne disque, compromission,
suppression accidentelle). À chaque scellement, le service dépose donc une
copie complète et datée du journal (audit-<horodatage>-seq<n>.log) sur un
serveur WebDAV distant (OPENEIDAS_AUDIT_REPLICA_URL) : Nextcloud, un
stockage d'objets exposé en WebDAV, ou tout hébergeur souverain qui l'offre —
aucun fournisseur particulier n'est imposé.
Chaque copie est un journal complet et vérifiable indépendamment :
tsa-server verify-audit audit-20260906T145600Z-seq000030.logretrouve exactement la même chaîne de hachage que sur l'instance d'origine, jusqu'au numéro de séquence capturé. L'échec de la réplication est journalisé mais non bloquant, comme pour le contreseing tiers.
L'état exigence par exigence, avec le mécanisme qui la porte et le test qui le
vérifie, est dans CONFORMITE-ETSI.md — généré depuis
crates/oe-conformance, donc incapable de diverger du code. Le tableau
ci-dessous en donne la lecture d'ensemble.
| Exigence | État du prototype | Cible |
|---|---|---|
| Module cryptographique | SoftHSM2 (logiciel), quatre tokens distincts (racine, CA émettrice, TSU, répondeur OCSP) | HSM certifié FIPS 140-2 niv. 3 / CC EAL4+ ; seul OPENEIDAS_PKCS11_MODULE change |
| Source de temps | Surveillance NTP de deux sources UTC(k) avec suspension automatique de l'émission | Réception redondante et indépendante, calibration documentée, journal des mesures conservé et audité |
| Cérémonie de clé | Scriptée, idempotente, procès-verbal consigné au journal d'audit (empreintes, opérateur, horodatage) — mais sans double contrôle ni témoin | Double contrôle, témoin indépendant, HSM certifié, racine hors ligne après cérémonie (voir CA.md) |
| Approbation RA | Point d'approbation réellement actif : aucun chemin du code ne mène à l'émission sans décision d'un opérateur identifié, consignée en base et au journal. Automatisée sous un compte technique pour que la démonstration/CI s'amorce sans opérateur humain | Revue humaine réelle par un opérateur RA nominatif, à la place de l'approbation automatisée |
| Journalisation | Journal chaîné par hachage, contresigné par des TSA tierces publiques et répliqué hors site à chaque scellement ; durée de conservation contrôlée au démarrage | Politique de conservation formalisée, réplication multi-région |
| Politique d'horodatage | OID de test 1.3.6.1.4.1.99999.1.1.1 ; brouillon de Policy/Practice Statement dans CPS.md |
OID sous l'arc PEN de l'association, CPS.md adopté formellement et publié |
| Profils de certificat | Structures Rust compilées et testées ; le certificat émis est relu depuis son DER et re-contrôlé avant délivrance ; CDP, AIA ca_issuers et répondeur OCSP réellement publiés et vérifiés |
OID de politique de certification propre |
| Continuité | Instance unique ; registre PostgreSQL sauvegardable, journal répliqué hors site | Redondance active/active, sauvegarde et restauration testées, plan de cessation d'activité engagé (voir CA.md) |
| Audit | Aucun | Évaluation par un organisme accrédité (LSTI, Apave), inscription à la liste de confiance |
Le prototype refuse de démarrer sur les écarts qui rendraient les jetons ou les certificats invalides (clé et certificat désaccordés, usage étendu absent ou non critique, clé trop courte, certificat expiré, autorité elle-même non conforme) et journalise un avertissement sur les écarts non bloquants.
Sur le moteur de CA/RA. Il n'y en a plus de tiers : bin/ca-server a
remplacé OpenXPKI Community. Le motif était la difficulté répétée à établir ce
que le moteur faisait réellement — un profil de certificat mal formé y
échouait silencieusement à l'émission plutôt qu'au chargement, et le point
d'approbation RA était contourné par une règle d'éligibilité sans qu'aucune
erreur ne le signale. Ce type d'opacité porte précisément sur des contrôles
qu'un audit eIDAS vient examiner. L'arbitrage, le périmètre repris et l'effort
consenti sont détaillés dans INDEPENDANCE.md.
Ce que ce remplacement ne supprime pas : la nécessité d'un audit de sécurité externe et indépendant — un auditeur scrutera probablement du code maison plus attentivement, faute d'antécédent — ni les mesures organisationnelles (cérémonie sous double contrôle, opérateur RA nominatif, CP/CPS publiés), identiques quel que soit le moteur.
- Temps. Passer d'une synchronisation réseau à une réception redondante et indépendante, conserver le journal des mesures et faire calibrer la chaîne de temps.
- HSM. Remplacer SoftHSM par un module certifié ; le code applicatif est déjà compatible.
- Politique. Publier la TSA Policy et la Practice Statement, obtenir un arc OID propre.
- Cérémonie de clé. Rejouer la cérémonie sur HSM certifié, sous double contrôle et témoin indépendant, avec procès-verbal contresigné ; retirer ensuite le token de la racine du système (voir CA.md).
- Autorité d'enregistrement. Substituer un opérateur RA nominatif à l'approbation automatisée de la démonstration.
- Exploitation. Supervision, deux instances derrière un répartiteur, sauvegarde et restauration testées, procédure de révocation testée.
- Qualification. Constituer le dossier ANSSI et engager l'audit d'un organisme accrédité, en s'appuyant sur CONFORMITE-ETSI.md comme point d'entrée.