Pular para o conteúdo
ecdsa.com

Sigstore vs GPG: assinatura de releases de software

Uma assinatura GPG diz que o portador de uma determinada chave assinou. Uma assinatura do Sigstore diz que uma determinada identidade, muitas vezes um fluxo de trabalho de CI, assinou em um momento registrado em log, com uma chave que existiu por poucos minutos.
Itens comparadosSigstoreOpenPGP

Sigstore vs GPG é a escolha entre duas formas de assinar artefatos de release, contêineres e pacotes. Com o OpenPGP (GnuPG e ferramentas compatíveis), o mantenedor guarda uma chave privada de longa duração, publica a impressão digital dela e distribui uma assinatura desanexada ao lado de cada arquivo. Com o Sigstore, o signatário se autentica via OpenID Connect, a autoridade certificadora Fulcio emite um certificado que vincula uma chave nova a essa identidade por dez minutos, a assinatura é registrada no log de transparência Rekor e a chave é descartada. Os verificadores conferem uma identidade, não uma chave.

Visão geral

Sigstore (keyless)OpenPGP (GPG)
Em que o verificador confiaUma identidade OIDC (e-mail ou URI do fluxo de trabalho) e o emissor, por meio da raiz do FulcioUma chave pública específica, obtida e conferida fora de banda
Vida útil da chave de assinaturaEfêmera; certificado válido por 10 minutosAnos; a expiração é definida e prorrogada pelo dono
Gestão de chavesNenhuma para os signatáriosGerar, fazer backup, proteger, rotacionar, revogar
Prova do momento da assinaturaEntrada no log do Rekor (e carimbo do tempo RFC 3161 opcional)Hora de criação da assinatura declarada pelo signatário
TransparênciaToda assinatura é registrada publicamenteNenhuma
RevogaçãoDesnecessária para chaves efêmeras; identidades comprometidas são tratadas por políticaCertificado de revogação que o dono precisa publicar
Assinatura offlineNão (exige o Fulcio e o Rekor, públicos ou privados)Sim
Formato do artefatoBundle do Sigstore (.sigstore.json): certificado, assinatura ou envelope DSSE, prova do logArquivo .asc ou .sig
Ferramentas típicascosign, sigstore-python, sigstore-js, gitsign, attest-build-provenance do GitHubgpg, Sequoia (sq, sqv), ferramentas do rpm e do apt
Onde é o padrãoProveniência do npm (disponibilidade geral em setembro de 2023), atestados do PyPI (novembro de 2024), atestados de artefatos do GitHub (disponibilidade geral em junho de 2024), CPython desde a 3.11 (única opção a partir da 3.14), Kubernetes e muitas imagens de contêinerMaven Central (obrigatório), repositórios Debian e RPM, tags do kernel Linux, muitos tarballs de código-fonte
PrivacidadeA identidade do signatário (e-mail ou repositório) fica pública no logChave e ID de usuário públicos só se o signatário os publicar

O que uma assinatura keyless contém

O npm armazena bundles do Sigstore dos pacotes publicados com proveniência. O script abaixo busca na API pública do registro os atestados de @noble/curves 2.3.0 e decodifica o certificado de assinatura (20 de outubro de 2026, curl, jq 1.8.1, OpenSSL 3.6.5).

Inspecionar o bundle do Sigstore por trás de um atestado de proveniência do 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
Saída
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

O pacote traz dois atestados: o atestado de publicação do próprio npm e uma declaração de proveniência SLSA assinada no CI. O certificado é válido por exatamente dez minutos e foi registrado no Rekor no mesmo segundo em que foi emitido. O titular dele não é uma pessoa nem uma chave, mas o fluxo de trabalho que foi executado — aqui, um fluxo de trabalho de release compartilhado, em outro repositório, fixado em um commit; outras extensões do Fulcio registram o repositório de origem, o commit, a tag e a execução. O verificador de bundles do Sigstore e o verificador de proveniência do npm decodificam os mesmos dados para qualquer pacote.

Como a verificação difere

Verificar uma assinatura GPG é um comando só, gpg --verify file.asc file, mas o significado dele depende de tudo o que veio antes: como o verificador obteve a chave, se a impressão digital foi conferida com uma fonte confiável e se a chave foi revogada desde então. A maioria dos resultados “Good signature” em pipelines de CI confia em qualquer chave que tenha sido importada algumas linhas antes.

