Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ The following config options are supported:
| timestampCertChain | | Path to PEM encoded certificate chain for RFC3161 Timestamp Authority verification. |
| autoclose | true | If true, autoclose the browser window after `autocloseTimeout`. In order for autoclose to work you must also set `connectorID`. |
| autocloseTimeout | 6 | If `autoclose` is true, this is how long to wait until the window is closed. |
| enableSigstoreGo | false | (Experimental) If true, use the sigstore-go libraries for signing and verification via the CMS↔bundle compatibility layer. Requires `rekorMode=offline`. The on-disk signature format is unchanged. See [docs/bundle-cms.md](./docs/bundle-cms.md) for more details. |
| enableSigstoreGo | true | If true (the default), use the sigstore-go libraries via the CMS↔bundle compatibility layer. Verification uses sigstore-go in all Rekor modes; signing uses it only in `rekorMode=offline` (online/legacy signing stays on the existing CMS path). Set to false to fall back to the legacy implementation. The on-disk signature format is unchanged. See [docs/bundle-cms.md](./docs/bundle-cms.md) for more details. |
| urlOpener | | Optional command used to open the OIDC login URL during the interactive auth flow. If unset, the platform default browser is used. The command is split using shell-style word splitting (quote arguments containing spaces) and each token is rendered as a Go [text/template](https://pkg.go.dev/text/template) with the login URL available as `{{.URL}}` (e.g. `firefox --new-tab {{.URL}}`). No shell is invoked, so shell metacharacters are inert. See [docs/url-opener.md](./docs/url-opener.md) for examples, including opening a specific Chrome profile. |

### Environment Variables
Expand All @@ -105,10 +105,10 @@ The following config options are supported:
| GITSIGN_TIMESTAMP_SERVER_URL | ✅ | | Address of timestamping authority. If set, a trusted timestamp will be included in the signature. |
| GITSIGN_TIMESTAMP_CERT_CHAIN | ✅ | | Path to PEM encoded certificate chain for RFC3161 Timestamp Authority verification. |
| GITSIGN_FULCIO_ROOT | ✅ | | Path to PEM encoded certificate for Fulcio CA (additional alias: SIGSTORE_ROOT_FILE) |
| GITSIGN_REKOR_MODE | ❌ | online | Rekor storage mode to operate in. One of [online, offline] (default: online)<br>online - Commit SHAs are stored in Rekor, requiring online verification for all commit objects.<br>offline - Hashed commit content is stored in Rekor, with Rekor attributes necessary for offline verification being stored in the commit itself.<br>Note: online verification will be deprecated in favor of offline in the future. |
| GITSIGN_REKOR_MODE | ❌ | offline | Rekor storage mode to operate in. One of [online, offline] (default: offline)<br>online - Commit SHAs are stored in Rekor, requiring online verification for all commit objects.<br>offline - Hashed commit content is stored in Rekor, with Rekor attributes necessary for offline verification being stored in the commit itself.<br>Note: online verification will be deprecated in favor of offline in the future. |
| GITSIGN_AUTOCLOSE | ❌ | true | If true, autoclose the browser window after `GITSIGN_AUTOCLOSE_TIME`. |
| GITSIGN_AUTOCLOSE_TIMEOUT | ❌ | 6 | If `GITSIGN_AUTOCLOSE` is true, this is how long to wait until the window is closed. |
| GITSIGN_ENABLE_SIGSTORE_GO | ❌ | false | (Experimental) If true, use the sigstore-go libraries for signing and verification via the CMS↔bundle compatibility layer. Requires `GITSIGN_REKOR_MODE=offline`. The on-disk signature format is unchanged. See [docs/bundle-cms.md](./docs/bundle-cms.md) for more details. |
| GITSIGN_ENABLE_SIGSTORE_GO | ❌ | true | If true (the default), use the sigstore-go libraries via the CMS↔bundle compatibility layer. Verification uses sigstore-go in all Rekor modes; signing uses it only in `GITSIGN_REKOR_MODE=offline` (online/legacy signing stays on the existing CMS path). Set to false to fall back to the legacy implementation. The on-disk signature format is unchanged. See [docs/bundle-cms.md](./docs/bundle-cms.md) for more details. |
| GITSIGN_URL_OPENER | ❌ | | Optional command used to open the OIDC login URL during the interactive auth flow. If unset, the platform default browser is used. The command is split using shell-style word splitting (quote arguments containing spaces) and each token is rendered as a Go text/template with the login URL available as `{{.URL}}` (e.g. `firefox --new-tab {{.URL}}`). No shell is invoked, so shell metacharacters are inert. See [docs/url-opener.md](./docs/url-opener.md) for examples, including opening a specific Chrome profile. |

For environment variables that support `Sigstore Prefix`, the values may be
Expand Down Expand Up @@ -301,7 +301,7 @@ Gitsign stores data in 2 places:
time, Gitsign records commits and the code signing certificates to
[Rekor](https://docs.sigstore.dev/rekor/overview/).

- If `rekorMode = online` (default)
- If `rekorMode = online`

This data is a
[HashedRekord](https://github.com/sigstore/rekor/blob/e375eb461cae524270889b57a249ff086bea6c05/types.md#hashed-rekord)
Expand All @@ -310,10 +310,7 @@ Gitsign stores data in 2 places:
[Verifying the Transparency Log](#verifying-the-transparency-log) for more
details.

- If `rekorMode = offline`

Note: offline verification is new, and should be considered experimental for
now.
- If `rekorMode = offline` (default)

By default, data is written to the
[public Rekor instance](https://docs.sigstore.dev/rekor/public-instance). In
Expand Down
25 changes: 16 additions & 9 deletions docs/bundle-cms.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ explains how the two formats relate and how the conversion works. The
compatibility layer lives in
[`internal/sigstore/compat`](../internal/sigstore/compat).

> **Note:** This conversion is currently experimental and opt-in. See
> [Enabling the sigstore-go path](#enabling-the-sigstore-go-path) below.
> **Note:** This conversion is enabled by default. See
> [Enabling the sigstore-go path](#enabling-the-sigstore-go-path) below for how
> to disable it or how it interacts with the Rekor mode.

## The key insight: the signed artifact is the SignedAttrs

Expand Down Expand Up @@ -104,17 +105,23 @@ Because the signature is over the `SignedAttrs` — which include the signing ti

## Enabling the sigstore-go path

Both directions are gated behind a single experimental option, off by default:
The sigstore-go path is controlled by a single option, on by default:

```sh
git config gitsign.enableSigstoreGo true
# or: GITSIGN_ENABLE_SIGSTORE_GO=true
# Disable it and fall back to the legacy CMS + Rekor implementation:
git config gitsign.enableSigstoreGo false
# or: GITSIGN_ENABLE_SIGSTORE_GO=false
```

This requires offline Rekor mode (`gitsign.rekorMode=offline`); gitsign will
error at startup otherwise, since the bundle signing path embeds the Rekor entry
in the signature (which is meaningless in online mode). Online/legacy signing
remains on the existing CMS path.
The two directions engage differently depending on the Rekor mode:

- **Verification** uses sigstore-go in all Rekor modes. Legacy online signatures
carry no embedded Rekor entry — sigstore-go cannot look these up from the
signature alone, so they transparently fall back to the legacy verifier.
- **Signing** uses sigstore-go only in offline Rekor mode
(`gitsign.rekorMode=offline`), where the bundle signing path embeds the Rekor
entry in the signature. Online/legacy signing remains on the existing CMS path
even when the option is enabled.

The on-disk CMS signature format is identical whether or not the option is set;
only the implementation of how signing and verification are performed changes.
7 changes: 5 additions & 2 deletions internal/commands/root/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ func commandSign(o *options, s *gsio.Streams, args ...string) error {
return errors.New("specify --help, --sign, or --verify")
}

if o.Config.EnableSigstoreGo {
fmt.Fprintln(s.TTYOut, "gitsign: experimental sigstore-go signing enabled") // nolint:errcheck
// The sigstore-go signing path only engages in offline Rekor mode (it embeds
// the Rekor entry in the signature); online/legacy signing stays on the CMS
// path even when sigstore-go is enabled.
if o.Config.EnableSigstoreGo && o.Config.RekorMode == "offline" {
fmt.Fprintln(s.TTYOut, "gitsign: sigstore-go signing enabled") // nolint:errcheck
}

userIdent, err := fulcio.NewIdentity(ctx, o.Config, s.TTYIn, s.TTYOut)
Expand Down
34 changes: 16 additions & 18 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,22 @@ type Config struct {

// Address of Rekor server
Rekor string
// Rekor storage mode to operate in. One of [online, offline] (default: online)
// Rekor storage mode to operate in. One of [online, offline] (default: offline)
// online - Commit SHAs are stored in Rekor, requiring online verification for all commit objects.
// offline - Hashed commit content is stored in Rekor, with Rekor attributes
// necessary for offline verification being stored in the commit itself.
// Note: online verification will be deprecated in favor of offline in the future.
RekorMode string

// EnableSigstoreGo enables the experimental sigstore-go code paths for both
// signing and verification (via the CMS<->bundle compat layer). When false
// (the default), the legacy CMS + Rekor signing and verification are used.
// The on-disk CMS signature format is unchanged either way.
// EnableSigstoreGo enables the sigstore-go code paths (via the CMS<->bundle
// compat layer). It is on by default; set it to false to fall back to the
// legacy CMS + Rekor implementation. The on-disk CMS signature format is
// unchanged either way.
//
// Verification uses sigstore-go in all Rekor modes (legacy online signatures
// with no embedded entry fall back to the legacy verifier). Signing uses
// sigstore-go only in offline Rekor mode, where the Rekor entry is embedded
// in the signature; online/legacy signing stays on the existing CMS path.
EnableSigstoreGo bool

// OIDC client ID for application
Expand Down Expand Up @@ -147,12 +152,12 @@ func Get() (*Config, error) {

// Start with default config
out := &Config{
Fulcio: "https://fulcio.sigstore.dev",
Rekor: "https://rekor.sigstore.dev",
ClientID: "sigstore",
Issuer: "https://oauth2.sigstore.dev/auth",
// TODO: default to offline
RekorMode: "online",
Fulcio: "https://fulcio.sigstore.dev",
Rekor: "https://rekor.sigstore.dev",
ClientID: "sigstore",
Issuer: "https://oauth2.sigstore.dev/auth",
RekorMode: "offline",
EnableSigstoreGo: true,
Autoclose: true,
AutocloseTimeout: 6,
}
Expand Down Expand Up @@ -190,13 +195,6 @@ func Get() (*Config, error) {
out.URLOpener = envOrValue("GITSIGN_URL_OPENER", out.URLOpener)
out.EnableSigstoreGo = envOrValue("GITSIGN_ENABLE_SIGSTORE_GO", fmt.Sprintf("%t", out.EnableSigstoreGo)) == "true"

// The sigstore-go signing path embeds the Rekor entry in the signature, which
// only applies to offline Rekor mode. Fail loudly rather than silently
// ignoring the setting in online mode.
if out.EnableSigstoreGo && out.RekorMode != "offline" {
return nil, fmt.Errorf("gitsign.enableSigstoreGo requires gitsign.rekorMode=offline, got %q", out.RekorMode)
}

return out, nil
}

Expand Down
43 changes: 33 additions & 10 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ func TestGet(t *testing.T) {
Issuer: "tacocat",
RedirectURL: "example.com",
ConnectorID: "bar",
RekorMode: "online",
RekorMode: "offline",
EnableSigstoreGo: true,
Autoclose: true,
AutocloseTimeout: 6,
// From config file.
Expand All @@ -107,22 +108,44 @@ func TestGet(t *testing.T) {
}
}

func TestEnableSigstoreGoRequiresOffline(t *testing.T) {
func TestEnableSigstoreGo(t *testing.T) {
t.Cleanup(func() { execFn = realExec })

// enableSigstoreGo with the default (online) Rekor mode must error.
// sigstore-go is opted in by default, including in the default (online)
// Rekor mode.
execFn = func() (io.Reader, error) {
return strings.NewReader("gitsign.enableSigstoreGo true\n"), nil
return strings.NewReader(""), nil
}
if _, err := Get(); err == nil {
t.Error("expected error when enableSigstoreGo is set without offline rekor mode")
got, err := Get()
if err != nil {
t.Fatalf("unexpected error with default config: %v", err)
}
if !got.EnableSigstoreGo {
t.Error("expected EnableSigstoreGo to default to true")
}

// enableSigstoreGo with offline mode is allowed.
// It can be turned off via git config.
execFn = func() (io.Reader, error) {
return strings.NewReader("gitsign.enableSigstoreGo true\ngitsign.rekorMode offline\n"), nil
return strings.NewReader("gitsign.enableSigstoreGo false\n"), nil
}
got, err = Get()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got.EnableSigstoreGo {
t.Error("expected gitsign.enableSigstoreGo=false to disable sigstore-go")
}

// It can be turned off via environment variable.
t.Setenv("GITSIGN_ENABLE_SIGSTORE_GO", "false")
execFn = func() (io.Reader, error) {
return strings.NewReader(""), nil
}
got, err = Get()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := Get(); err != nil {
t.Errorf("expected enableSigstoreGo + offline mode to be valid, got: %v", err)
if got.EnableSigstoreGo {
t.Error("expected GITSIGN_ENABLE_SIGSTORE_GO=false to disable sigstore-go")
}
}
Loading