Kurz gesagt. Beim „Keyless“-Signieren mit Sigstore weist der Signierer eine Identität mit einem OIDC-Token nach (ein Workflow in GitHub Actions, ein Google- oder Microsoft-Konto). Fulcio stellt für einen ephemeren Schlüssel ein Zertifikat aus, das etwa zehn Minuten gültig ist und diese Identität enthält. Die Signatur wird im öffentlichen Transparenzlog Rekor erfasst, und alles, was für die Prüfung nötig ist, landet in einem Bundle (.sigstore.json). Die Prüfung muss klären, wer signiert hat, nicht nur, ob die Signatur gültig ist. In cosign heißt das --certificate-identity und --certificate-oidc-issuer; beide verlangt cosign im Keyless-Modus. npm-Provenance und Artefakt-Attestierungen von GitHub beruhen auf demselben Mechanismus: npm audit signatures und gh attestation verify prüfen sie für dich.
Warum Keyless Signing
Klassisches Signieren von Releases mit GPG oder einem langlebigen Codesignaturschlüssel hat zwei chronische Probleme. Wer ein Release prüft, muss wissen, welcher Schlüssel der richtige ist – die Verteilung der Schlüssel ist also das eigentliche Vertrauensproblem. Und der Schlüssel muss jahrelang irgendwo gespeichert werden, meist als CI-Secret, wo er abfließen kann. Sigstore ersetzt den Schlüssel durch eine Identität, die Menschen bereits verstehen, etwa „der Release-Workflow von Repository X“ oder „alice@example.com“. Und es ersetzt die Lebensdauer des Schlüssels durch einen Logeintrag, der belegt, wann die Signatur erstellt wurde.
Sigstore besteht aus öffentlichen Diensten, die die OpenSSF-Community betreibt (die „Public-Good-Instanz“), und aus Client-Tools:
- Fulcio ist eine Zertifizierungsstelle, die kurzlebige X.509-Zertifikate für OIDC-Identitäten ausstellt.
- Rekor ist das Transparenzlog für Signaturen und Attestierungen.
- TUF-Metadaten verteilen die Roots of Trust (die CA von Fulcio, den Schlüssel von Rekor, Zeitstempeldienste) an die Clients.
- cosign ist das CLI für Blobs und Container-Images. sigstore-python, sigstore-js, sigstore-go und sigstore-java sind Bibliotheken für dieselben Aufgaben.
Was beim Signieren passiert
- Der Client erzeugt im Speicher ein ephemeres Schlüsselpaar.
- Er holt sich ein OIDC-Token: in GitHub Actions vom Runner (
id-token: write), auf einem Laptop über eine Anmeldung im Browser. - Fulcio prüft das Token und stellt ein Zertifikat aus, das den öffentlichen Schlüssel an die Identität bindet: Die E-Mail-Adresse oder Workflow-URI kommt in den Subject Alternative Name, Aussteller und CI-Details kommen in Erweiterungen.
- Der Client signiert den Digest des Artefakts und reicht Signatur und Zertifikat bei Rekor ein; Rekor gibt einen Logeintrag mit Inclusion Proof und signiertem Zeitstempel zurück.
- Der ephemere private Schlüssel wird verworfen. Das Bundle speichert Zertifikat, Signatur und Log-Nachweis.
Das Zertifikat im Bundle, das cosign mit seinem eigenen Release v3.1.3 ausliefert, zeigt, wie kurzlebig es ist:
issuer=O=sigstore.dev, CN=sigstore-intermediate
notBefore=Aug 6 01:03:25 2026 GMT
notAfter=Aug 6 01:13:25 2026 GMT
X509v3 Extended Key Usage:
Code Signing
X509v3 Subject Alternative Name: critical
email:keyless@projectsigstore.iam.gserviceaccount.com
1.3.6.1.4.1.57264.1.1:
https://accounts.google.com
Zehn Minuten Gültigkeit. Die Signatur bleibt danach prüfbar, weil der Rekor-Eintrag belegt, dass sie innerhalb dieses Fensters erstellt wurde. Das Feld integratedTime dieses Rekor-Eintrags entspricht 2026-08-06 01:03:25 UTC.
Das Bundle
Ein Bundle ist ein JSON-Dokument, das alles enthält, was ein Verifizierer braucht, außer dem Artefakt und den Roots of Trust. Das hier ist das Release-Bundle von cosign, gekürzt:
{
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
"verificationMaterial": {
"certificate": { "rawBytes": "MIIDDzCCApWgAwIBAgIUavXJ..." },
"tlogEntries": [
{
"logIndex": "2352242192",
"kindVersion": { "kind": "hashedrekord", "version": "0.0.1" },
"integratedTime": "1785978205",
"inclusionPromise": { "signedEntryTimestamp": "MEYCIQCvddr55BoX..." },
"inclusionProof": { "logIndex": "2230337930", "treeSize": "2230337938", "rootHash": "62m7TRzwvcKyu+r6...", "hashes": ["..."], "checkpoint": { "envelope": "..." } },
"canonicalizedBody": "eyJhcGlWZXJzaW9u..."
}
],
"timestampVerificationData": { "rfc3161Timestamps": ["..."] }
},
"messageSignature": {
"messageDigest": { "algorithm": "SHA2_256", "digest": "xdMk4JGCaw16eOsW..." },
"signature": "MEUCIECQp5FyO00l..."
}
}
certificateist das Leaf-Zertifikat von Fulcio, mit der Identität.tlogEntriesenthält den Rekor-Eintrag: einen Inclusion Proof gegen einen signierten Checkpoint und einen signierten Zeitstempel des Eintrags. Damit muss ein Verifizierer Rekor überhaupt nicht kontaktieren.timestampVerificationDataenthält optionale Zeitstempel nach RFC 3161 von einem Zeitstempeldienst.- Der Inhalt ist entweder
messageSignaturefür eine einfache Blob-Signatur oderdsseEnvelopefür eine Attestierung, also ein signiertes in-toto-Statement.
Der Sigstore-Bundle-Verifizierer prüft alle diese Teile im Browser und zeigt die Identität im Zertifikat an – das Erste, worauf du schauen solltest, wenn eine Prüfung fehlschlägt.
Prüfen: Auf die Identität kommt es an
cosign prüft sein eigenes Release-Binary (ghcr.io/sigstore/cosign/cosign:v3.1.3, 20. Oktober 2026). Das Release wird auf Infrastruktur von Google gebaut, deshalb ist die Identität ein Dienstkonto von Google und der Aussteller Google, nicht GitHub:
$ cosign verify-blob cosign-linux-arm64 --bundle cosign-linux-arm64.sigstore.json \
--certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
--certificate-oidc-issuer https://accounts.google.com
Verified OK
Jetzt die Fehlerfälle. Jeder liefert eine Meldung, die dir genau sagt, welche Prüfung fehlgeschlagen ist:
--- right email, wrong issuer
error during command execution: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected issuer value "https://token.actions.githubusercontent.com", got "https://accounts.google.com"
--- a different identity
error during command execution: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "release@example.com", got "keyless@projectsigstore.iam.gserviceaccount.com"
--- no identity at all
error during command execution: --certificate-identity or --certificate-identity-regexp is required for verification in keyless mode
--- a modified binary
error during command execution: failed to verify signature: could not verify message: invalid signature when validating ASN.1 encoded signature
Der dritte Fall ist so beabsichtigt. Jeder kann ein Fulcio-Zertifikat für seine eigene Identität bekommen und damit beliebige Daten signieren. Eine Signatur hat erst dann Aussagekraft, wenn du prüfst, ob die Identität die erwartete ist. cosign verweigert die Prüfung von Keyless-Signaturen ohne Identität (Fehlerseite). Der letzte Fall zeigt, wie eine Manipulation aussieht (Fehlerseite).
Woher bekommst du die erwartete Identität? Aus der Dokumentation des Projekts – oder du liest sie einmal aus einem Bundle aus, dem du vertraust, und pinnst sie in deinem Prüfskript. --certificate-identity-regexp ist für Tags nützlich (zum Beispiel ...release.yml@refs/tags/v.*), aber verankere den Ausdruck an beiden Enden. Ein Muster wie .*sigstore.* passt auf Identitäten, die jeder anlegen kann.
Signieren in GitHub Actions
In CI ist die Identität der Workflow. Fulcio schreibt die Workflow-Datei und die Ref in den SAN und die Details in Erweiterungen. Das npm-Provenance-Bundle von sigstore@5.0.0 zeigt, was ein Zertifikat aus GitHub Actions enthält – mit einem kleinen Skript aus seinen X.509-Erweiterungen dekodiert:
1.3.6.1.4.1.57264.1.8 Issuer (V2) https://token.actions.githubusercontent.com
1.3.6.1.4.1.57264.1.9 Build Signer URI https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main
1.3.6.1.4.1.57264.1.12 Source Repository URI https://github.com/sigstore/sigstore-js
1.3.6.1.4.1.57264.1.13 Source Repository Digest 7d2900eca1c22b3f87c13987c8d4b7c9a29b733a
1.3.6.1.4.1.57264.1.14 Source Repository Ref refs/heads/main
1.3.6.1.4.1.57264.1.18 Build Config URI https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main
1.3.6.1.4.1.57264.1.20 Build Trigger push
1.3.6.1.4.1.57264.1.21 Run Invocation URI https://github.com/sigstore/sigstore-js/actions/runs/26781866618/attempts/1
Auch diese Claims kann cosign prüfen, zusätzlich zum SAN:
$ cosign verify-blob-attestation --bundle provenance.sigstore.json --type slsaprovenance1 \
--certificate-identity-regexp '^https://github\.com/sigstore/sigstore-js/\.github/workflows/release\.yml@' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-github-workflow-repository sigstore/sigstore-js \
--certificate-github-workflow-trigger push \
sigstore-5.0.0.tgz
Verified OK
Damit du eigene Releases in GitHub Actions signieren kannst, braucht der Job die Berechtigung, ein OIDC-Token anzufordern. Das README zu GitHubs actions/attest nennt diese minimale Konfiguration für die Build-Provenance einer Datei:
permissions:
id-token: write
attestations: write
artifact-metadata: write
steps:
# ... build steps that produce dist/app.tar.gz ...
- uses: actions/attest@v4
with:
subject-path: 'dist/app.tar.gz'
Öffentliche Repositorys nutzen die Public-Good-Instanz von Sigstore. Private Repositorys nutzen die eigene Instanz von GitHub, die GitHub Enterprise Cloud voraussetzt. actions/attest-build-provenance funktioniert weiterhin, ist seit v4 aber nur noch ein Wrapper um actions/attest. Um einen Blob direkt im Job mit cosign zu signieren, verwendest du cosign sign-blob --bundle app.sigstore.json app.tar.gz --yes. Die Hilfe von cosign zu --oidc-provider führt github-actions unter den Token-Quellen auf, die cosign durchprobiert, wenn keine gesetzt ist. Für diese Seite haben wir keine Keyless-Signatur erstellt, weil jede Keyless-Signatur auf der öffentlichen Instanz ein dauerhafter, öffentlicher Logeintrag ist.
Eine Signatur aus einem Workflow ist nur so vertrauenswürdig wie der Workflow selbst. Wer auf den Branch pushen oder den Workflow auslösen kann, kann mit dieser Identität eine gültige Signatur erzeugen. Schütze Release-Workflows mit Branch- und Tag-Schutz, verpflichtenden Reviews und Freigaben für Deployment-Umgebungen, und pinne die Prüfung auf eine Tag-Ref (@refs/tags/...), wenn du aus Tags veröffentlichst.
npm-Provenance
npm veröffentlicht für Pakete, die in einer unterstützten CI gebaut werden, zwei Arten von Sigstore-Nachweisen: eine von der Registry signierte Publish-Attestierung und eine vom Build signierte SLSA-Provenance-Attestierung. npm audit signatures prüft beide für alles Installierte:
$ npm audit signatures
audited 52 packages in 2s
52 packages have verified registry signatures
26 packages have verified attestations
(use --json --include-attestations to view attestation details)
Die Attestierungen einer Version sind öffentlich abrufbar, unter https://registry.npmjs.org/-/npm/v1/attestations/<name>@<version>. Für sigstore@5.0.0:
https://github.com/npm/attestation/tree/main/specs/publish/v0.1 | bundle application/vnd.dev.sigstore.bundle+json;version=0.2
subject: pkg:npm/sigstore@5.0.0 849a897e81bf7b8a8541a6ae...
https://slsa.dev/provenance/v1 | bundle application/vnd.dev.sigstore.bundle.v0.3+json
subject: pkg:npm/sigstore@5.0.0 849a897e81bf7b8a8541a6ae...
workflow: https://github.com/sigstore/sigstore-js .github/workflows/release.yml refs/heads/main
builder: https://github.com/actions/runner/github-hosted
commit: 7d2900eca1c22b3f87c13987c8d4b7c9a29b733a
Der Subject-Digest ist der SHA-512-Hash des Tarballs (849a897e...), daher lässt sich das Provenance-Bundle mit cosign gegen die heruntergeladene Datei .tgz prüfen, wie oben gezeigt. Mit Trusted Publishing von npm (npm CLI 11.5.1 oder neuer) erhalten Pakete, die aus GitHub Actions oder GitLab CI über OIDC veröffentlicht werden, automatisch Provenance – ohne das Flag --provenance und ohne langlebiges npm-Token. CircleCI wird für Trusted Publishing unterstützt, für Provenance aber noch nicht. Außerhalb einer unterstützten CI schlägt npm publish --provenance mit der Meldung Automatic provenance generation not supported for provider: null fehl. Der npm-Provenance-Checker zeigt Repository, Workflow und Commit für jede beliebige Paketversion.
Artefakt-Attestierungen von GitHub
GitHub speichert Attestierungen für Release-Assets und stellt sie über seine API bereit. Der Release-Tarball gh_2.102.0_linux_arm64.tar.gz des CLI gh (SHA-256 7862c86c...) hatte am 20. Oktober 2026 zwei Attestierungen: SLSA-Provenance aus dem Deployment-Workflow von cli/cli und eine Release-Attestierung, die GitHub selbst signiert hat. Ist das Provenance-Bundle lokal gespeichert, funktioniert gh attestation verify ohne Anmeldung und gibt die Richtlinie aus, die es durchgesetzt hat:
$ gh attestation verify gh_2.102.0_linux_arm64.tar.gz --repo cli/cli --bundle gh-1.sigstore.json
Loaded digest sha256:7862c86c72f43df3a2d93ddde6f473285b4e2af61b494849846827e513ef6484 for file:///d/gh_2.102.0_linux_arm64.tar.gz
Loaded 1 attestations from /d/gh-1.sigstore.json
The following policy criteria will be enforced:
- Predicate type must match:................ https://slsa.dev/provenance/v1
- Source Repository Owner URI must match:... https://github.com/cli
- Source Repository URI must match:......... https://github.com/cli/cli
- Subject Alternative Name must match regex: (?i)^https://github\.com/cli/cli/
- OIDC Issuer must match:................... https://token.actions.githubusercontent.com
✓ Verification succeeded!
The following 1 attestation matched the policy criteria
- Attestation #1
- Build repo:..... cli/cli
- Build workflow:. .github/workflows/deployment.yml@refs/heads/trunk
- Signer repo:.... cli/cli
- Signer workflow: .github/workflows/deployment.yml@refs/heads/trunk
Mit --repo example/cli endet dieselbe Prüfung mit ✗ Sigstore verification failed. Die Standardrichtlinie (Eigentümer, Repository, SAN-Präfix) ist ein guter Anfang. Für automatisierte Releases ergänze --signer-workflow, um die genaue Workflow-Datei zu pinnen, und --deny-self-hosted-runners, wenn deine Builds nur auf von GitHub gehosteten Runnern laufen. Dasselbe Bundle lässt sich auch mit cosign prüfen: cosign verify-blob-attestation --type slsaprovenance1 mit dem SAN https://github.com/cli/cli/.github/workflows/deployment.yml@refs/heads/trunk gab Verified OK aus.
Die zweite Attestierung ist anders aufgebaut. Als Predicate-Typ trägt sie https://in-toto.io/attestation/release/v0.2. Das Zertifikat nennt URI:https://dotcom.releases.github.com und wurde von O=GitHub, Inc., CN=Fulcio Intermediate l1 ausgestellt, dem eigenen Fulcio von GitHub, mit einem Zeitstempel nach RFC 3161 statt eines Rekor-Eintrags. GitHub erstellt sie für Repositorys, in denen unveränderliche Releases aktiviert sind. Diese Attestierung belegt, dass das Asset zum Release gehört, nicht, wie es gebaut wurde. gh release verify und gh release verify-asset prüfen sie. In unserem Test brauchte gh release verify-asset ein angemeldetes gh: Ohne Token brach gh 2.102.0 mit To get started with GitHub CLI, please run: gh auth login ab.
Wenn Keyless Signing nicht passt
Builds in Air-Gap-Umgebungen, private Artefakte, die nicht in einem öffentlichen Log auftauchen dürfen, oder Richtlinien, die einen Schlüssel in einem HSM verlangen: cosign signiert auch mit Schlüsseln, und der Upload ins Log lässt sich abschalten. Getestet mit cosign v3.1.3:
$ cosign generate-key-pair
Private key written to cosign.key
Public key written to cosign.pub
$ cosign sign-blob --key cosign.key --tlog-upload=false --use-signing-config=false --bundle app.sigstore.json app-1.0.0.tar.gz --yes
Using payload from: app-1.0.0.tar.gz
Signing artifact...
Wrote bundle to file app.sigstore.json
$ cosign verify-blob --key cosign.pub --bundle app.sigstore.json --insecure-ignore-tlog=true app-1.0.0.tar.gz
WARNING: Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the blob.
Verified OK
$ cosign verify-blob --key cosign.pub --bundle app.sigstore.json app-1.0.0.tar.gz
error during command execution: failed to verify log inclusion: not enough verified log entries from transparency log: 0 < 1
Das Bundle enthält dann nur den Hinweis publicKey, und du musst wieder einen langlebigen Schlüssel verteilen und schützen – genau das Problem, das Keyless Signing beseitigt. Für ein privates, aber weiterhin auditierbares Setup betreibe eine eigene Sigstore-Instanz (Fulcio, Rekor, TUF) oder nutze die eines Anbieters. GitHub macht genau das für private Repositorys.
Der Stand der Infrastruktur 2026
- Rekor v2, ein Tile-basiertes Log, wurde im Oktober 2025 allgemein verfügbar. Verifizierer brauchen cosign 2.6.0 oder 3.0.1 und neuer. Rekor v2 hat keinen Suchindex mehr.
- Im Juni 2026 kündigten die Maintainer an, dass die Public-Good-Instanz „auf absehbare Zeit weiterhin Rekor v1 als Standard-Log nutzen wird“. Rekor v2 bleibt für Ökosysteme, die sich dafür entscheiden, im Produktivbetrieb.
- Eine separate Post-Quanten-Instanz von Sigstore mit ML-DSA ist laut der Ankündigung vom Oktober 2026 „für Mitte 2027 angepeilt“. Die klassische Instanz soll bis zu einer Abschaltung weiterlaufen, die „vorläufig für 2029 oder 2030 geplant“ ist. Bundles bleiben in beiden Fällen das Format.
Bei der Prüfung ändert sich kaum etwas: Halte deine Clients aktuell, prüfe anhand von Bundles und pinne Identitäten, nicht Log-URLs.
Häufige Fehler
- Die Signatur prüfen, aber nicht die Identität. Eine gültige Sigstore-Signatur sagt nur, dass irgendjemand signiert hat.
- Zu weit gefasste Identitätsmuster. Nicht verankerte reguläre Ausdrücke oder eine Prüfung des Ausstellers ohne Prüfung des SAN.
- Pinning auf einen Branch, obwohl du aus Tags veröffentlichst.
@refs/heads/mainakzeptiert alles, was ausmaingebaut wurde, auch einen Build, den niemand veröffentlichen wollte. - Annehmen, dass die Signatur den Build belegt. Eine Blob-Signatur belegt, wer die Bytes signiert hat. Provenance (SLSA, in-toto) sagt, wie und woraus sie gebaut wurden. Prüfe das, was deine Richtlinie verlangt.
- Persönliche Releases mit einer persönlichen E-Mail-Adresse signieren, ohne zu bedenken, dass die E-Mail-Adresse im Zertifikat steht und damit in einem öffentlichen, dauerhaften Log.
- Abgetrennte Dateien
.sigund.pemälterer cosign-Versionen ausliefern. Veröffentliche das Bundle.sigstore.json. Es ist das Format, das alle aktuellen Verifizierer verstehen.
Quellen
- Sigstore-Dokumentation und das Bundle-Format.
- cosign v3.1.3 und seine Release-Bundles.
- Sigstore-Blog: Rekor v2 allgemein verfügbar (Oktober 2025), sigstore.dev und die Weiterentwicklung von Rekor (Juni 2026), Post-Quanten-Kryptografie für Sigstore (Oktober 2026).
- npm: Trusted Publishing und der Endpunkt für Registry-Attestierungen.
- GitHub:
actions/attest,gh attestation verify,gh release verifyund das in-toto-Release-Predicate.
Die Prüfungen liefen am 20. Oktober 2026 in ghcr.io/sigstore/cosign/cosign:v3.1.3, mit npm 11.19.1 und gh 2.102.0 (dem offiziellen Release-Tarball) in node:24.22.0-trixie.