BénéficesDoc CLIVersion 0.1Démarrer

Continuum Academy · Chapitre 03

Anatomie d'une attestation in-toto

Dans le chapitre précédent, nous avons séparé hash, attestation, provenance et signature. Nous savons à quoi chacune de ces notions sert. Ouvrons maintenant l’attestation de payment-service-4.7.2.tar.gz.

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "payment-service-4.7.2.tar.gz",
      "digest": {
        "sha256": "3a16...9f21"
      }
    }
  ],
  "predicateType": "https://example.com/attestation/build/v1",
  "predicate": {
    "...": "..."
  }
}

Voici la structure d’un Statement in-toto v1 : le document qui associe un sujet à une affirmation. Le type example.com et le contenu du predicate sont pédagogiques, non standardisés. Le digest est fictif et abrégé ; les points de suspension ne constituent pas une empreinte utilisable. Nous compléterons l’exemple dans ce chapitre.

Nous étudions l’in-toto Attestation Framework, pas les mécanismes historiques layout/link d’in-toto. Le chapitre 04 remplacera notre Predicate pédagogique par une véritable structure SLSA Provenance.

1 — Quatre champs, quatre rôles

Lisons d’abord les quatre noms, sans entrer dans les valeurs :

  • _type : comment lire la structure du Statement ?
  • subject : de quel artefact parlons-nous ?
  • predicateType : quel genre d’affirmation faisons-nous ?
  • predicate : quelles données portent cette affirmation ?

Le Predicate est la partie consacrée à l’affirmation elle-même. Il est placé dans le Statement, qui précise à quoi cette affirmation s’applique. Ces deux niveaux évitent de devoir redéfinir la manière de désigner l’artefact pour chaque nouvelle sorte de déclaration.

2 — _type : quel objet sommes-nous en train de lire ?

{
  "_type": "https://in-toto.io/Statement/v1"
}

Cet extrait isole le champ ; ce n’est pas un Statement complet. Pour la version étudiée, sa valeur est exactement https://in-toto.io/Statement/v1. Elle identifie le schéma de l’objet extérieur, c’est-à-dire sa structure et les règles de lecture associées.

Un lecteur reconnaît ainsi un Statement v1 avant d’examiner son affirmation. La valeur ne dit pas si celle-ci parle de fabrication, de tests ou de livraison. C’est le rôle de predicateType. Les deux identifiants se trouvent au même niveau dans le JSON, mais répondent à deux questions différentes.

3 — subject : de quoi parlons-nous ?

Les crochets de subject sont significatifs : c’est une liste. Notre exemple contient une entrée, mais un Statement peut désigner plusieurs sujets. Chaque sujet utilisé ici comporte un digest.

Dans cette entrée, name aide à distinguer et à retrouver le fichier : payment-service-4.7.2.tar.gz. Le couple sha256 / valeur indique l’algorithme et l’empreinte à comparer. Pour rattacher le Statement au contenu reçu, le vérificateur utilise le digest.

Un destinataire pourrait renommer l’archive en release.tar.gz sans changer ses octets. À l’inverse, deux fichiers différents pourraient porter le même nom. Le nom facilite la lecture et peut servir à des règles locales ; il ne remplace pas la comparaison cryptographique du contenu.

4 — predicateType : quelle question posons-nous ?

predicateType est une URI, un identifiant de type. Ici : https://example.com/attestation/build/v1. Cette valeur désigne la définition selon laquelle lire le Predicate, pas l’adresse de téléchargement de l’artefact.

Une même structure de Statement pourrait porter une provenance, un résultat d’analyse de vulnérabilités ou des informations de livraison. Ces expressions désignent des catégories ; elles ne sont pas, à elles seules, les URI normatives à écrire dans le JSON.

Le sujet répond à « à propos de quoi ? ». Le type répond à « quel genre d’affirmation ? ». Si un outil ne connaît pas ce type, il ne peut pas conclure que l’affirmation satisfait ses exigences simplement parce que le JSON est lisible.

5 — predicate : que disons-nous ?

EXEMPLE PÉDAGOGIQUE — PAS UN SCHÉMA STANDARD. Voici un extrait montrant notre type et ses données :

