Spezifikation

Compliance-Evidence-Attestation, Version 1.

https://attestkeep.com/attestation/compliance-evidence/v1 ist der predicateType jeder Attestation, die Attestkeep über einen Compliance-Bericht signiert. Hat Sie ein Dokument zu dieser URL geführt, finden Sie hier seine Definition: die Aussage, jedes Feld, den Envelope, die signierte Kodierung und ein echtes Beispiel zum Prüfen.

Die Aussage

In einem Satz: Dieser Compliance-Bericht wurde von dieser Installation erzeugt und seitdem nicht verändert. Das ist die ganze Aussage, und alles Weitere folgt daraus.

Die Aussage wird in dem Format transportiert, das die Supply-Chain-Welt für solche Zwecke bereits nutzt – ein in-toto-Statement in einem DSSE-Envelope, signiert mit Ed25519 –, statt in einem selbst entworfenen Signaturschema. Das ist Absicht: Ein Format, das nur wir prüfen könnten, machte uns zum Engpass, und der Wert des Dokuments liegt gerade darin, dass ein Auditor es ohne uns prüfen kann. Der Signaturschlüssel wird in der Installation erzeugt und verlässt sie nie; vertrauen muss man nur seiner öffentlichen Hälfte.

Das Statement und sein Subject

Die Payload des Envelopes ist ein in-toto-Statement in Version 1. Seine äußeren Felder:

FeldTypBedeutung
_typestringImmer https://in-toto.io/Statement/v1.
subjectarrayGenau ein Resource Descriptor, der den Bericht benennt, um den es geht: name ist die Berichts-ID, digest.sha256 der SHA-256 genau der erzeugten Bytes, als 64 kleingeschriebene Hex-Zeichen ohne Präfix sha256:.
predicateTypestringImmer https://attestkeep.com/attestation/compliance-evidence/v1, also die Adresse dieser Seite.
predicateobjectDie Aussage selbst; jedes Feld ist im nächsten Abschnitt definiert.

Das Statement handelt von einem Dokument, also ist das Dokument das Subject. Der Bericht selbst steckt bewusst nicht in der Attestation: Er ist groß, und ein Leser hat ihn vielleicht als PDF- oder CSV-Fassung vorliegen. Belegbar sein muss, dass seine Kopie die signierte ist, und ein Digest beantwortet das für jede Kopie der Bytes – eine eingebettete Kopie beantwortete es nur für sich selbst.

Framework und Prüfzeitraum stehen neben dem Subject in der Signatur. Derselbe Zeitraum, gegen zwei Frameworks bewertet, ergibt zwei verschiedene Dokumente, und niemand darf das eine als das andere ausgeben können. Nichts im Statement – Subject, Digest, Zeitraum, Framework, Zeitstempel – lässt sich nach dem Signieren ändern, ohne dass die Prüfung fehlschlägt.

Predicate-Felder

Optionale Felder fehlen im JSON, wenn sie leer sind; als Platzhalter werden sie nie geschrieben. Ein Zeitstempel, den die Installation nicht kennt, steht als leerer String im JSON, nie als erfundenes Datum.

FeldTypBedeutung
report_idstringDie Identität des Dokuments, dieselbe ID, die auf dem Bericht steht; so lässt sich die vorliegende Attestation auf einen Blick dem zugehörigen Bericht zuordnen. Sie wiederholt den name des Subjects.
frameworkstringDas Compliance-Mapping, nach dem der Bericht zusammengestellt wurde, zum Beispiel soc2. Es steht in der Signatur, damit sich der Bericht eines Frameworks nicht als der eines anderen ausgeben lässt.
periodobjectDas Prüffenster, das der Bericht abdeckt.
period.start, period.endstringRFC-3339-Zeitstempel, auf UTC normalisiert. Als Strings übertragen, weil die exakten Bytes signiert werden.
installationobjectIdentifiziert den Signierenden, ohne den Cluster zu beschreiben.
installation.fingerprintstringDie opake Cluster-Kennung, an die die Lizenz gebunden ist – ein Hash ohne Topologie und ohne Namen, nichts, wogegen ein Kunde in einem Dokument, das sein Haus verlässt, Einwände hätte.
installation.license_idstring, optionalDamit kann der Aussteller bestätigen, dass die Installation zu diesem Zeitpunkt lizenziert war – das Bindeglied zwischen „diese Datei ist unverändert“ und „sie stammt aus einer echten Installation“.
installation.binding_idstring, optionalDie Lizenzbindung, unter der diese Installation aktiviert wurde; dient derselben Bestätigung.
generated_atstringWann die Installation den Bericht erzeugt hat, nach ihrer eigenen Uhr, in RFC 3339 UTC. Eine Behauptung des Signierenden, kein Beweis: Nichts bindet das Dokument an eine vertrauenswürdige Uhr, und wer das braucht, muss es anderweitig beschaffen.
generated_bystring, optionalDas Konto, das den Bericht angefordert hat, als user:<account id>. Seit 1.2.0 nie ein Name oder eine E-Mail-Adresse; der Betreiber löst die ID bei Bedarf über das Audit-Log seiner Konsole auf.
generatorobjectDie Software, die den Bericht erzeugt hat.
generator.name, generator.versionstringDas Release, dessen Compliance-Mappings beim Erzeugen des Berichts galten. Mappings ändern sich, wenn Frameworks genauer gelesen werden; ein Nachweis, der seine Lesart nicht nennt, lässt sich später nicht erneut prüfen.

