BlackMesa Labs

Une signature prouve qui et quoi. Jamais quand.

Le champ « signé le » d'un document signé sort de votre propre horloge. Il est couvert par la signature, donc immodifiable après coup — et choisissable avant. Antidater ne coûte rien, et la chaîne de signatures n'y change rien non plus. La réponse tient en quatre gestes, elle se vérifie avec openssl de série, et elle se divise entre plusieurs juridictions pour trente-deux octets.

Un rapport signé répond à deux questions : quelle clé a signé, et quels constats elle a signés. On le prend en général pour une réponse à une troisième, qui n’y est pas.

Le champ signed_at est pourtant bien là, et il est couvert par la signature — on ne peut donc pas le modifier après coup sans casser le sceau. Mais il est lu sur l’horloge de la machine qui signe. La vôtre. Antidater un document signé ne coûte rien du tout : il suffit de signer avec l’horloge qu’on veut.

Et le chaînage ne rattrape pas ça. Un rapport qui nomme l’empreinte de celui qu’il remplace fait une belle piste d’audit — tant que c’est une autre personne qui tient les clés. Si c’est vous, vous pouvez reconstruire la chaîne entière dans l’ordre, et elle se vérifiera parfaitement.

Alors : à quoi ressemble une date dont on ne peut pas discuter ?

La réponse, en quatre gestes

Elle tient en une phrase : on sort la date de chez soi. Dans le détail, quatre gestes, dont aucun ne demande de nous faire confiance.

  1. Faire attester une empreinte par un tiers, pas le document. Trente-deux octets partent, rien d’autre. Ce tiers n’a aucun intérêt dans vos conclusions, et c’est ce qui donne sa valeur à sa signature.
  2. Choisir un tiers dont la racine est déjà publique, pour que votre lecteur vérifie avec openssl de série et rien qui vienne de vous.
  3. Comparer l’empreinte avant de parler de confiance. C’est une comparaison de hachés ; elle ne demande ni certificat, ni racine, ni autorisation.
  4. Garder la plus ancienne attestation, et réattester tant que le schéma qui la signe tient.

Ce que vous obtenez est précis, et il faut l’énoncer précisément : cette empreinte existait au plus tard à cette date, selon quelqu’un qui n’est pas vous. C’est une borne supérieure — et c’est exactement la forme de l’affirmation « nous avions inventorié avant le 31 décembre 2026 ».

Le reste de cet article explique pourquoi chacun des quatre gestes est nécessaire, ce que ça coûte, et où ça s’arrête.

Ce qu’une autorité d’horodatage fait, exactement

RFC 3161. Vous lui envoyez une empreinte, jamais le document. Trente-deux octets. Elle vous renvoie un jeton signé disant que cette empreinte lui a été présentée à cet instant.

C’est tout le mécanisme, et c’est toute l’élégance : une autorité peut attester une date sans qu’on lui dise ce qu’elle date. Rien de votre code, de vos noms de fichiers ou de vos constats ne sort de la machine — une empreinte ne se retourne pas.

L’autorité n’a par ailleurs aucun intérêt dans vos conclusions, et c’est précisément ce qui donne sa valeur à sa signature.

Ce qu’on fait attester, et pourquoi ce choix-là

Trois candidats : le fichier, la signature, ou l’empreinte des constats. Nous attestons l’empreinte, pour une raison qui tient en une commande.

Elle est imprimée dans le rapport. Donc n’importe qui vérifie la date sans jamais toucher à notre outil :

openssl ts -verify -digest 8ecb829ccc278ef7ab3138f56e07ee13319cbb72ce1dabbfc58ee7b4d6b670b5 \
  -in report.html.tsr -CAfile <magasin de certificats>

Forme simplifiée, pour montrer la mécanique. Ce -CAfile est le piège le plus coûteux de tout cet article, et la section « sur quelle machine » plus bas donne la version qui marche partout.

Pas de fichier à canonicaliser, pas d’ordre de champs sur lequel se mettre d’accord, pas de format à nous. openssl ts -verify -digest prend une empreinte hexadécimale directement.

Et ça survit à une clé éphémère. Quand un rapport est signé par une paire créée pour lui seul puis détruite, la clé n’existe plus — la date attestée, si.