{
  "predicateType": "https://example.com/attestation/build/v1",
  "predicate": {
    "source": "https://git.example.org/payments/payment-service",
    "commit": "7d34a8b4e1f98c2040e9844ab754ae068943bb16",
    "builder": "payments-release-builder"
  }
}

Nous avons choisi trois champs pour rendre la lecture concrète. source, commit et builder ne sont pas des champs génériques imposés par in-toto. Notre exemple les définit à cet emplacement ; un autre type de Predicate peut employer une structure complètement différente.

in-toto fournit le cadre du Statement. Le type de Predicate définit le sens et le schéma des données internes. Dans la spécification Statement v1, predicate est facultatif et son absence équivaut à un objet vide ; c’est ensuite le type concerné qui détermine les données nécessaires. Notre exemple le renseigne explicitement.

6 — Regardons le Statement en entier

Voici maintenant un objet JSON complet. Le digest a la longueur d’un SHA-256 : 64 caractères hexadécimaux fictifs, sans correspondance avec une archive réellement fournie. Le Predicate reste notre modèle pédagogique.

Lire le Statement, champ par champ

Un objet JSON complet. Le surlignage guide la lecture sans masquer les autres champs. Predicate pédagogique, non standardisé ; valeurs fictives.

Les quatre champs sont visibles ensemble. Les notes donnent le rôle de chacun.

in-toto Statement v1

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "payment-service-4.7.2.tar.gz",
      "digest": {
        "sha256": "3a16000000000000000000000000000000000000000000000000000000009f21"
      }
    }
  ],
  "predicateType": "https://example.com/attestation/build/v1",
  "predicate": {
    "source": "https://git.example.org/payments/payment-service",
    "commit": "7d34a8b4e1f98c2040e9844ab754ae068943bb16",
    "builder": "payments-release-builder"
  }
}
  1. 01 / _type

    La valeur Statement/v1 indique comment lire l’objet extérieur.

  2. 02 / subject

    La comparaison avec le fichier reçu porte sur digest.sha256, pas sur name.

  3. 03 / predicateType

    Cette URI example.com est pédagogique. Ce n’est pas une provenance SLSA.

  4. 04 / predicate

    source, commit et builder appartiennent à notre exemple ; in-toto ne les impose pas.

La lecture suit les quatre zones : reconnaître le Statement, identifier le contenu concerné, sélectionner la définition de l’affirmation, puis lire ses données. Les accolades et les crochets rendent les relations visibles : digest appartient à une entrée de subject ; builder appartient à notre predicate.

Aucun champ signature n’apparaît dans cet objet. Ce n’est pas un oubli du dessin.

7 — Mais où est la signature ?

Nous avons parlé de signature au chapitre précédent. Pourtant, il n’y en a aucune dans ce Statement. Le Statement et l’enveloppe de signature sont deux couches différentes.

Le document porte l’affirmation. L’enveloppe permet de transporter sa représentation et les éléments nécessaires à son authentification. DSSE, pour Dead Simple Signing Envelope, est l’enveloppe recommandée par le framework ; elle n’est pas l’unique enveloppe autorisée dans tous les contextes. La spécification des enveloppes in-toto décrit cette séparation.

Envelopper notre document ne change donc pas la place de subject ou de predicate. Nous ajoutons une couche autour de ses octets, pas une propriété dans le Statement.

8 — Le Statement à l’intérieur de DSSE

Dans l’enveloppe JSON DSSE, les champs s’appellent payloadType, payload et signatures. Le payload est le contenu transporté : ici, les octets du Statement, encodés en base64. Cet encodage permet de les représenter dans une chaîne JSON ; il ne les chiffre pas.

Un Statement dans son enveloppe

Le document reste le même. DSSE transporte ses octets encodés et des signatures dans une couche extérieure.

La vue décodée montre le Statement contenu dans payload. payloadType et signatures appartiennent à l’enveloppe.

DSSE Envelope

payloadTypeapplication/vnd.in-toto+json

payload

Aperçu abrégé du base64 ; aucune donnée n’est chiffrée.

ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YxIiwKICAic3ViamVjdCI6IFsKICAgIHsKICAg

in-toto Statement v1 · Vue décodée

_type
https://in-toto.io/Statement/v1
subject
payment-service-4.7.2.tar.gz
predicateType
https://example.com/attestation/build/v1
predicate
source · commit · builder

signatures

