Skip to content

Add experimental sigstore-go support#819

Open
wlynch wants to merge 12 commits into
sigstore:mainfrom
wlynch:sigstore-go-compat
Open

Add experimental sigstore-go support#819
wlynch wants to merge 12 commits into
sigstore:mainfrom
wlynch:sigstore-go-compat

Conversation

@wlynch
Copy link
Copy Markdown
Member

@wlynch wlynch commented Jun 2, 2026

Summary

Adds a new config option - gitsign.enableSigstoreGo if set to true, then new sigstore-go signer/verifier is used to do the signing and verification of commits.

At a high level, this works by adding a compatibility layer between Sigstore Bundles and gitsign's CMS signature layout (which we use to stay compatible with other Git tooling). When signing, we get the bundle then convert it to a CMS signature. When verifying we construct the bundle from the CMS signature, then use sigstore-go to verify.

CAVEATS: For now, signing requires use of offline signatures. If verification encounters an online signature, we fall back to the old non-sigstore-go behavior. I'll probably add online signing/verification support in another PR.

Part of #537

Release Note

  • Adds a new experimental config option - gitsign.enableSigstoreGo if set to true, then new sigstore-go signer/verifier is used to do the signing and verification of commits. This should not have a meaningful visible effect for most users.

Documentation

wlynch and others added 11 commits June 2, 2026 12:50
Introduces internal/sigstore/compat, the foundation for refactoring
gitsign onto the sigstore-go libraries without changing the on-disk CMS
signature format.

CMS -> bundle (verification direction):
- SignerInfoToBundle converts a single CMS signer into a sigstore v0.3
  bundle, returning a SignerBundle{Bundle, Artifact}. Artifact is the
  marshaled SignedAttrs that the bundle signs over (gitsign signs the
  SignedAttrs, not the commit body), to be supplied as the verification
  artifact.
- SignedDataToBundle converts every signer, returning one SignerBundle
  per SignerInfo: a bundle holds a single MessageSignature, and the CMS
  format permits multiple signers.

bundle -> CMS attributes (signing direction):
- BundleToAttributes converts each Rekor tlog entry in a bundle into CMS
  unsigned attributes (one protocol.Attributes set per entry), encoded
  under OIDRekorTransparencyLogEntry as the legacy signing path did. The
  caller owns the SignedData mutation.

Refactors internal/rekor/oid to share the HashedRekord canonical body
recompute: extracts ToLogEntryProto (raw canonical body, sigstore
convention) and adds ProtoToLogEntryAnon, while ToLogEntry keeps its
existing base64 behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
Adds an opt-in verification path (cfg.VerifyBundle, set via
gitsign.verifyBundle git config or GITSIGN_VERIFY_BUNDLE) that verifies a
gitsign CMS signature by converting it to sigstore bundles and verifying
them with sigstore-go. The default behavior is unchanged.

internal/gitsign:
- bundle.go: verifyBundle converts each signer with compat.SignerInfoToBundle
  and verifies it with sigstore-go. At least one signer must verify (a
  failing signer is skipped, not fatal); the first verified signer drives the
  VerificationSummary. Content binding (hash(object) == SignedAttrs
  message-digest) is enforced separately since sigstore-go signs over the
  SignedAttrs, and a Rekor entry is required (no current-time fallback). When
  a signer carries an RFC3161 timestamp it must verify against the configured
  TSA; embedded SCTs are verified by default unless IgnoreSCT.
- trustedroot.go: builds a sigstore-go root.TrustedMaterial from gitsign's
  existing trust sources (Fulcio roots, Rekor/CT log keys, cfg.TimestampCert).