C’est aussi pour ça que le jeton est un fichier à part, en DER brut à côté de la signature : c’est exactement ce que lit la commande openssl ts de série. Un artefact qu’on ne peut ouvrir qu’avec notre outil n’est pas une preuve, c’est une promesse.

Le piège : openssl ts -verify ne répond pas à la question qu’on croit

Voici la partie qui nous a demandé de la mesure plutôt que de la lecture.

openssl ts -verify s’arrête sur une chaîne de certificats qu’il ne sait pas construire — avant d’avoir comparé l’empreinte. Conséquence : deux situations radicalement différentes reviennent avec le même message d’échec.

  • « Je n’arrive pas à remonter jusqu’à une racine de confiance » — l’autorité est auto-signée, ou il manque un intermédiaire. Ce n’est pas une falsification.
  • « Ce jeton atteste un autre document » — là, c’est grave.

Sur une autorité auto-signée, les deux affichent Verification: FAILED. Nous l’avons constaté en passant volontairement une mauvaise empreinte : même réponse que pour la bonne.

La correction est de comparer l’empreinte soi-même, d’abord, en la lisant dans le jeton. C’est une comparaison de hachés : elle ne demande aucun certificat, aucune racine, aucune confiance. Après seulement vient la question de la chaîne.

Ce qui donne quatre états, pas deux :

état ce que ça veut dire
vérifiée la signature de l’autorité tient, jusqu’à une racine de confiance de cette machine
chaîne non vérifiée l’empreinte correspond bien à ce document ; aucune chaîne n’a pu être construite. Autorité auto-signée ou intermédiaire absent — pas une falsification
invalide le jeton atteste une autre empreinte, ou il a été modifié
invérifiable ici pas d’openssl, ou aucun magasin de certificats

Les confondre, c’est transformer un certificat racine manquant en accusation de faux. Ou, bien pire, l’inverse.

Quelle autorité ? C’est une décision, pas un défaut

Nous n’avons pas mis d’autorité par défaut dans l’outil. Qui atteste vos dates est une décision du même ordre que le régime réglementaire que vous retenez — un défaut la prendrait pour vous, dans une juridiction que vous n’avez pas choisie.

Voilà ce que donne la mesure, sur quatre autorités gratuites :

autorité résultat
Certum / Asseco (Pologne, UE) vérifiée avec le magasin du système, RSA-4096
GlobalSign (Belgique, UE) vérifiée avec le magasin du système, RSA-3072
DigiCert (États-Unis) vérifiée avec le magasin du système, RSA-4096
Sectigo (UK) jeton dont openssl ne retrouve pas le signataire
FreeTSA (Allemagne) auto-signée : il faut livrer sa racine pour que quiconque vérifie
TSA fédérale belge auto-signée également
timestamp.apple.com répond, mais chaîne auto-signée et politique annoncée 1.2.3 — ce n’est pas un service public

Le critère qui tranche n’est pas la notoriété, c’est la racine publique : un lecteur vérifie avec openssl de série et rien qui vienne de vous. Dès qu’il faut joindre un certificat racine à votre document, votre preuve voyage avec une pièce que vous fournissez vous-même — ce qui en retire une bonne partie de l’intérêt.

« Vous avez juste déplacé la confiance »

C’est l’objection sérieuse, et il faut la prendre dans sa version forte : vous avez remplacé « croyez mon horloge » par « croyez l’horloge de Certum ». Le problème n’est pas résolu, il a changé d’adresse.

Elle est en partie juste. Voilà les trois raisons pour lesquelles le déplacement n’est pas neutre, et ce qui reste quand on les a admises.

1. La forme de l’affirmation change. Votre signed_at est une date que vous choisissez. Le jeton est une borne que quelqu’un d’autre pose. Pour antidater, il ne vous suffit plus de régler une horloge : il faut qu’une autorité émette un jeton faux, ou que vous en fabriquiez un — c’est-à-dire que vous cassiez RSA-4096. On est passé d’un geste gratuit à une attaque.

2. Les intérêts ne sont pas les mêmes. L’autorité ne sait pas ce qu’elle date : elle ne voit qu’une empreinte. Elle n’a donc aucun moyen de favoriser une conclusion plutôt qu’une autre, et son activité entière repose sur le fait que sa signature veut dire quelque chose. Antidater pour vous, c’est risquer tout son métier sur votre fichier. Pour une autorité qualifiée eIDAS, ces obligations sont auditées, et c’est précisément pour ça que sa racine est dans les magasins de certificats.