[
  {
    "keyid": "demo-key",
    "sig": "<base64 signature>"
  }
]

Signature de démonstration non calculée. keyid est un indice facultatif, pas une identité authentifiée.

Voir le JSON avec le payload complet

Le payload encode exactement le Statement affiché plus haut. La valeur de sig est un emplacement réservé : cette enveloppe n’est pas un exemple cryptographiquement vérifiable.

{
  "payloadType": "application/vnd.in-toto+json",
  "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YxIiwKICAic3ViamVjdCI6IFsKICAgIHsKICAgICAgIm5hbWUiOiAicGF5bWVudC1zZXJ2aWNlLTQuNy4yLnRhci5neiIsCiAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgInNoYTI1NiI6ICIzYTE2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5ZjIxIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL2V4YW1wbGUuY29tL2F0dGVzdGF0aW9uL2J1aWxkL3YxIiwKICAicHJlZGljYXRlIjogewogICAgInNvdXJjZSI6ICJodHRwczovL2dpdC5leGFtcGxlLm9yZy9wYXltZW50cy9wYXltZW50LXNlcnZpY2UiLAogICAgImNvbW1pdCI6ICI3ZDM0YThiNGUxZjk4YzIwNDBlOTg0NGFiNzU0YWUwNjg5NDNiYjE2IiwKICAgICJidWlsZGVyIjogInBheW1lbnRzLXJlbGVhc2UtYnVpbGRlciIKICB9Cn0=",
  "signatures": [
    {
      "keyid": "demo-key",
      "sig": "<base64 signature>"
    }
  ]
}

Voici la forme simplifiée de cette enveloppe :

{
  "payloadType": "application/vnd.in-toto+json",
  "payload": "<base64 encoded Statement>",
  "signatures": [
    {
      "keyid": "demo-key",
      "sig": "<base64 signature>"
    }
  ]
}

Les valeurs entre chevrons sont des emplacements réservés, pas du base64 à vérifier. Le volet dépliable du schéma contient, lui, l’encodage exact du Statement affiché plus haut ; sa signature reste fictive.

payloadType indique comment interpréter les octets décodés. Une enveloppe signée peut contenir une ou plusieurs signatures. Chaque sig contient normalement une signature encodée en base64. keyid est un indice facultatif pour trouver une clé ; il n’est pas authentifié à lui seul et ne constitue pas une identité de confiance. Ces noms et rôles viennent de l’enveloppe JSON DSSE.

9 — Pourquoi ne pas signer simplement le JSON brut ?

DSSE lie cryptographiquement le type du payload et ses octets, grâce au Pre-Authentication Encoding, ou PAE. Ce mécanisme prépare un message non ambigu avant la signature ; il évite notamment de réinterpréter les mêmes octets signés sous un autre type de message.

La signature porte sur cette préparation des octets décodés, pas sur le texte base64 de l’enveloppe. Le vérificateur conserve les octets exacts : réindenter le Statement puis le sérialiser à nouveau peut changer le message à vérifier, même si les valeurs JSON paraissent identiques. Le protocole DSSE définit précisément ce traitement.

10 — Que fait réellement un vérificateur ?

Recevoir l’enveloppe et recevoir l’archive donnent accès à deux objets différents. Le vérificateur doit établir leurs relations, puis examiner l’affirmation selon ses règles. Il ne peut pas déduire tous ces résultats d’un seul contrôle de signature.

Cinq contrôles, cinq questions

Une lecture des points de contrôle, sans verdict simulé. Chaque ligne demande des données ou des règles différentes.

Les cinq contrôles restent distincts. Leur ordre à l’écran est pédagogique ; l’implémentation doit aussi analyser l’enveloppe pour la vérifier.

  1. ADSSE

    Vérifier la signature

    Le type et les octets sont-ils authentifiés par une clé acceptée ?

  2. B_type

    Lire le Statement

    Cette version du schéma est-elle prise en charge ?

  3. CArtefact reçu

    Calculer → comparer subject.digest

    L’empreinte du fichier correspond-elle au sujet déclaré ?

  4. DpredicateType

    Choisir l’interprétation

    Savons-nous lire ce genre d’affirmation ?

  5. Epredicate

    Évaluer selon la politique

    Ces données satisfont-elles nos exigences ?