Supporting changes:
- config: VerifyBundle option (git config + env).
- fulcioroots: CertsFromConfig returns []*x509.Certificate for the trust
  material (FulcioCertificateAuthority can't consume a CertPool).
- compat: ParseSignaturePEM (PEM-or-DER -> CMS SignedData), now shared by
  pkg/git, pkg/rekor, and the bundle path; SignerInfoToBundle takes a
  SignerInfo value.
- internal/e2e: bundle parity test (e2e build tag) comparing the legacy and
  bundle paths on the offline commit fixture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
Adds an opt-in signing path where sigstore-go produces the signature and
Rekor entry as a bundle, which is then converted to a CMS signature for
storage. The on-disk CMS format is unchanged. Gated, together with the
verification path, by cfg.EnableSigstoreGo (gitsign.enableSigstoreGo /
GITSIGN_ENABLE_SIGSTORE_GO), which requires offline Rekor mode.

Signing flow (internal/signature):
- signBundle builds the CMS signed attributes, has sigstore-go's sign.Bundle
  sign them (using gitsign's existing identity via Identity.Keypair, so the
  OIDC/Fulcio flow and credential cache are unchanged) and upload to Rekor,
  then converts the bundle to CMS via the compat layer.
- A validatingRekorClient rejects a Rekor response lacking an inclusion
  proof, turning a would-be panic in sigstore-go's conversion into an error.
- The signing key/cert binding is checked before signing; RFC3161 timestamps
  are applied to the assembled CMS via the fork.

compat:
- BuildSignedAttributes / BundleToSignedData (the inverse of SignerInfoToBundle):
  assemble a CMS SignedData from a bundle, byte-equivalent to the fork's signer.
- Keypair adapts a crypto.Signer to sign.Keypair, deriving algorithms from the
  key; CertificateProvider supplies the identity's existing Fulcio cert.

fork/ietf-cms:
- SignedAttributes + AddSignerInfoWithSignature assemble a SignerInfo with an
  externally-computed signature (the non-signing half of AddSignerInfo), so the
  CMS assembly lives in the CMS layer rather than being duplicated in compat.

config: EnableSigstoreGo replaces the separate VerifyBundle/SignBundle options.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
Adds docs/bundle-cms.md explaining how gitsign's CMS/PKCS7 signature format
maps to the Sigstore bundle format, the CMS<->bundle conversions used by the
experimental sigstore-go signing and verification paths, and the field
mapping (the signed artifact being the marshaled CMS SignedAttrs).

Also documents the gitsign.enableSigstoreGo / GITSIGN_ENABLE_SIGSTORE_GO
option in the README config tables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
verifySigner now returns the bundle it verified, and verifyBundle
populates a new VerificationSummary.Bundle field with it (nil on the
legacy path). Also makes verifySigner's policyOpts variadic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
When cfg.EnableSigstoreGo is set, commandSign now prints a message to the
user TTY so it is clear the experimental sigstore-go signing path is active.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
Compare the signing key and certificate public key via the key's own
Equal(crypto.PublicKey) bool method (implemented by all stdlib public key
types) instead of marshaling both to PKIX and comparing bytes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
The helper was only called once and added no abstraction over the inline
Equal check, so fold it into its single caller.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
This reverts commit 2763429.

Signed-off-by: Billy Lynch <billy@chainguard.dev>
…log entry

Older "online" signatures embed no Rekor transparency log entry - their entry
lives in Rekor keyed on the commit SHA and is found via online search, which
sigstore-go cannot do from the signature alone. The bundle verification path
now signals these via errNoEmbeddedRekorEntry, and Verify falls back to the
legacy path so they keep verifying under gitsign.enableSigstoreGo.

Adds a regression test using a real online-mode signature fixture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
@wlynch wlynch force-pushed the sigstore-go-compat branch from eeb2f52 to ac217bd Compare June 2, 2026 16:51
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an experimental opt-in path to perform gitsign commit/tag signing and verification using sigstore-go, while preserving gitsign’s existing CMS/PKCS7 on-disk signature format by converting between CMS and Sigstore Bundles.

Changes:

  • Introduces gitsign.enableSigstoreGo / GITSIGN_ENABLE_SIGSTORE_GO (requires rekorMode=offline) and wires it into signing + verification flows.
  • Adds a CMS↔Bundle compatibility layer (internal/sigstore/compat) plus supporting Rekor OID protobuf helpers.
  • Implements bundle-based verification and bundle-based offline signing (Rekor entry embedded), with tests and documentation.

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
README.md Documents the new experimental config/env options and offline requirement.
pkg/rekor/rekor.go Switches signature parsing to shared compat PEM/DER parsing helper.
pkg/git/verify.go Extends verification summary to optionally include the verified bundle.
pkg/git/verifier.go Switches signature parsing to compat helper (PEM/DER).
pkg/git/signature_test.go Adds sigstore-go Keypair adapter usage in tests.
internal/sigstore/compat/testdata/tlog.json Adds Rekor log entry fixture for compat tests.
internal/sigstore/compat/testdata/commit.txt Adds signed commit fixture for compat tests.
internal/sigstore/compat/signeddata.go Adds bundle→CMS assembly helpers for signature storage compatibility.
internal/sigstore/compat/signeddata_test.go Tests byte-for-byte CMS equivalence between legacy and assembled CMS.
internal/sigstore/compat/keypair.go Adapts crypto.Signer to sigstore-go sign.Keypair.
internal/sigstore/compat/compat.go Implements CMS→bundle conversion, artifact definition, and Rekor/timestamp mapping.
internal/sigstore/compat/compat_test.go Tests CMS↔bundle conversions and Rekor attribute round-tripping.
internal/sigstore/compat/certificate.go Provides a sigstore-go CertificateProvider backed by gitsign’s Fulcio cert.
internal/sigstore/compat/attrs.go Extracts RFC3161 timestamp tokens from CMS unsigned attributes.
internal/signature/testdata/tlog.json Adds Rekor log entry fixture for bundle signing tests.
internal/signature/sign.go Adds experimental bundle signing option (sigstore-go signing + conversion).
internal/signature/bundlesign.go Implements sign→bundle→CMS flow and Rekor client wrapper for inclusion-proof enforcement.
internal/signature/bundle_test.go Unit tests for bundle signing path and Rekor wrapper behavior.
internal/rekor/oid/pbcompat.go Adds protobuf→LogEntryAnon conversion for bundle-carried tlog entries.
internal/rekor/oid/oid.go Refactors Rekor body recomputation and adds TransparencyLogEntry proto reconstruction.
internal/gitsign/trustedroot.go Builds sigstore-go TrustedMaterial from existing gitsign trust sources.
internal/gitsign/testdata/online.commit Adds “online signature” fixture to ensure bundle path falls back correctly.
internal/gitsign/gitsign.go Wires cfg gating and fallback logic for bundle-based verification.
internal/gitsign/gitsign_test.go Updates identity test type to support sigstore-go Keypair adapter.
internal/gitsign/bundle.go Adds bundle verification implementation (CMS→bundle conversion + sigstore-go verification + content binding).
internal/gitsign/bundle_test.go Tests fallback behavior when no embedded Rekor entry exists.
internal/fulcio/identity.go Adds Keypair() to Fulcio identity to support bundle signing.
internal/fulcio/fulcioroots/fulcioroots.go Adds raw-certs access for building sigstore-go trust material.
internal/fork/ietf-cms/assemble.go Adds helpers to build signed attributes and assemble CMS with external signatures.
internal/fork/ietf-cms/assemble_test.go Tests external-signature CMS assembly verifies with fork verifier.
internal/e2e/bundle_test.go E2E parity test between legacy and bundle verification paths.
internal/config/config.go Adds config/env parsing + enforces offline Rekor mode when sigstore-go enabled.
internal/config/config_test.go Tests offline requirement enforcement for enableSigstoreGo.
internal/commands/root/sign.go Enables bundle signing path from CLI when config flag is set (and emits a notice).
go.mod Promotes sigstore-go to a direct dependency.
docs/bundle-cms.md Documents the CMS↔bundle mapping and the experimental enablement flow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/signature/bundlesign.go
Comment thread internal/signature/sign.go
- Preallocate the claims slice via a composite literal (prealloc).
- Drop the unused *models.LogEntryAnon return from loadSignedData and the
  constant path parameter from parseCommit in the compat tests (unparam).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Billy Lynch <billy@chainguard.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants