Aller au contenu
ecdsa.com

Sigstore vs GPG : signer des versions logicielles

Une signature GPG atteste que le détenteur d’une clé donnée a signé. Une signature Sigstore atteste qu’une identité donnée, souvent un workflow CI, a signé à un instant consigné dans un journal, avec une clé qui n’a existé que quelques minutes.
Éléments comparésSigstoreOpenPGP

Sigstore vs GPG, c’est le choix entre deux façons de signer les artefacts de version, les conteneurs et les paquets. Avec OpenPGP (GnuPG et les outils compatibles), un mainteneur conserve une clé privée de longue durée, publie son empreinte et fournit une signature détachée à côté de chaque fichier. Avec Sigstore, le signataire s’authentifie avec OpenID Connect, l’autorité de certification Fulcio délivre un certificat qui lie une clé neuve à cette identité pendant dix minutes, la signature est enregistrée dans le journal de transparence Rekor, puis la clé est jetée. Les vérificateurs contrôlent une identité, pas une clé.

Vue d’ensemble

Sigstore (sans clé)OpenPGP (GPG)
Ce à quoi le vérificateur fait confianceUne identité OIDC (e-mail ou URI du workflow) et un émetteur, via la racine de FulcioUne clé publique précise, obtenue et vérifiée hors bande
Durée de vie de la clé de signatureÉphémère ; certificat valide 10 minutesDes années ; expiration fixée et prolongée par le propriétaire
Gestion des clésAucune pour les signatairesGénération, sauvegarde, protection, rotation, révocation
Preuve de l’heure de signatureEntrée du journal Rekor (et horodatage RFC 3161 facultatif)Heure de création de la signature déclarée par le signataire
TransparenceChaque signature consignée dans un journal publicAucune
RévocationInutile pour des clés éphémères ; les identités compromises sont traitées par les politiques de vérificationCertificat de révocation que le propriétaire doit publier
Signature hors ligneNon (nécessite Fulcio et Rekor, publics ou privés)Oui
Format de l’artefactBundle Sigstore (.sigstore.json) : certificat, signature ou enveloppe DSSE, preuve du journalFichier .asc ou .sig
Outils courantscosign, sigstore-python, sigstore-js, gitsign, attest-build-provenance de GitHubgpg, Sequoia (sq, sqv), outils rpm et apt
Où c’est la normeProvenance npm (GA en septembre 2023), attestations PyPI (novembre 2024), attestations d’artefacts GitHub (GA en juin 2024), CPython depuis la 3.11 (seule option à partir de la 3.14), Kubernetes et de nombreuses images de conteneursMaven Central (obligatoire), dépôts Debian et RPM, tags du noyau Linux, nombreuses archives tar de sources
ConfidentialitéL’identité du signataire (e-mail ou dépôt) est publique dans le journalClé et ID utilisateur publics uniquement si le signataire les publie

Ce que contient une signature sans clé

npm stocke des bundles Sigstore pour les paquets publiés avec provenance. Le script ci-dessous récupère les attestations de @noble/curves 2.3.0 via l’API publique du registre et décode le certificat de signature (20 octobre 2026, curl, jq 1.8.1, OpenSSL 3.6.5).

Inspecter le bundle Sigstore derrière une attestation de provenance npm
#!/bin/sh
curl -s "https://registry.npmjs.org/-/npm/v1/attestations/@noble%2fcurves@2.3.0" -o att.json
jq -r '.attestations[].predicateType' att.json
bundle='.attestations[] | select(.predicateType == "https://slsa.dev/provenance/v1") | .bundle'
jq -r "$bundle | .mediaType" att.json
jq -r "$bundle | .verificationMaterial.tlogEntries[0] | \"Rekor log index \(.logIndex), integrated \(.integratedTime | tonumber | todate)\"" att.json
jq -r "$bundle | .verificationMaterial.certificate.rawBytes" att.json | openssl base64 -d -A > leaf.der
openssl x509 -inform DER -in leaf.der -noout -issuer -dates -ext subjectAltName
openssl x509 -inform DER -in leaf.der -noout -text | grep -A1 "57264.1.1:" | tail -1 | sed 's/^ */OIDC issuer: /'
rm -f att.json leaf.der
Sortie
https://github.com/npm/attestation/tree/main/specs/publish/v0.1
https://slsa.dev/provenance/v1
application/vnd.dev.sigstore.bundle.v0.3+json
Rekor log index 2357213216, integrated 2026-08-06T07:37:48Z
issuer=O=sigstore.dev, CN=sigstore-intermediate
notBefore=Aug  6 07:37:48 2026 GMT
notAfter=Aug  6 07:47:48 2026 GMT
X509v3 Subject Alternative Name: critical
    URI:https://github.com/paulmillr/jsbt/.github/workflows/release.yml@5320e209ca8cc9b1b0b569675e164bdd2a139d71