Verificar uma assinatura do Sigstore exige nomear a identidade esperada, o que obriga a resolver a questão da política no próprio comando. O script abaixo verifica o arquivo de checksums da própria release v3.1.3 do cosign com a imagem de contêiner oficial do cosign v3.1.3, três vezes: com a identidade que o projeto Sigstore documenta para as releases dele (uma conta de serviço do Google, emissor https://accounts.google.com), com uma identidade de fluxo de trabalho do GitHub Actions que não assinou o arquivo e depois de acrescentar um byte ao arquivo.

Verificação keyless de um arquivo público de release com o 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
Saída (20 de outubro de 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

No primeiro resultado, o cosign verificou a cadeia de certificados até a raiz do Sigstore (distribuída via TUF), a assinatura sobre o arquivo, a prova de inclusão do Rekor e se o horário do log cai dentro da janela de dez minutos do certificado. A segunda execução é a razão de ser da fixação de identidade: a assinatura é criptograficamente válida, mas não foi feita pela identidade que o verificador pediu, por isso é rejeitada; a terceira falha na própria assinatura. Para artefatos gerados no GitHub Actions, a identidade é o arquivo do fluxo de trabalho com a ref dele, como o fluxo de trabalho release.yml@5320e209… do certificado de proveniência do npm acima, e o emissor é https://token.actions.githubusercontent.com.

Os ecossistemas de pacotes embutem essas verificações nas próprias ferramentas. O npm verifica tanto as assinaturas do registro quanto os atestados do Sigstore de tudo o que está instalado:

Verificar as assinaturas do registro e a proveniência dos pacotes npm instalados
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
Saída
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)

O gh attestation verify faz o mesmo com os atestados de artefatos do GitHub, e o PyPI confere os atestados com o publicador confiável (trusted publisher) do projeto no momento em que eles são enviados.

Contrapartidas a considerar

  • A confiança passa para os provedores de identidade e para a infraestrutura do Sigstore: um atacante que controle a conta do GitHub do mantenedor ou o fluxo de trabalho de release consegue obter assinaturas válidas. A transparência torna isso detectável (os mantenedores podem monitorar o log em busca da própria identidade), não impossível.
  • Metadados públicos: identidades de e-mail pessoais aparecem no log público; identidades de fluxo de trabalho revelam nomes de repositórios. Implantações privadas do Sigstore evitam as duas coisas.
  • Disponibilidade: assinar exige o Fulcio e o Rekor online. A verificação de um bundle funciona offline depois que a raiz de confiança está em cache.
  • A operação evolui: o Rekor v2, um log baseado em tiles, está em disponibilidade geral desde outubro de 2025, mas a instância pública mantém o Rekor v1 como padrão enquanto os mantenedores trabalham na preparação para a criptografia pós-quântica.
  • Os custos do GPG são humanos: chaves se perdem, expiram sem que ninguém perceba ou nunca são verificadas pelos usuários downstream — o principal motivo pelo qual Python, npm e PyPI deixaram de usá-lo.

Quando escolher cada um

  • Pacotes gerados no GitHub Actions ou no GitLab CI para npm, PyPI, crates ou contêineres: Sigstore, via publicação confiável (trusted publishing) e proveniência.
  • Imagens de contêiner: assinaturas e atestados do cosign, verificados por políticas de admissão.
  • Maven Central: o OpenPGP é obrigatório; acrescente também um bundle do Sigstore, que o Central valida desde janeiro de 2025.
  • Pacotes e repositórios de distribuições Linux: OpenPGP, como as ferramentas da distribuição esperam.
  • Assinatura em ambiente isolado (air-gapped) ou offline: OpenPGP (ou uma implantação privada do Sigstore com seu próprio provedor de identidade).
  • Commits e tags do Git: chaves SSH ou OpenPGP, ou o gitsign para commits keyless (veja SSH vs GPG para assinar commits).

Notas de migração

  1. Assine no CI, não em notebooks: mova as releases para um fluxo de trabalho com token OIDC (id-token: write no GitHub Actions) e assine lá.
  2. Publique os dois durante um período de transição: distribua os arquivos .asc e .sigstore.json e documente a identidade e o emissor esperados ao lado da impressão digital da chave antiga.
  3. Use os mecanismos nativos do registro onde eles existirem: o --provenance do npm e a publicação confiável, os publicadores confiáveis do PyPI, os atestados de artefatos do GitHub.
  4. Fixe as identidades com precisão nas políticas de verificação: repositório, arquivo do fluxo de trabalho e tag ou branch, não só a organização.
  5. Monitore o log em busca das suas identidades e guarde o certificado de revogação da sua chave OpenPGP até que a última release assinada com ela deixe de ter suporte.

Referências

Todas as comparações