Der DSSE-Envelope

Das Statement steckt in einem DSSE-Envelope dieser Form:

{
  "payload":     "<standard base64 of the statement JSON>",
  "payloadType": "application/vnd.in-toto+json",
  "signatures": [
    { "keyid": "ak-…", "sig": "<base64 of an Ed25519 signature>" }
  ]
}
  • payload ist Standard-base64 mit Padding über genau die serialisierten Statement-Bytes. Das Statement wird einmal serialisiert: Die signierten Bytes sind die übertragenen Bytes.
  • payloadType ist Teil dessen, was signiert wird (siehe die Kodierung unten); eine Signatur über ein in-toto-Statement kann also nie als Signatur über dieselben, anders gelesenen Bytes ausgegeben werden.
  • sig ist eine Ed25519-Signatur (RFC 8032) über PAE(payloadType, payload), also über das Pre-Authentication-Encoding, nicht über den base64-String.
  • keyid wird aus dem Public Key abgeleitet: "ak-", gefolgt von den ersten 16 Hex-Zeichen des SHA-256 des 32-Byte-Schlüssels. Weil sie abgeleitet ist, kann sie dem benannten Schlüssel nicht widersprechen; sie ist aber nur ein Hinweis – ein Prüfprogramm muss gegen einen Schlüssel prüfen, dem es bereits vertraut, nie gegen einen, den ihm der Envelope nennt.
  • Ein Envelope kann mehrere Signaturen tragen. Ein Prüfprogramm prüft jeden Eintrag gegen seinen vertrauenswürdigen Schlüssel und akzeptiert den Envelope, sobald ein Eintrag gültig ist.

Die PAE-Kodierung

Signiert wird das Pre-Authentication-Encoding der Payload zusammen mit ihrem Typ:

PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP body

Dabei ist SP ein einzelnes Leerzeichen und LEN die Bytelänge als dezimales ASCII. Längen zählen Bytes, nie Zeichen. So ist zum Beispiel PAE("http://example.com/HelloWorld", "hello world") die Bytefolge DSSEv1 29 http://example.com/HelloWorld 11 hello world.

Die gesamte Sicherheit von DSSE beruht auf diesem einen Gedanken. Würde nur die Payload signiert, ließe sich eine Signatur über ein in-toto-Statement als Signatur über etwas anderes ausgeben, das zufällig dieselben Bytes unter einem anderen payloadType hat. PAE bindet beides zusammen, mit Längenpräfix, sodass keines wie ein Teil des anderen aussehen kann.

Die Zertifikatskette

Die Signatur des Envelopes belegt, dass der Bericht unverändert ist, und nennt einen Schlüssel. Wem dieser Schlüssel gehört, kann sie allein nicht belegen: Ein Prüfprogramm, das den Public Key von demjenigen übernimmt, der das Dokument bringt, belegt nur innere Stimmigkeit. Diese Lücke schließt das Lizenzzertifikat. Attestkeep signiert es für jede lizenzierte Installation, und es trägt den Signaturschlüssel der Installation für Nachweise innerhalb der Signatur – wer einem Attestkeep-Schlüssel vertraut, kann also den Bericht jeder Installation prüfen:

