Vérifier une preuve hors ligne
01Ce que ce document permet #sec_e0af9bcf
REVIEWIT produit, à chaque signature, une attestation scellée dont on peut extraire un paquet de vérification : un fichier qui contient tout ce qu'il faut pour refaire la démonstration ailleurs.
Ce document est la spécification complète de ce format. Il est publié pour une raison précise : permettre à un tiers de vérifier une preuve sans nous, et même sans nous croire. Il décrit assez exactement la canonicalisation, la construction des arbres et la vérification des preuves d'inclusion pour qu'un auditeur, une direction des systèmes d'information ou un cabinet indépendant puisse écrire son propre vérificateur et obtenir le même verdict que le nôtre.
C'est la forme la plus haute de la garantie que REVIEWIT annonce. Une plateforme qui dirait « faites-nous confiance, c'est scellé » demanderait exactement ce que ce document rend inutile.
02À qui il s'adresse #sec_c1bc9732
À un lecteur technique : la personne qui vérifiera une preuve, ou celle qui écrira l'outil qui la vérifie. Il n'est pas nécessaire pour lire un rapport d'audit, ni pour utiliser REVIEWIT.
Un auditeur qui souhaite seulement obtenir un verdict n'a pas à lire ce texte : l'écran « Vérifier une preuve » du Workspace Center et l'outil en ligne de commande appliquent ce contrat, sans écart. Ce document existe pour ceux qui refusent de s'en remettre à l'un ou à l'autre — et ils ont raison de le pouvoir.
03Ce qu'il vous faut #sec_48297a56
Quatre éléments, et ils ne viennent pas du même endroit :
- Le paquet de vérification — le fichier lui-même. Il vous est remis avec la preuve qu'on vous oppose, ou vous le téléchargez depuis REVIEWIT.
- Un vérificateur — celui que vous écrivez à partir du présent document, ou celui que REVIEWIT met à disposition. Les deux doivent rendre le même verdict ; s'ils divergent, c'est un défaut, et il nous concerne.
- Le trousseau public de l'autorité — servi par ce portail à l'adresse /.well-known/seal-keyring.json , et remis avec l'annexe sécurité de votre contrat. Il porte les clés publiques avec lesquelles se contrôle la signature du sceau, et seulement elle . Tout le reste — l'empreinte, les racines de Merkle, le chaînage, la preuve d'inclusion — se vérifie sans aucune clé, depuis le seul fichier.
- L'empreinte de la clé racine — portée par l'annexe sécurité signée avec votre contrat. C'est elle, et elle seule, qui établit que le trousseau que vous avez téléchargé est bien le nôtre (§8.3).
04Ce que le fichier seul établit, et ce qu'il n'établit pas #sec_192edcae
Sans trousseau, un paquet reste entièrement vérifiable dans sa cohérence interne : que le manifeste produit bien l'empreinte annoncée, que les listes produisent bien les racines attestées, qu'un élément donné figure bien dans le périmètre scellé.
Ce que le fichier seul n'établit pas , c'est que ce contenu a été scellé par nous. Un paquet peut se déclarer scellé sans l'être : cette déclaration ne vaut rien tant que la signature de l'autorité n'a pas été contrôlée avec sa clé publique. Un vérificateur conforme doit donc distinguer ces deux constats et ne jamais présenter le second comme acquis à partir du premier.
051. Canonicalisation, version 1 #sec_53b1cef4
Un document canonique est un JSON UTF-8 sans BOM, sans espace ni retour à la ligne, tel que :
- les clés d'un objet sont triées par ordre ordinal des points de code ;
- les chaînes sont normalisées Unicode NFC ; seuls " , \ et les caractères de contrôle sont échappés ; le non-ASCII n'est pas échappé ;
- les seuls nombres sont des entiers signés sur 64 bits ; aucun flottant ; une proportion s'exprime pour dix mille ;
- les instants sont en temps universel, au format yyyy-MM-ddTHH:mm:ss.ffffffZ — six décimales, la septième tronquée ; les dates civiles au format yyyy-MM-dd ;
- une valeur non applicable est absente ; il n'existe ni null , ni chaîne vide tenant lieu d'absence ;
- les suites d'octets sont en hexadécimal minuscule .
Un document contenant un null , un flottant ou une clé dupliquée n'est pas un document canonique et est refusé.
062. Hachage #sec_9d21b1bf
SHA-256 , identifiant "SHA-256" porté dans cryptography.hash du manifeste.
073. Schéma d'arbre, version 1 #sec_3b2d707a
| Élément | Définition | |
|---|---|---|
| Préfixes de domaine | feuille 0x00 , nœud 0x01 , arbre vide 0x02 , racine 0x03 | |
| Feuille | H(0x00 ‖ aléa ‖ contenu) — l'aléa fait 32 octets, propre à chaque feuille | |
| Nœud | H(0x01 ‖ gauche ‖ droite) | |
| Niveau impair | le nœud orphelin est promu tel quel au niveau supérieur, jamais dupliqué | |
| Engagement | objet canonique `{"domain":{"empty":2,"leaf":0,"node":1,"root":3},"hash":"SHA-256","leafCount":n,"schema":1,"tree":"scope" | "decisions"}` |
| Racine publiée | H(0x03 ‖ engagement ‖ racine brute) pour n ≥ 1 | |
| Racine du lot vide | H(0x02 ‖ engagement) avec leafCount = 0 |
Ordre des feuilles. Clé composite (SHA-256(identité), identité) , comparée octet par octet en lexicographique non signé, le second terme ne départageant que les égalités du premier. Deux identités identiques dans un lot sont un refus.
Deux arbres. scope : le contenu de la feuille est l'identité stable canonique {"criterion":…,"object":…,"type":…} . decisions : le contenu est la décision canonique (§5). Même ordre, même position pour un même item.
Aléas. Dérivés par SEAL de la génération cryptography.secretGeneration , par (arbre, position). Le secret ne figure dans aucun paquet.
084. Preuve d'inclusion #sec_b535f3ee
{"commitment":{…},"content":hex,"path":[{"side":"left"|"right","sibling":hex} | {"promoted":true} …],"position":p,"root":hex,"salt":hex,"schema":1}
Vérification : recalculer la feuille, remonter le chemin en exigeant à chaque niveau exactement ce que la structure impose — un frère du bon côté quand index a un voisin, une promotion quand index est le dernier d'un niveau impair —, puis H(0x03 ‖ engagement ‖ nœud) et comparer à la racine attestée. Un chemin plus court ou plus long que la structure est un refus.
095. Manifeste, version 1 #sec_9dbbc182
Blocs de l'objet racine (clés triées) : actors , context , cryptography , decisions , governance , header , qualification , scope , snapshot , statement .
- header : attestation , canonicalization (1), complementRank , lot , originLot (absent pour le lot initial), schema (1).
- decisions[i] : actor , decidedAt , exceptionUntil , identity , justification , origin ( human | defaultRule | systemClosure | nonDecidedUncovered ), previous , rule ( id , motive , version — présent si et seulement si l'origine n'est pas humaine), verdict .
- qualification : closure , conflictOfInterest , coverage ( complete ), decisionMode , origins (quatre décomptes, calculés).
- governance.indicators : les quatre entiers avec collected:true , ou {"collected":false} .
- cryptography : hash , preflight ( {"state":"unavailable"} ou référence), secretGeneration , trees.scope et trees.decisions ( commitment , root ).
Forme scellée. L'empreinte scellée — celle que l'ancrage porte — est SHA-256 de la forme canonique où scope et decisions sont remplacés par leur engagement {"count":n,"hash":SHA-256(liste canonique)} . Les listes ne voyagent que dans le paquet complet ; les racines de Merkle les engagent déjà.
106. Paquets #sec_de906bc8
| Champ | Complet | Ponctuel |
|---|---|---|
| specification | REVIEWIT-SignOff-Verification/1 | idem |
| kind | complete | ponctual |
| attestation | forme scellée | forme scellée |
| digest | SHA-256(attestation) | idem |
| scope.items , decisions | listes complètes, en ordre canonique | — |
| salts.scope , salts.decisions | tous les aléas, par position | — |
| proof | — | une preuve, avec l'aléa de la seule feuille dévoilée |
| sealing | {"state":"unanchored"} ; ou anchored avec domain , segment , rank , anchoredAt , seal:"pending" ; ou sealed avec en plus seal.reference , seal.sealedAt | idem |
117. Procédure #sec_9a00ec24
- Relire le paquet comme document canonique (§1) ; refuser sinon.
- Vérifier specification .
- Recalculer SHA-256(attestation) et le comparer à digest , et à l'empreinte tenue par ailleurs le cas échéant.
- Vérifier header.schema , header.canonicalization , cryptography.hash .
- Complet : vérifier les engagements des deux listes ; vérifier qu'à chaque position la décision porte l'identité de l'item ; reconstruire chaque arbre depuis la liste et les aléas (§3) et comparer racine et leafCount à cryptography.trees .
- Ponctuel : vérifier que la preuve vise la racine et le leafCount attestés pour son arbre ; vérifier la preuve (§4).
- Lire sealing.state et le restituer tel quel : cette version ne vérifie ni l'ancrage ni le sceau, et un paquet unanchored n'est pas opposable.
Un paquet est valide quand tous les contrôles exécutés réussissent. Cela n'établit pas qu'il est scellé.
128. Trousseau public et ancrage par clé racine #sec_71a42b6e
Le sceau d'un paquet se vérifie avec la clé publique de l'autorité , publiée dans un trousseau : un fichier JSON, indépendant des paquets, que le tiers détient.
{
"keys": [ { "kid": "signing:1", "alg": "ecdsa-p256-sha256", "spki": "<DER SubjectPublicKeyInfo, base64>" } ],
"attestation": {
"version": "seal.keyring.v1",
"count": 1,
"digest": "<hex>",
"alg": "ecdsa-p256-sha256",
"rootKid": "keyring-root:1",
"rootSpki": "<DER SubjectPublicKeyInfo de la racine, base64>",
"signature": "<base64>"
}
}
Le paquet nomme sa clé, le trousseau la fournit. sealing.seal.keyId désigne l'entrée à employer. Une entrée n'est jamais retirée d'un trousseau : la retirer rendrait invérifiables toutes les preuves qu'elle a scellées.
8.1 Empreinte du contenu
Retenir les entrées portant à la fois kid , alg et spki — une entrée incomplète ne confère aucune clé et n'entre pas dans le calcul. Les trier par kid , en comparaison ordinale sur les points de code. Former une ligne par entrée, kid | alg | spki suivie d'un saut de ligne U+000A . L'empreinte est le SHA-256 de la concaténation UTF-8 de ces lignes, en hexadécimal minuscule.
La date de création d'une entrée, si elle figure, n'entre pas dans le calcul : c'est un instantané d'affichage.
8.2 Vecteur signé par la racine
seal.keyring.v1|<nombre d'entrées retenues>|<empreinte du contenu>
Signé en forme texte, encodé en UTF-8, par la clé racine, selon attestation.alg . Les suites admises sont celles du sceau : ecdsa-p256-sha256 (signature ASN.1 DER, RFC 3279) et rsa-pkcs1-sha256 . Un algorithme inconnu est un refus, jamais une admission.
8.3 Ce que le tiers détient, et pourquoi la racine
L'ancre du dispositif est l' empreinte de la clé racine — SHA-256 de son SubjectPublicKeyInfo DER, en hexadécimal minuscule — portée par l'annexe sécurité que le client signe avec son contrat.
Elle porte sur la racine , jamais sur le fichier. Un trousseau grandit : chaque rotation de clé de scellement y ajoute une entrée. Une empreinte du fichier se périmerait donc à la première rotation, et un auditeur détenant le bon fichier, périmé, verrait tomber une preuve authentique. La racine, elle, ne change pas ; l'annexe signée n'est jamais rouverte.
8.4 Procédure de vérification d'un trousseau
- Recalculer l'empreinte du contenu (§8.1) et le nombre d'entrées retenues, et les comparer à attestation.digest et attestation.count . Recalculer d'abord, vérifier la signature ensuite : contrôler la signature sur l'empreinte déclarée laisserait passer un trousseau dont on a changé les clés en conservant le bloc signé.
- Vérifier attestation.signature sur le vecteur (§8.2) avec attestation.rootSpki .
- Comparer l'empreinte de attestation.rootSpki à celle que porte l'annexe.
Six issues, et une seule est opposable :
| Issue | Ce qui est établi |
|---|---|
| Trousseau ancré | Signature vérifiée et racine conforme à l'annexe. Seul état opposable. |
| Signé, racine non contrôlée | Aucune empreinte d'annexe fournie : cohérence interne seulement — n'importe qui peut signer un trousseau avec sa propre racine. |
| Signé par une autre racine | Le fichier n'est pas celui que l'annexe désigne. |
| Altéré | Le contenu ne produit plus l'empreinte attestée. |
| Attestation invalide | La racine déclarée n'a pas signé ce contenu. |
| Non ancré | Aucun bloc attestation — un trousseau antérieur au dispositif. Ce n'est pas une faute ; c'est un fichier sans ancre. |
Un trousseau non ancré permet toujours de contrôler un sceau. Ce qu'il ne permet pas, c'est d'établir d'où vient la clé publique employée — et une signature vérifiée avec une clé de provenance inconnue n'établit rien.