3. Et surtout : cette confiance se divise. Une autorité reste un point de confiance unique. Mais rien, dans RFC 3161, n’empêche de présenter la même empreinte à plusieurs autorités — elles ne voient que ces trente-deux octets, elles n’ont rien à coordonner, et elles n’ont pas besoin de savoir qu’elles ne sont pas seules. Nous l’avons fait sur l’empreinte de l’inventaire de ce site, en présentant la même empreinte à trois d’entre elles :

autorité juridiction date attestée jeton publié
Certum / Asseco Pologne (UE) 6 oct. 2026 21:58 UTC report.html.tsr
GlobalSign Belgique (UE) 8 oct. 2026 02:19 UTC report.html.2.tsr
DigiCert États-Unis 8 oct. 2026 02:19 UTC report.html.3.tsr

Ces trois fichiers sont en ligne à côté du rapport, et les trois se vérifient avec la recette plus bas. Le coût marginal d’une autorité est d’une requête HTTP : une autorité ne voit que trente-deux octets, elle n’a rien à coordonner, et elle n’a pas besoin de savoir qu’elle n’est pas seule.

Les dates diffèrent, et c’est le mécanisme qui se montre : Certum tient la plus ancienne attestation de ces constats et la garde — les deux autres ont été ajoutées le jour où nous avons écrit cet article. Ce qu’on gagne est précis, et mérite de l’être : chaque autorité pose une borne indépendante. La date qu’on peut défendre sans accorder sa confiance à un seul opérateur est donc la plus tardive des trois, le 8 octobre ; celle qu’on peut défendre en faisant confiance à Certum est le 6. Falsifier la plus ancienne ne détache plus ce document d’une date : les deux autres le bornent toutes seules.

Ce qui reste, et qu’il faut dire. Un jeton donne une borne supérieure, jamais une borne inférieure : il atteste que ces constats existaient au plus tard à cette date, et il ne dira jamais qu’ils n’existaient pas avant. Si votre besoin est dans l’autre sens — prouver une antériorité qu’on vous contesterait — ce n’est pas l’outil.

Et personne n’atteste rétroactivement. Une autorité ne signera pas aujourd’hui une date de la semaine dernière, et c’est bien tout l’intérêt. Le corollaire est désagréable : la date doit être demandée au moment où le document est produit. Un horodatage qu’il faut penser à aller chercher est un horodatage qu’on ne fera pas — ce qui est la seule raison pour laquelle ce mécanisme appartient à l’outil qui produit le rapport, et pas à une procédure.

Le faire chez vous, aujourd’hui, sans nous

Rien de tout cela n’a besoin de notre outil. Quatre commandes, openssl et curl, sur n’importe quel artefact que vous voulez pouvoir dater — un SBOM, un rapport d’audit, un paquet de version, un procès-verbal.

# 1. l'empreinte de ce que vous voulez dater
openssl dgst -sha256 -r artefact.json | cut -d' ' -f1
7643cc3ff2957a137b15c509382463efe5fe29c26a14224009462266adf77672

# 2. la requête — « -cert » demande à l'autorité d'inclure son certificat,
#    sans quoi personne ne pourra vérifier sans vous le demander
openssl ts -query -digest 7643cc3f… -sha256 -cert -out horodatage.tsq

# 3. l'envoyer. 69 octets partent, un jeton de 6 ko revient
curl -s -H 'Content-Type: application/timestamp-query' \
  --data-binary @horodatage.tsq http://time.certum.pl > horodatage.tsr

# 4. la vérification — c'est la commande que votre lecteur lancera. Les deux
#    premières lignes trouvent son magasin de certificats et la chaîne que le
#    jeton porte déjà ; la section « sur quelle machine » explique pourquoi
O=$(openssl version -d | sed 's/.*"\(.*\)"/\1/')
for f in "$O/cert.pem" "$O/certs/ca-certificates.crt" /etc/ssl/ca-bundle.pem; do
  [ -f "$f" ] && CA="$f" && break
done
openssl ts -reply -in horodatage.tsr -token_out -out token.der
openssl pkcs7 -inform DER -in token.der -print_certs -out chain.pem