Attestkeep-Root-Schlüssel
  └─ signiert → Lizenzzertifikat (kompaktes JWT)
                   └─ cnf.jwk nennt → Nachweisschlüssel der Installation
                                         └─ signiert → DSSE-Envelope
                                                          └─ Subject-Digest bindet → Bericht

Der Root-Schlüssel. Dieser Wert, über TLS von dieser Seite bezogen, ist der einzige Vertrauensanker der Kette:

kid:     ak1
ed25519: e287aabe4712197cfa5b6fdf9ea8f2d7f0333ce16ca69aac7e4a3f1e84c04179

Das Zertifikat ist ein Compact JWT (RFC 7515): base64url(header) "." base64url(payload) "." base64url(signature), mit base64url ohne Padding (RFC 4648 Abschnitt 5, kein =). Der Header ist {"alg":"EdDSA","typ":"JWT","kid":"ak1"}; ein Prüfprogramm muss jedes alg außer EdDSA ablehnen, was die klassischen JWT-Downgrade-Angriffe (alg=none, Algorithmusverwechslung) schon konstruktionsbedingt ausschließt. Die Signatur ist Ed25519 über die ASCII-Bytes von header_b64 "." payload_b64.

Die Payload-Felder, die ein Kettenprüfer liest (unbekannte Felder sind zu ignorieren, der Aussteller kann Claims ergänzen):

FeldTypBedeutung
substringDie Lizenz-ID, für die das Zertifikat ausgestellt wurde.
fprstringDer Cluster-Fingerabdruck, an den die Lizenz gebunden ist; installation.fingerprint im Predicate muss genau diesen Wert haben.
planstringDer Plan-Code, für einen Kettenprüfer nur informativ.
iat, expnumberAusstellung und Ablauf, Unix-Sekunden. exp von 0 bedeutet, dass das Zertifikat nicht abläuft.
jtistringDie Seriennummer des Zertifikats.
cnf.jwkstringDer Confirmation Claim aus RFC 7800: der Ed25519-Public-Key der Installation für Nachweise, als genau 64 Hex-Zeichen. Kein verschachteltes JWK-Objekt, sondern die rohen 32 Bytes, hex-kodiert. Ein Zertifikat ohne dieses Feld bürgt für keinen Schlüssel und kann keine Kette verankern.

So prüfen Sie einen Bericht über die Kette:

  1. Teilen Sie das Zertifikat an . in genau drei Teile; dekodieren Sie den Header und lehnen Sie ihn ab, wenn alg nicht EdDSA ist.
  2. Prüfen Sie die Ed25519-Signatur über die ASCII-Bytes von header_b64 "." payload_b64 mit dem Root-Schlüssel oben.
  3. Dekodieren Sie die Payload und lesen Sie cnf.jwk, den Nachweisschlüssel der Installation.
  4. Prüfen Sie den DSSE-Envelope mit diesem Schlüssel, genau wie in den Abschnitten oben beschrieben.
  5. Abgleich: predicate.installation.fingerprint im Statement muss dem fpr des Zertifikats entsprechen. Ohne diesen Schritt ließe sich ein echtes Zertifikat neben einem echten Envelope einer anderen Installation vorlegen. Ist installation.license_id vorhanden und weicht von sub ab, warnen Sie den Leser.

Ablauf ist eine Information, kein Ablehnungsgrund. Zertifikate rotieren alle dreißig Tage, und für einen Bericht aus dem letzten Quartal bürgt das damals gültige Zertifikat; würden abgelaufene Zertifikate abgelehnt, wäre jeder alte Bericht unprüfbar. Ein Prüfprogramm sollte das Datum eines abgelaufenen Zertifikats nennen und das Urteil dem Leser überlassen.

Vollständiges Beispiel

Dieser Envelope wurde mit dem Referenz-Signierer erzeugt. Er ist mit einem absichtlich öffentlichen Testschlüssel signiert – der Ed25519-Seed besteht aus den Bytes 0x01 bis 0x20 – und zeigt daher das Format, nicht die Herkunft eines echten Berichts. Echte Attestations werden mit einem Schlüssel signiert, der in jeder Installation erzeugt wird und sie nie verlässt.