OIDC issuer: https://token.actions.githubusercontent.com

Le paquet porte deux attestations : l’attestation de publication propre à npm et une déclaration SLSA de provenance signée en CI. Le certificat est valide exactement dix minutes et a été consigné dans Rekor à la seconde même de sa délivrance. Son sujet n’est ni une personne ni une clé, mais le workflow qui s’est exécuté, ici un workflow de publication partagé, situé dans un autre dépôt et épinglé sur un commit ; d’autres extensions Fulcio enregistrent le dépôt source, le commit, le tag et l’exécution. Le vérificateur de bundles Sigstore et le vérificateur de provenance npm décodent les mêmes données pour n’importe quel paquet.

En quoi la vérification diffère

Vérifier une signature GPG tient en une commande, gpg --verify file.asc file, mais sa portée dépend de tout ce qui précède : la façon dont le vérificateur a obtenu la clé, le contrôle ou non de l’empreinte auprès d’une source digne de confiance, et la révocation éventuelle de la clé depuis. La plupart des résultats « Good signature » dans les pipelines CI font confiance à n’importe quelle clé importée quelques lignes plus tôt.

Vérifier une signature Sigstore impose de nommer l’identité attendue, ce qui fait entrer la question de la politique de vérification dans la commande elle-même. Le script ci-dessous vérifie trois fois, avec l’image de conteneur officielle de cosign v3.1.3, le fichier des sommes de contrôle de cette même version v3.1.3 de cosign : par rapport à l’identité que le projet Sigstore documente pour ses versions (un compte de service Google, émetteur https://accounts.google.com), par rapport à une identité de workflow GitHub Actions qui n’a pas signé le fichier, puis après l’ajout d’un octet à la fin du fichier.

Vérification sans clé d’un fichier de version public avec cosign v3.1.3
url=https://github.com/sigstore/cosign/releases/download/v3.1.3
curl -sSLO "$url/cosign_checksums.txt"
curl -sSLO "$url/cosign_checksums.txt.sigstore.json"
cosign() { docker run --rm -v "$PWD":/work:ro -w /work ghcr.io/sigstore/cosign/cosign:v3.1.3 "$@"; }

echo "== expected identity"
cosign verify-blob cosign_checksums.txt \
  --bundle cosign_checksums.txt.sigstore.json \
  --certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
  --certificate-oidc-issuer https://accounts.google.com

echo "== any other identity"
cosign verify-blob cosign_checksums.txt \
  --bundle cosign_checksums.txt.sigstore.json \
  --certificate-identity "https://github.com/sigstore/cosign/.github/workflows/release.yml@refs/tags/v3.1.3" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

echo "== modified file"
printf 'x' >> cosign_checksums.txt
cosign verify-blob cosign_checksums.txt \
  --bundle cosign_checksums.txt.sigstore.json \
  --certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
  --certificate-oidc-issuer https://accounts.google.com
Sortie (20 octobre 2026)
== expected identity
Verified OK
== any other identity
Error: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "https://github.com/sigstore/cosign/.github/workflows/release.yml@refs/tags/v3.1.3", got "keyless@projectsigstore.iam.gserviceaccount.com"
error during command execution: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "https://github.com/sigstore/cosign/.github/workflows/release.yml@refs/tags/v3.1.3", got "keyless@projectsigstore.iam.gserviceaccount.com"
== modified file
Error: failed to verify signature: could not verify message: invalid signature when validating ASN.1 encoded signature
error during command execution: failed to verify signature: could not verify message: invalid signature when validating ASN.1 encoded signature

Pour le premier résultat, cosign a contrôlé la chaîne de certificats jusqu’à la racine Sigstore (distribuée via TUF), la signature du fichier et la preuve d’inclusion Rekor, et vérifié que l’heure du journal tombe dans la fenêtre de dix minutes du certificat. Le deuxième essai montre tout l’intérêt de l’épinglage d’identité : la signature est cryptographiquement valide, mais elle n’a pas été produite par l’identité qu’exige le vérificateur, elle est donc rejetée ; le troisième échoue sur la signature elle-même. Pour les artefacts construits dans GitHub Actions, l’identité est le fichier de workflow avec sa référence (ref), comme le workflow release.yml@5320e209… du certificat de provenance npm ci-dessus, et l’émetteur est https://token.actions.githubusercontent.com.

Les écosystèmes de paquets intègrent ces vérifications à leurs propres outils. npm vérifie à la fois les signatures de son registre et les attestations Sigstore de tout ce qui est installé :

Vérifier les signatures du registre et la provenance des paquets npm installés
cd "$(mktemp -d)" && npm init -y > /dev/null
npm install --ignore-scripts --no-fund --no-audit @noble/curves@2.3.0 @noble/hashes@2.3.0
npm audit signatures
Sortie
added 2 packages in 589ms
audited 2 packages in 0s

2 packages have verified registry signatures

2 packages have verified attestations
(use --json --include-attestations to view attestation details)

gh attestation verify fait de même pour les attestations d’artefacts GitHub, et PyPI vérifie les attestations par rapport à l’éditeur de confiance (trusted publisher) du projet au moment où elles sont envoyées.

Compromis à connaître

  • La confiance se déplace vers les fournisseurs d’identité et l’infrastructure de Sigstore : un attaquant qui contrôle le compte GitHub du mainteneur ou le workflow de publication peut obtenir des signatures valides. La transparence rend cela détectable (les mainteneurs peuvent surveiller le journal pour repérer leur identité), pas impossible.
  • Métadonnées publiques : les identités e-mail personnelles apparaissent dans le journal public ; les identités de workflow révèlent les noms des dépôts. Les déploiements privés de Sigstore évitent l’un et l’autre.
  • Disponibilité : signer nécessite que Fulcio et Rekor soient en ligne. La vérification d’un bundle fonctionne hors ligne une fois la racine de confiance mise en cache.
  • L’exploitation évolue : Rekor v2, un journal à base de tuiles (tiles), est en disponibilité générale depuis octobre 2025, mais l’instance publique conserve Rekor v1 par défaut pendant que les mainteneurs préparent le passage au post-quantique.
  • Les coûts de GPG sont humains : des clés se perdent, expirent sans que personne s’en aperçoive ou ne sont jamais vérifiées par les utilisateurs en aval, et c’est la principale raison pour laquelle Python, npm et PyPI l’ont abandonné.

Quand choisir l’un ou l’autre

  • Paquets construits dans GitHub Actions ou GitLab CI pour npm, PyPI, crates ou des conteneurs : Sigstore, via la publication de confiance et la provenance.
  • Images de conteneurs : signatures et attestations cosign, vérifiées par des politiques d’admission.
  • Maven Central : OpenPGP est obligatoire ; ajoutez aussi un bundle Sigstore, que Central valide depuis janvier 2025.
  • Paquets et dépôts des distributions Linux : OpenPGP, comme l’attendent les outils des distributions.
  • Signature isolée du réseau (air-gapped) ou hors ligne : OpenPGP (ou un déploiement privé de Sigstore avec votre propre fournisseur d’identité).
  • Commits et tags Git : clés SSH ou OpenPGP, ou gitsign pour des commits sans clé (voir SSH vs GPG pour signer les commits).

Notes de migration

  1. Signez en CI, pas sur les ordinateurs portables : déplacez la publication des versions vers un workflow doté d’un jeton OIDC (id-token: write dans GitHub Actions) et signez-y.
  2. Publiez les deux pendant une période de transition : fournissez des fichiers .asc et .sigstore.json, et documentez l’identité et l’émetteur attendus à côté de l’empreinte de l’ancienne clé.
  3. Utilisez les mécanismes natifs des registres là où ils existent : --provenance et la publication de confiance (trusted publishing) de npm, les éditeurs de confiance (trusted publishers) de PyPI, les attestations d’artefacts GitHub.
  4. Épinglez précisément les identités dans les politiques de vérification : dépôt, fichier de workflow et tag ou branche, pas seulement l’organisation.
  5. Surveillez le journal pour repérer vos identités, et conservez le certificat de révocation de votre clé OpenPGP jusqu’à ce que la dernière version signée avec elle ne soit plus prise en charge.

Références

Tous les comparatifs