openssl ts -verify -digest 7643cc3f… -in horodatage.tsr \
  -CAfile "$CA" -untrusted chain.pem
Verification: OK

Rangez le .tsr à côté de l’artefact et imprimez l’empreinte dans le document. C’est tout : votre lecteur n’a besoin ni de vous, ni d’un format maison, ni d’une bibliothèque.

Pour lire ce qu’un jeton dit — la date, et l’empreinte qu’il atteste :

openssl ts -reply -in horodatage.tsr -text | grep '^Time stamp'
Time stamp: Oct  7 20:50:11 2026 GMT

openssl ts -reply -in horodatage.tsr -text \
  | awk '/^Message data:/{f=1;next} /^[A-Za-z]/{f=0} f' \
  | sed -E 's/^ *[0-9a-f]{4} - //; s/   .*$//' | tr -dc '0-9a-f'
7643cc3ff2957a137b15c509382463efe5fe29c26a14224009462266adf77672

Cette dernière ligne est la réponse pratique au piège décrit plus haut : elle compare l’empreinte sans rien demander à personne, et elle marche même quand la chaîne de certificats ne se construit pas.

Petite note sur le tr -dc '0-9a-f', parce qu’elle illustre le reste de l’article mieux que n’importe quel paragraphe : nous avions d’abord écrit tr -d ' -\n', qui marche très bien sur un Mac. Le tr de GNU lit ces trois caractères comme une plage, de l’espace au retour à la ligne, la trouve inversée, et refuse. Nous l’avons découvert en rejouant cet article dans un conteneur Debian vide avant de le publier. Toutes les commandes de cette page y sont passées.

Avec une autorité à racine publique, openssl ts -verify fait d’ailleurs la distinction tout seul — nous avons modifié le document et relancé la commande :

message imprint mismatch
Verification: FAILED

Un message clair et spécifique, qu’on n’obtient pas avec une autorité auto-signée. C’est l’argument le plus concret pour en choisir une dont la racine est déjà dans les magasins de certificats.

Dans Sablier, tout ça tient dans un drapeau — --timestamp=<url> — parce qu’une bonne pratique qu’il faut se rappeler de faire à la main est une bonne pratique que personne ne fait. Le drapeau se répète, ou prend une liste séparée par des virgules, et chaque autorité reçoit la même empreinte :

sablier scan . --sign=sablier.key \
  --timestamp=http://time.certum.pl,http://timestamp.digicert.com

Chaque jeton va dans son propre emplacement — report.html.tsr, report.html.2.tsr, dans l’ordre où les autorités ont été nommées — et l’emplacement appartient à l’autorité, d’un passage au suivant : celle qui était injoignable aujourd’hui laisse son emplacement vide au lieu de décaler les autres et de leur faire perdre leur antériorité. sablier verify parcourt les emplacements plutôt que de lire le premier, parce qu’un lecteur à qui l’on a remis trois attestations et une commande qui en vérifie une en aurait vérifié un tiers, et conclu sur la totalité.

Mais les jetons produits sont des fichiers ordinaires, vérifiables par les mêmes commandes, par quelqu’un qui n’a jamais installé l’outil.

Et vous pouvez l’essayer tout de suite, sur un vrai document : l’inventaire cryptographique de ce site est publié avec sa signature et son jeton.

curl -sO https://mesa.black/audit/report.html
curl -sO https://mesa.black/audit/report.html.tsr

O=$(openssl version -d | sed 's/.*"\(.*\)"/\1/')
for f in "$O/cert.pem" "$O/certs/ca-certificates.crt" /etc/ssl/ca-bundle.pem; do
  [ -f "$f" ] && CA="$f" && break
done
openssl ts -reply -in report.html.tsr -token_out -out token.der
openssl pkcs7 -inform DER -in token.der -print_certs -out chain.pem

openssl ts -verify \
  -digest 8ecb829ccc278ef7ab3138f56e07ee13319cbb72ce1dabbfc58ee7b4d6b670b5 \
  -in report.html.tsr -CAfile "$CA" -untrusted chain.pem
Verification: OK

