Attestation de preuves de conformité, v1.
https://attestkeep.com/attestation/compliance-evidence/v1 est le predicateType de chaque attestation qu'Attestkeep signe sur un rapport de conformité. Si un document vous a mené à cette URL, cette page en est la définition : l'affirmation, chaque champ, l'enveloppe, l'encodage signé et un vrai exemple à vérifier.
L'affirmation
En une phrase : ce rapport de conformité a été produit par cette installation et n'a pas été modifié depuis. C'est toute l'affirmation, et tout ce qui suit en découle.
L'affirmation est transportée dans le format que le monde de la supply chain utilise déjà pour cela (un statement in-toto dans une enveloppe DSSE, signée en Ed25519) plutôt que dans un schéma de signature de notre cru. Ce choix est délibéré : un format que nous seuls pourrions vérifier ferait de nous un goulot d'étranglement, et la valeur du document tient justement à ce qu'un auditeur puisse le vérifier sans nous. La clé de signature est générée dans l'installation et ne la quitte jamais ; on ne demande à personne de faire confiance à autre chose que sa clé publique.
Le statement et son subject
Le payload de l'enveloppe est un statement in-toto, version 1. Ses champs extérieurs :
| Champ | Type | Sens |
|---|---|---|
_type | string | Toujours https://in-toto.io/Statement/v1. |
subject | array | Exactement un resource descriptor désignant le rapport concerné : name est l'identifiant du rapport et digest.sha256 le SHA-256 des octets exacts générés, en 64 caractères hexadécimaux minuscules sans préfixe sha256:. |
predicateType | string | Toujours https://attestkeep.com/attestation/compliance-evidence/v1, c'est-à-dire l'adresse de cette page. |
predicate | object | L'affirmation elle-même ; chaque champ est défini dans la section suivante. |
Le statement porte sur un document, donc le document est le subject. Le rapport lui-même n'est pas transporté dans l'attestation, et c'est voulu : le rapport est volumineux, et un lecteur peut l'avoir sous forme de PDF ou de CSV. Ce qui doit être prouvable, c'est que la copie qu'il a en main est celle qui a été signée ; un digest répond à cette question pour n'importe quelle copie des octets, alors qu'une copie intégrée n'y répondrait que pour elle-même.
Le framework et la période d'audit figurent dans la signature, à côté du subject. La même période évaluée selon deux frameworks donne deux documents différents, et un lecteur ne doit pas pouvoir faire passer l'un pour l'autre. Aucun élément du statement (subject, digest, période, framework, horodatages) ne peut être modifié après signature sans que la vérification échoue.
Champs du predicate
Les champs facultatifs sont omis du JSON quand ils sont vides, jamais remplis d'une valeur de substitution. Un horodatage que l'installation n'a pas est écrit comme une chaîne vide, jamais comme une date inventée.
| Champ | Type | Sens |
|---|---|---|
report_id | string | L'identité du document, le même identifiant que celui imprimé sur le rapport, pour rapprocher d'un coup d'œil l'attestation et le rapport que vous avez en main. Il reprend le name du subject. |
framework | string | Le mapping de conformité selon lequel le rapport a été assemblé, par exemple soc2. Dans la signature, pour que le rapport d'un framework ne puisse pas être présenté comme celui d'un autre. |
period | object | La fenêtre d'audit couverte par le rapport. |
period.start, period.end | string | Horodatages RFC 3339, normalisés en UTC. Transmis comme chaînes, parce que ce sont les octets exacts qui sont signés. |
installation | object | Identifie le signataire sans décrire le cluster. |
installation.fingerprint | string | L'identifiant opaque du cluster auquel la licence est liée : un hash sans topologie, sans noms, sans rien qu'un client refuserait de voir dans un document qui sort de chez lui. |
installation.license_id | string, facultatif | Permet à l'émetteur de confirmer que l'installation était sous licence à ce moment-là : c'est le lien entre « ce fichier est intact » et « il vient d'une vraie installation ». |
installation.binding_id | string, facultatif | La liaison de licence sous laquelle cette installation s'est activée ; elle sert à la même confirmation. |
generated_at | string | Le moment où l'installation a produit le rapport, d'après sa propre horloge, en RFC 3339 UTC. C'est une assertion du signataire, pas une preuve : rien ne lie le document à une horloge de confiance, et un lecteur qui en a besoin doit l'obtenir ailleurs. |
generated_by | string, facultatif | Le compte qui a demandé le rapport, sous la forme user:<account id>. Depuis la 1.2.0, ce n'est jamais un nom ni une adresse e-mail ; sur demande, l'opérateur retrouve l'identifiant dans le journal d'audit de sa console. |
generator | object | Le logiciel qui a produit le rapport. |
generator.name, generator.version | string | La release dont les mappings de conformité étaient en vigueur. Les mappings évoluent à mesure que les frameworks sont lus plus attentivement ; une preuve qui ne dit pas sur quelle lecture elle repose ne peut pas être réexaminée plus tard. |
L'enveloppe DSSE
Le statement est placé dans une enveloppe DSSE de cette forme :
{
"payload": "<standard base64 of the statement JSON>",
"payloadType": "application/vnd.in-toto+json",
"signatures": [
{ "keyid": "ak-…", "sig": "<base64 of an Ed25519 signature>" }
]
}payloadest le base64 standard, avec padding, des octets exacts du statement sérialisé. Le statement est sérialisé une seule fois : les octets signés sont les octets transmis.payloadTypefait partie de ce qui est signé (voir l'encodage ci-dessous) ; une signature sur un statement in-toto ne peut donc jamais être présentée comme une signature sur les mêmes octets lus comme autre chose.sigest une signature Ed25519 (RFC 8032) surPAE(payloadType, payload), c'est-à-dire sur le pre-authentication encoding, et non sur la chaîne base64.keyidest dérivé de la clé publique :"ak-"suivi des 16 premiers caractères hexadécimaux du SHA-256 de la clé de 32 octets. Puisqu'il est dérivé, il ne peut pas contredire la clé qu'il nomme ; et ce n'est qu'un indice : un vérificateur doit s'appuyer sur une clé en laquelle il a déjà confiance, jamais sur une clé que l'enveloppe lui désigne.- Une enveloppe peut porter plusieurs signatures. Un vérificateur teste chaque entrée avec sa clé de confiance et accepte l'enveloppe si l'une d'elles se vérifie.
L'encodage PAE
Ce qui est signé, c'est le pre-authentication encoding du payload avec son type :
PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP bodyoù SP est une espace unique et LEN la longueur en octets en ASCII décimal. Les longueurs comptent des octets, jamais des caractères. Par exemple, PAE("http://example.com/HelloWorld", "hello world") est la suite d'octets DSSEv1 29 http://example.com/HelloWorld 11 hello world.
Toute la sécurité de DSSE tient dans cette idée. Signer le payload seul permettrait de présenter une signature faite sur un statement in-toto comme une signature sur autre chose qui aurait les mêmes octets sous un autre payloadType. PAE lie les deux, avec préfixe de longueur, pour qu'aucun ne puisse passer pour une partie de l'autre.
La chaîne de certificats
La signature de l'enveloppe prouve que le rapport est inchangé et nomme une clé. Elle ne peut pas, à elle seule, prouver à qui appartient cette clé : un vérificateur qui prend la clé publique des mains de celui qui apporte le document ne prouve que la cohérence interne. Le certificat de licence comble cette lacune. C'est un document qu'Attestkeep signe pour chaque installation sous licence, et il porte la clé de signature de preuves de l'installation dans la signature : un lecteur qui fait confiance à une seule clé Attestkeep peut donc vérifier un rapport de n'importe quelle installation :
Clé racine Attestkeep
└─ signe → certificat de licence (JWT compact)
└─ cnf.jwk désigne → clé de preuves de l'installation
└─ signe → enveloppe DSSE
└─ le digest du subject épingle → rapportLa clé racine. Cette valeur, obtenue sur cette page via TLS, est le seul point de confiance de la chaîne :
kid: ak1
ed25519: e287aabe4712197cfa5b6fdf9ea8f2d7f0333ce16ca69aac7e4a3f1e84c04179Le certificat est un compact JWT (RFC 7515) : base64url(header) "." base64url(payload) "." base64url(signature), en base64url sans padding (RFC 4648, section 5, sans =). Le header est {"alg":"EdDSA","typ":"JWT","kid":"ak1"} ; un vérificateur doit refuser tout alg autre que EdDSA, ce qui exclut par construction les attaques JWT classiques par rétrogradation (alg=none, confusion d'algorithme). La signature est en Ed25519 sur les octets ASCII de header_b64 "." payload_b64.
Champs du payload lus par un vérificateur de chaîne (tout champ inconnu doit être ignoré : l'émetteur peut ajouter des claims) :
| Champ | Type | Sens |
|---|---|---|
sub | string | L'id de la licence pour laquelle le certificat a été émis. |
fpr | string | L'empreinte du cluster auquel la licence est liée : c'est la valeur que doit avoir installation.fingerprint dans le predicate. |
plan | string | Le code du plan, informatif pour un vérificateur de chaîne. |
iat, exp | number | Émission et expiration, en secondes unix. Un exp à 0 signifie que le certificat n'expire pas. |
jti | string | Le numéro de série du certificat. |
cnf.jwk | string | Le claim de confirmation de la RFC 7800 : la clé publique Ed25519 de preuves de l'installation, en exactement 64 caractères hexadécimaux. Pas un objet JWK imbriqué, mais les 32 octets bruts encodés en hexadécimal. Un certificat sans ce champ ne garantit aucune clé et ne peut ancrer aucune chaîne. |
Pour vérifier un rapport par la chaîne :
- Découpez le certificat sur
.en exactement trois parties ; décodez le header et refusez-le sialgn'est pasEdDSA. - Vérifiez la signature Ed25519 sur les octets ASCII de
header_b64 "." payload_b64avec la clé racine ci-dessus. - Décodez le payload et lisez
cnf.jwk, la clé de preuves de l'installation. - Vérifiez l'enveloppe DSSE avec cette clé, exactement comme le décrivent les sections ci-dessus.
- Contre-vérification : le
predicate.installation.fingerprintdu statement doit être égal aufprdu certificat. Sans cette étape, on pourrait présenter un certificat authentique à côté d'une enveloppe authentique venant d'une autre installation. Siinstallation.license_idest présent et diffère desub, avertissez le lecteur.
L'expiration est une information, pas un motif de rejet. Les certificats changent tous les trente jours, et un rapport du trimestre dernier est garanti par le certificat en vigueur à l'époque : refuser les certificats expirés rendrait invérifiable tout ancien rapport. Un vérificateur doit indiquer la date d'un certificat expiré et laisser le jugement au lecteur.
Un exemple complet
Cette enveloppe a été produite par le signataire de référence. Elle est signée avec une clé de test délibérément publique (la seed Ed25519 est faite des octets 0x01 à 0x20) : elle illustre donc le format, pas la provenance d'un vrai rapport. Les vraies attestations sont signées avec une clé générée dans chaque installation, qui ne la quitte jamais.
{
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicnB0LTlmMmM3YTQxZDBiODNlNTYiLCJkaWdlc3QiOnsic2hhMjU2IjoiNzQ3YjgzZWNkMTY2OTEyNjk2NmVlNjAyZTM3MjI0NTE2Y2M5YmMyZGQ5NWFhZWJlNDIwZWQxY2IzNGVkZWMwYyJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2F0dGVzdGtlZXAuY29tL2F0dGVzdGF0aW9uL2NvbXBsaWFuY2UtZXZpZGVuY2UvdjEiLCJwcmVkaWNhdGUiOnsicmVwb3J0X2lkIjoicnB0LTlmMmM3YTQxZDBiODNlNTYiLCJmcmFtZXdvcmsiOiJzb2MyIiwicGVyaW9kIjp7InN0YXJ0IjoiMjAyNi0wNy0wMVQwMDowMDowMFoiLCJlbmQiOiIyMDI2LTA3LTMxVDIzOjU5OjU5WiJ9LCJpbnN0YWxsYXRpb24iOnsiZmluZ2VycHJpbnQiOiIzZjdiMWE5ZTVjMmQ4ZjA0YTZiM2UxYzdkOTA1MmU4YTFiNGY2YzNkN2U5YTBiMmM0ZDZlOGYwYTFiM2M1ZDdlIiwibGljZW5zZV9pZCI6ImxpYy03YzMxZjBkMiIsImJpbmRpbmdfaWQiOiJiaW5kLWE5NGUyYzE3In0sImdlbmVyYXRlZF9hdCI6IjIwMjYtMDgtMDFUMDY6MDA6MDBaIiwiZ2VuZXJhdGVkX2J5IjoidXNlcjp1c3ItN2QyYzFhOWU0YjNmNWM2ZDhlMGYxYTJiIiwiZ2VuZXJhdG9yIjp7Im5hbWUiOiJhdHRlc3RrZWVwIiwidmVyc2lvbiI6IjAuMS4wIn19fQ==",
"payloadType": "application/vnd.in-toto+json",
"signatures": [
{
"keyid": "ak-65b60673d6ed884b",
"sig": "DUTZfcrSxs8IuyoPBcBOVa10sgg5Yy2/QQkCF8t0RFa2U+sjp5NGfVwHprbZ30dNtCogfZeexpLR8EYKsEWKCQ=="
}
]
}Le payload se décode en ce statement :
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "rpt-9f2c7a41d0b83e56",
"digest": {
"sha256": "747b83ecd1669126966ee602e37224516cc9bc2dd95aaebe420ed1cb34edec0c"
}
}
],
"predicateType": "https://attestkeep.com/attestation/compliance-evidence/v1",
"predicate": {
"report_id": "rpt-9f2c7a41d0b83e56",
"framework": "soc2",
"period": {
"start": "2026-07-01T00:00:00Z",
"end": "2026-07-31T23:59:59Z"
},
"installation": {
"fingerprint": "3f7b1a9e5c2d8f04a6b3e1c7d9052e8a1b4f6c3d7e9a0b2c4d6e8f0a1b3c5d7e",
"license_id": "lic-7c31f0d2",
"binding_id": "bind-a94e2c17"
},
"generated_at": "2026-08-01T06:00:00Z",
"generated_by": "user:usr-7d2c1a9e4b3f5c6d8e0f1a2b",
"generator": {
"name": "attestkeep",
"version": "0.1.0"
}
}
}La clé publique qui la vérifie, sous la forme qu'attend le vérificateur :
79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664Vérifiez-la avec le script de la documentation, qui n'utilise que la bibliothèque standard, ou avec n'importe quelle implémentation DSSE : elle passe, et changer un seul caractère du payload la fait échouer. Le vérificateur dans le navigateur prend le paquet livré plutôt qu'une enveloppe et une clé isolées : il est conçu pour un auditeur qui détient une vraie archive, pas pour lire une spécification.
Vérifier une attestation
Trois façons, par ordre croissant d'indépendance :
- la page de vérification de ce site, qui tourne entièrement dans votre navigateur et n'envoie rien nulle part ;
- tout outil qui comprend DSSE et in-toto, car le format ne contient rien de propriétaire ;
- une douzaine de lignes de votre cru, dans n'importe quel langage : vérifier le certificat de licence avec la clé racine, prendre la clé de l'installation dans son
cnf.jwk, décoder le payload en base64, construire le PAE, vérifier la signature Ed25519 avec cette clé, et seulement ensuite lire le statement.
Quelle que soit la voie choisie, le point de confiance est le même : la clé racine d'Attestkeep, qui garantit la clé de l'installation par le certificat de licence, comme décrit plus haut. Jamais une clé prise dans le document lui-même, dont le keyid est une étiquette et non une preuve d'identité, et jamais une clé acceptée sur la foi de celui qui a livré le rapport. Une clé remise en main propre vérifie quand même une enveloppe, mais ne prouve que la cohérence interne, pas l'origine. Les limites sont les mêmes elles aussi : une chaîne valide prouve que le document est inchangé et quelle installation l'a signé. Ce n'est pas une signature électronique au sens juridique, et elle ne prouve pas quand la signature a eu lieu : generated_at et la période relèvent de la déclaration de l'installation elle-même. Ces deux limites sont détaillées sur la page de vérification.
Mis à jour : 29 août 2026