VALID SIGNATURE ≠ MATCHING ARTIFACT

MATCHING ARTIFACT ≠ ACCEPTABLE CLAIM

A — Vérifier l’enveloppe. La signature correspond-elle au type et aux octets du payload ? La clé utilisée est-elle acceptée pour ce contexte ? La validité cryptographique et l’acceptation de la clé sont deux décisions.

B — Lire le Statement. Le _type correspond-il à une structure prise en charge ? Un document inconnu ne devient pas interprétable parce qu’il est signé.

C — Identifier le sujet. Le vérificateur calcule le digest de l’archive réellement reçue et le compare à subject.digest. Il lui faut donc les octets du fichier, pas seulement le document qui le décrit.

D — Comprendre l’affirmation. Le predicateType correspond-il à une définition prise en charge ? Savoir décoder du JSON ne suffit pas à comprendre toutes les affirmations possibles.

E — Évaluer le Predicate. Les données satisfont-elles la politique du destinataire ? Pour notre exemple, une règle pourrait exiger un commit attendu ou un builder précis, sans prouver que le récit décrit fidèlement l’exécution.

VALID SIGNATURE ≠ MATCHING ARTIFACT

MATCHING ARTIFACT ≠ ACCEPTABLE CLAIM

Ces contrôles forment une grille de lecture, pas un ordre d’exécution universel. En pratique, l’outil doit déjà analyser l’enveloppe pour vérifier sa signature, puis interpréter les mêmes octets authentifiés.

11 — Trois erreurs fréquentes

  • « subject.name identifie cryptographiquement l’artefact. » Non : le nom est lisible ; le digest permet de rattacher le contenu au sujet.
  • « Une signature DSSE valide prouve que le Predicate est vrai. » Non : elle authentifie le message selon la clé et le modèle de confiance retenus, sans observer les événements déclarés.
  • « _type et predicateType sont interchangeables. » Non : le premier identifie le schéma du Statement ; le second, le type sémantique et le schéma du Predicate.

12 — Ce que le standard fixe, et ce qu’il laisse à décider

Le Statement in-toto v1 fixe comment désigner les sujets, identifier le type de Predicate et placer ses données. DSSE fournit un protocole de signature et un format d’enveloppe. Des outils différents disposent ainsi d’une structure commune à lire.

Cela ne choisit pas automatiquement les acteurs dignes de confiance, les clés acceptées ou la politique métier du destinataire. Cela ne garantit pas non plus la vérité de l’affirmation, ni la sécurité du logiciel. Nous réserverons la gestion de confiance à son propre chapitre.

13 — Et la provenance dans tout cela ?

Comme au chapitre 02, la provenance se trouve dans l’affirmation. Il n’existe pas de champ générique supplémentaire provenance à ajouter au Statement.

Pour utiliser SLSA Provenance, nous conserverons la structure extérieure et le sujet, avec cette répartition :

in-toto Statement
  subject       → notre artefact
  predicateType → https://slsa.dev/provenance/v1
  predicate     → données SLSA Provenance

Changer uniquement l’URI ne suffit pas : les données doivent respecter le modèle qu’elle désigne. Nous construirons ce Predicate au chapitre 04, sans reprendre notre objet pédagogique comme s’il était déjà du SLSA.

14 — Continuum Attest

Continuum Attest conserve un receipt natif, différent d’un Statement in-toto. Son code d’interopérabilité convertit ce receipt avec attest export --format in-toto en Statement v1 portant un Predicate SLSA Provenance v1, dans une enveloppe DSSE. Les digests exportés utilisent BLAKE3, et non le SHA-256 pédagogique de ce chapitre.

attest import --format in-toto contrôle le schéma pris en charge, la cohérence des champs et la signature selon le magasin de confiance local. Il ne recalcule pas le digest d’un artefact livré. Ces commandes figurent dans la documentation CLI ; elles ne constituent pas un vérificateur universel de tout Predicate ni de toute politique.

15 — À retenir

Le Statement structure l’affirmation. subject désigne son objet ; predicateType identifie sa nature ; predicate en porte les données. DSSE peut envelopper et signer ce document.

Nous connaissons maintenant la structure. Il reste à remplacer notre Predicate pédagogique par un vrai modèle de provenance : 04 — Comprendre SLSA Provenance, à venir.