{
  "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicnB0LTlmMmM3YTQxZDBiODNlNTYiLCJkaWdlc3QiOnsic2hhMjU2IjoiNzQ3YjgzZWNkMTY2OTEyNjk2NmVlNjAyZTM3MjI0NTE2Y2M5YmMyZGQ5NWFhZWJlNDIwZWQxY2IzNGVkZWMwYyJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2F0dGVzdGtlZXAuY29tL2F0dGVzdGF0aW9uL2NvbXBsaWFuY2UtZXZpZGVuY2UvdjEiLCJwcmVkaWNhdGUiOnsicmVwb3J0X2lkIjoicnB0LTlmMmM3YTQxZDBiODNlNTYiLCJmcmFtZXdvcmsiOiJzb2MyIiwicGVyaW9kIjp7InN0YXJ0IjoiMjAyNi0wNy0wMVQwMDowMDowMFoiLCJlbmQiOiIyMDI2LTA3LTMxVDIzOjU5OjU5WiJ9LCJpbnN0YWxsYXRpb24iOnsiZmluZ2VycHJpbnQiOiIzZjdiMWE5ZTVjMmQ4ZjA0YTZiM2UxYzdkOTA1MmU4YTFiNGY2YzNkN2U5YTBiMmM0ZDZlOGYwYTFiM2M1ZDdlIiwibGljZW5zZV9pZCI6ImxpYy03YzMxZjBkMiIsImJpbmRpbmdfaWQiOiJiaW5kLWE5NGUyYzE3In0sImdlbmVyYXRlZF9hdCI6IjIwMjYtMDgtMDFUMDY6MDA6MDBaIiwiZ2VuZXJhdGVkX2J5IjoidXNlcjp1c3ItN2QyYzFhOWU0YjNmNWM2ZDhlMGYxYTJiIiwiZ2VuZXJhdG9yIjp7Im5hbWUiOiJhdHRlc3RrZWVwIiwidmVyc2lvbiI6IjAuMS4wIn19fQ==",
  "payloadType": "application/vnd.in-toto+json",
  "signatures": [
    {
      "keyid": "ak-65b60673d6ed884b",
      "sig": "DUTZfcrSxs8IuyoPBcBOVa10sgg5Yy2/QQkCF8t0RFa2U+sjp5NGfVwHprbZ30dNtCogfZeexpLR8EYKsEWKCQ=="
    }
  ]
}

Die Payload dekodiert zu diesem 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"
    }
  }
}

Der Public Key, der es prüft, in der Form, die das Prüfprogramm erwartet:

79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664

Prüfen Sie es mit dem Skript aus der Dokumentation, das nur die Standardbibliothek nutzt, oder mit jeder DSSE-Implementierung: Die Prüfung besteht, und ein einziges geändertes Zeichen in der Payload lässt sie scheitern. Das Prüfprogramm im Browser erwartet das gelieferte Paket statt eines losen Envelopes mit Schlüssel – es ist für einen Auditor mit einem echten Archiv gebaut, nicht zum Lesen einer Spezifikation.

Eine Attestation prüfen

Drei Wege, in zunehmender Unabhängigkeit:

  • die Prüfseite auf dieser Website, die vollständig in Ihrem Browser läuft und nirgendwohin etwas sendet;
  • jedes Werkzeug, das DSSE und in-toto versteht – das Format enthält nichts Proprietäres;
  • Ein Dutzend eigene Zeilen in einer beliebigen Sprache: Lizenzzertifikat mit dem Root-Schlüssel prüfen, den Schlüssel der Installation aus cnf.jwk nehmen, die Payload base64-dekodieren, das PAE bilden, die Ed25519-Signatur mit diesem Schlüssel prüfen und erst dann das Statement lesen.

Welchen Weg Sie auch wählen, der Vertrauensanker ist derselbe: der Root-Schlüssel von Attestkeep, der über das Lizenzzertifikat für den Schlüssel der Installation bürgt, wie oben beschrieben – nie ein Schlüssel aus dem Dokument selbst, dessen keyid eine Bezeichnung und kein Berechtigungsnachweis ist, und nie ein Schlüssel, den man dem Überbringer des Berichts einfach glaubt. Ein von Hand übergebener Schlüssel prüft einen Envelope zwar ebenfalls, belegt aber nur innere Stimmigkeit, nicht die Herkunft. Auch die Grenzen sind dieselben: Eine gültige Kette belegt, dass das Dokument unverändert ist und welche Installation es signiert hat. Sie ist keine rechtsgültige elektronische Signatur und belegt nicht, wann signiert wurde – generated_at und der Zeitraum sind eigene Angaben der Installation. Beide Grenzen sind auf der Prüfseite ausgeführt.

Stand: 29. August 2026