Les deux autres jetons sont à côté — report.html.2.tsr et report.html.3.tsr, GlobalSign et DigiCert — et se vérifient avec la même commande en changeant le nom du fichier. La section suivante explique pourquoi ce bloc fait six lignes au lieu d’une.

L’empreinte est imprimée dans le rapport lui-même, et la date que vous obtiendrez est le 6 octobre 2026 à 21 h 58 UTC — la première fois que ces constats ont été attestés, et pas la dernière fois que nous avons republié la page.

Et sur quelle machine ? La commande que tout le monde publie est fausse

Voici la partie que nous n’avions pas vue venir, et qui a demandé une série de mesures plutôt qu’une lecture de documentation.

La commande qu’on trouve partout pour vérifier un jeton RFC 3161 est celle-ci :

openssl ts -verify -digest <empreinte> -in horodatage.tsr \
  -CAfile /etc/ssl/certs/ca-certificates.crt

Elle contient deux hypothèses fausses.

Le magasin de certificats n’est pas à cet endroit. ca-certificates.crt est une convention Debian. Sur Fedora, sur Rocky, sur openSUSE, ce fichier n’existe pas — et quand -CAfile désigne un fichier absent, openssl ts -verify ne dit pas « ce fichier n’existe pas » : il répond Verification: FAILED. Votre lecteur en conclut que votre jeton est mauvais.

Et le jeton porte sa chaîne, mais tout le monde ne la lit pas. Un jeton demandé avec -cert contient les certificats qui remontent jusqu’à la racine — nous l’avons vérifié, le nôtre en porte trois. OpenSSL 3 les utilise. LibreSSL, c’est-à-dire l’openssl livré avec macOS, ne les utilise pas et répond :

Verify error:unable to get local issuer certificate
Verification: FAILED

Même message d’échec. Même conclusion erronée pour le lecteur.

Nous avons donc cherché une recette qui marche partout, et nous l’avons mesurée sur huit cibles :

# 1. le magasin de certificats de votre openssl, où qu'il soit
O=$(openssl version -d | sed 's/.*"\(.*\)"/\1/')
for f in "$O/cert.pem" "$O/certs/ca-certificates.crt" /etc/ssl/ca-bundle.pem; do
  [ -f "$f" ] && CA="$f" && break
done

# 2. la chaîne que le jeton porte déjà, extraite pour la fournir explicitement
openssl ts -reply -in report.html.tsr -token_out -out token.der
openssl pkcs7 -inform DER -in token.der -print_certs -out chain.pem

# 3. la vérification
openssl ts -verify -digest <empreinte> -in report.html.tsr \
  -CAfile "$CA" -untrusted chain.pem
cible openssl magasin trouvé résultat
Debian 13 OpenSSL 3.5.7 /usr/lib/ssl/cert.pem OK
Ubuntu 24.04 OpenSSL 3.0.13 /usr/lib/ssl/cert.pem OK
Alpine 3.22 OpenSSL 3.5.9 /etc/ssl/cert.pem OK
Fedora 42 OpenSSL 3.2.6 /etc/pki/tls/cert.pem OK
Rocky 9 OpenSSL 3.5.8 /etc/pki/tls/cert.pem OK
openSUSE Leap 15.6 OpenSSL 3.1.4 /etc/ssl/ca-bundle.pem OK
macOS + Homebrew OpenSSL 3.6.0 /opt/homebrew/etc/openssl@3/cert.pem OK
macOS, openssl d’Apple LibreSSL 3.3.6 /private/etc/ssl/cert.pem OK

openssl version -d donne le répertoire contre lequel ce binaire-là a été compilé, ce qui couvre six cas sur huit ; openSUSE range son paquet ailleurs, d’où la troisième entrée de la boucle. Et -untrusted chain.pem ne change rien là où ce n’était pas nécessaire — c’est ce qui permet d’avoir une recette plutôt qu’une par système.

C’est maintenant celle que le rapport imprime, avec la note qui explique les trois étapes. Parce qu’une preuve qu’on ne peut vérifier que sur la distribution de celui qui l’a produite n’est pas une preuve portable, et qu’un lecteur dont la commande échoue n’apprend pas que sa distribution est différente : il apprend que notre sceau est cassé.

La limite, imprimée dans le document

Une autorité d’horodatage signe en RSA ou en ECDSA. Soit exactement ce qu’un inventaire post-quantique classe comme vulnérable.

Un outil qui passe quarante pages à expliquer que les signatures périment ne s’accorde pas une exception pour celle dont il dépend. Le rapport l’écrit donc à côté de la date :

Ce que le jeton ne prouve pas : rien au-delà de RSA-4096, le schéma qui le signe. Une date à opposer plus tard doit être réattestée tant que ce schéma tient.

Un horodatage est une preuve pour un litige dans les prochaines années. Pas pour 2040.

Le piège de conception : la plus ancienne attestation est la seule qui vaut

Celui-ci mérite d’être énoncé parce qu’il se prend à l’envers très naturellement.

Un site qui republie son inventaire redemande volontiers un jeton à chaque publication. C’est le geste qui semble le plus propre — et il détruit la preuve qu’on cherchait à construire.

Parce que la seule affirmation qui compte est : ces constats existaient au plus tard à cette date. Écraser la première attestation par une plus récente efface exactement ça. Vous vous retrouvez avec un document qui prouve qu’il existait ce matin, alors que vous vouliez prouver qu’il existait en octobre.

La règle qui en découle est courte : le sceau ne bouge pas tant que les constats ne bougent pas. Un jeton qui atteste déjà cette empreinte est conservé ; une empreinte différente en redemande un. Même chose pour la signature : la resigner à l’identique produit un maillon de chaîne qui relie une empreinte à elle-même, c’est-à-dire du bruit déguisé en historique.

Pourquoi maintenant

La feuille de route coordonnée du groupe de coopération NIS, du 23 juin 2025, fait de l’inventaire cryptographique un First Step dû au 31 décembre 2026.

« Nous avions inventorié avant l’échéance » est une affirmation sur une date. C’est le seul argument qui nous a convaincus d’ajouter tout ce qui précède : sans un tiers, cette phrase ne vaut que la parole de celui qui la prononce — et le champ qui la porte sort de son horloge.

Bonnes pratiques

  • Ne confondez pas « signé le » et « daté ». Le premier sort de votre horloge, le second d’un tiers qui n’a aucun intérêt dans vos conclusions.
  • Faites attester une empreinte, pas un fichier. Elle s’imprime dans le document, elle se vérifie avec une commande standard, et elle survit à une clé détruite.
  • Comparez l’empreinte du jeton avant de poser la question de la confiance. C’est une comparaison de hachés, elle ne demande aucun certificat — et sans elle, « chaîne invérifiable » et « ce jeton parle d’autre chose » se ressemblent.
  • Choisissez une autorité à racine publique, sinon votre preuve voyage avec une pièce que vous fournissez vous-même.
  • Testez la commande de vérification ailleurs que sur votre machine. Le chemin du magasin de certificats et le openssl installé varient, et les deux échouent avec le même Verification: FAILED qu’un faux jeton.
  • Gardez la plus ancienne attestation. Redemander un jeton à chaque publication efface la seule chose qu’il établissait.
  • Divisez la confiance si l’enjeu le justifie. La même empreinte peut partir chez deux ou trois autorités de juridictions différentes, pour une requête HTTP de plus chacune. La date défendable sans faire confiance à personne en particulier est alors la plus tardive de la liste.
  • Demandez la date au moment de produire le document. Personne ne l’attestera rétroactivement — c’est l’intérêt du mécanisme, et sa contrainte.

Points de vigilance

  • Une chaîne de signatures tenue par une seule clé n’est pas une piste d’audit : elle se reconstruit en entier, dans l’ordre, et elle se vérifie.
  • Un horodatage repose sur une signature classique. Il faut le réattester tant que le schéma tient, et le dire dans le document plutôt que de laisser croire à une preuve éternelle.
  • L’empreinte quitte la machine. Elle ne révèle rien, mais si votre document promet que rien ne sort, cette promesse devient « rien ne sort, sauf si vous demandez un horodatage » — et ça doit être une option explicite, jamais un défaut.
  • Un jeton qu’il faut accompagner du certificat racine de l’autorité est une preuve à moitié portable.
  • Un horodatage borne par le haut, jamais par le bas. Il ne prouvera jamais qu’un document n’existait pas avant la date attestée, et présenter une borne supérieure comme une date exacte est le genre d’approximation qui se retourne devant quelqu’un d’attentif.

← Tous les articles