Skip to content
Merged
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
7 changes: 6 additions & 1 deletion e2e/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/go-git/go-git/v5/storage/memory"
"github.com/sigstore/cosign/v3/pkg/providers"
"github.com/sigstore/gitsign/internal/git/gittest"
"github.com/sigstore/gitsign/internal/sigstoreroot"
"github.com/sigstore/gitsign/pkg/fulcio"
gsgit "github.com/sigstore/gitsign/pkg/git"
"github.com/sigstore/gitsign/pkg/gitsign"
Expand Down Expand Up @@ -104,7 +105,11 @@ func TestSign(t *testing.T) {
sig := []byte(commit.PGPSignature)

// Verify the commit
verifier, err := gsgit.NewDefaultVerifier(ctx)
trustedRoot, err := sigstoreroot.FetchTrustedRoot()
if err != nil {
t.Fatalf("error fetching trusted root: %v", err)
}
verifier, err := gsgit.NewVerifierFromTrustedRoot(trustedRoot)
if err != nil {
t.Fatal(err)
}
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ require (
github.com/jonboulle/clockwork v0.5.0
github.com/mattn/go-tty v0.0.7
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/secure-systems-lab/go-securesystemslib v0.10.0
github.com/sigstore/cosign/v3 v3.0.4
github.com/sigstore/fulcio v1.8.5
github.com/sigstore/protobuf-specs v0.5.0
github.com/sigstore/rekor v1.5.0
github.com/sigstore/sigstore v1.10.4
github.com/sigstore/sigstore-go v1.1.4
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
golang.org/x/crypto v0.47.0
Expand Down Expand Up @@ -206,7 +208,6 @@ require (
github.com/pborman/uuid v1.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
Expand All @@ -215,7 +216,6 @@ require (
github.com/sergi/go-diff v1.4.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/sigstore/rekor-tiles/v2 v2.0.1 // indirect
github.com/sigstore/sigstore-go v1.1.4 // indirect
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.3 // indirect
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.3 // indirect
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.3 // indirect
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package version
package version // nolint:revive

import (
"encoding/json"
Expand Down
11 changes: 7 additions & 4 deletions internal/e2e/offline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@ import (

"github.com/sigstore/gitsign/internal/fulcio/fulcioroots"
"github.com/sigstore/gitsign/internal/git/gittest"
"github.com/sigstore/gitsign/internal/sigstoreroot"
"github.com/sigstore/gitsign/pkg/git"
"github.com/sigstore/gitsign/pkg/rekor"
"github.com/sigstore/sigstore/pkg/tuf"
)

func TestVerifyOffline(t *testing.T) {
ctx := context.Background()

// Initialize to prod root.
tuf.Initialize(ctx, tuf.DefaultRemoteRoot, nil)
root, intermediate, err := fulcioroots.New(x509.NewCertPool(), fulcioroots.FromTUF(ctx))
trustedRoot, err := sigstoreroot.FetchTrustedRoot()
if err != nil {
t.Fatalf("error fetching trusted root: %v", err)
}

root, intermediate, err := fulcioroots.New(x509.NewCertPool(), fulcioroots.FromTrustedRoot(trustedRoot))
if err != nil {
t.Fatalf("error getting certificate root: %v", err)
}
Expand Down
69 changes: 69 additions & 0 deletions internal/e2e/sigstoreroot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2026 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build e2e
// +build e2e

package e2e

import (
"testing"

"github.com/sigstore/gitsign/internal/sigstoreroot"
)

func TestFetchTrustedRoot(t *testing.T) {
trustedRoot, err := sigstoreroot.FetchTrustedRoot()
if err != nil {
t.Fatalf("FetchTrustedRoot() error = %v", err)
}
if trustedRoot == nil {
t.Fatal("FetchTrustedRoot() returned nil")
}

ctPubs, err := sigstoreroot.GetCTLogPubs(trustedRoot)
if err != nil {
t.Fatalf("GetCTLogPubs() error = %v", err)
}
if len(ctPubs.Keys) == 0 {
t.Fatal("GetCTLogPubs() returned no keys")
}

rekorPubs, err := sigstoreroot.GetRekorPubs(trustedRoot)
if err != nil {
t.Fatalf("GetRekorPubs() error = %v", err)
}
if len(rekorPubs.Keys) == 0 {
t.Fatal("GetRekorPubs() returned no keys")
}

certs, err := sigstoreroot.FulcioCertificates(trustedRoot)
if err != nil {
t.Fatalf("FulcioCertificates() error = %v", err)
}
if len(certs) == 0 {
t.Fatal("FulcioCertificates() returned no certificates")
}

hasCA := false
for _, cert := range certs {
if cert.IsCA {
hasCA = true
break
}
}
if !hasCA {
t.Fatal("FulcioCertificates() did not return any CA certificates")
}
}
46 changes: 16 additions & 30 deletions internal/fulcio/fulcioroots/fulcioroots.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import (
"bytes"
"context"
"crypto/x509"
"errors"
"fmt"
"os"

"github.com/sigstore/gitsign/internal/config"
"github.com/sigstore/gitsign/internal/sigstoreroot"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/tuf"
)

type CertificateSource func() ([]*x509.Certificate, error)
Expand Down Expand Up @@ -67,41 +67,27 @@ func NewFromConfig(ctx context.Context, cfg *config.Config) (*x509.CertPool, *x5
return New(x509.NewCertPool(), src...)
}

const (
// This is the root in the fulcio project.
fulcioTargetStr = "fulcio.crt.pem"

// This is the v1 migrated root.
fulcioV1TargetStr = "fulcio_v1.crt.pem"

// This is the untrusted v1 intermediate CA certificate, used or chain building.
fulcioV1IntermediateTargetStr = "fulcio_intermediate_v1.crt.pem"
)

// FromTUF loads certs from the TUF client.
func FromTUF(ctx context.Context) CertificateSource {
// FromTUF loads Fulcio certificates from the sigstore-go TUF cache.
func FromTUF(_ context.Context) CertificateSource {
return func() ([]*x509.Certificate, error) {
tufClient, err := tuf.NewFromEnv(ctx)
trustedRoot, err := sigstoreroot.FetchTrustedRoot()
if err != nil {
return nil, fmt.Errorf("initializing tuf: %w", err)
}
// Retrieve from the embedded or cached TUF root. If expired, a network
// call is made to update the root.
targets, err := tufClient.GetTargetsByMeta(tuf.Fulcio, []string{fulcioTargetStr, fulcioV1TargetStr, fulcioV1IntermediateTargetStr})
certs, err := sigstoreroot.FulcioCertificates(trustedRoot)
if err != nil {
return nil, fmt.Errorf("error getting targets: %w", err)
}
if len(targets) == 0 {
return nil, errors.New("none of the Fulcio roots have been found")
return nil, fmt.Errorf("initializing tuf: %w", err)
}
return certs, nil
}
}

certs := []*x509.Certificate{}
for _, t := range targets {
c, err := cryptoutils.UnmarshalCertificatesFromPEM(t.Target)
if err != nil {
return nil, fmt.Errorf("error unmarshalling certificates: %w", err)
}
certs = append(certs, c...)
// FromTrustedRoot loads Fulcio certificates from a pre-fetched TrustedRoot.
func FromTrustedRoot(trustedRoot *root.TrustedRoot) CertificateSource {
return func() ([]*x509.Certificate, error) {
certs, err := sigstoreroot.FulcioCertificates(trustedRoot)
if err != nil {
return nil, fmt.Errorf("getting fulcio certificates from trusted root: %w", err)
}
return certs, nil
}
Expand Down
13 changes: 11 additions & 2 deletions internal/gitsign/gitsign.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/sigstore/gitsign/internal/config"
"github.com/sigstore/gitsign/internal/fulcio/fulcioroots"
rekorinternal "github.com/sigstore/gitsign/internal/rekor"
"github.com/sigstore/gitsign/internal/sigstoreroot"
"github.com/sigstore/gitsign/pkg/git"
"github.com/sigstore/gitsign/pkg/rekor"
"github.com/sigstore/sigstore/pkg/cryptoutils"
Expand Down Expand Up @@ -86,10 +87,18 @@ func NewVerifierWithCosignOpts(ctx context.Context, cfg *config.Config, opts *co
// and warn if missing.
var certverifier cert.Verifier
if opts != nil {
ctpub, err := cosign.GetCTLogPubs(ctx)
trustedRoot, err := sigstoreroot.FetchTrustedRoot()
if err != nil {
return nil, fmt.Errorf("error fetching trusted root: %w", err)
}
ctpub, err := sigstoreroot.GetCTLogPubs(trustedRoot)
if err != nil {
return nil, fmt.Errorf("error getting CT log public key: %w", err)
}
rekorPubs, err := sigstoreroot.GetRekorPubs(trustedRoot)
if err != nil {
return nil, fmt.Errorf("error getting Rekor public key: %w", err)
}
identities, err := opts.Identities()
if err != nil {
return nil, fmt.Errorf("error parsing identities: %w", err)
Expand All @@ -99,7 +108,7 @@ func NewVerifierWithCosignOpts(ctx context.Context, cfg *config.Config, opts *co
RootCerts: root,
IntermediateCerts: intermediate,
CTLogPubKeys: ctpub,
RekorPubKeys: rekor.PublicKeys(),
RekorPubKeys: rekorPubs,
CertGithubWorkflowTrigger: opts.CertGithubWorkflowTrigger,
CertGithubWorkflowSha: opts.CertGithubWorkflowSha,
CertGithubWorkflowName: opts.CertGithubWorkflowName,
Expand Down
117 changes: 117 additions & 0 deletions internal/sigstoreroot/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2026 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package sigstoreroot loads the Sigstore trusted root via the sigstore-go TUF client.
package sigstoreroot

import (
"crypto/x509"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"

"github.com/sigstore/cosign/v3/pkg/cosign"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/tuf"
sigstoretuf "github.com/sigstore/sigstore/pkg/tuf"
)

// TUFOptions returns sigstore-go TUF options, reading the mirror URL from remote.json if available.
func TUFOptions() *tuf.Options {
opts := tuf.DefaultOptions()
if mirror, err := readRemoteHint(opts.CachePath); err == nil && mirror != "" {
opts.RepositoryBaseURL = mirror
}
return opts
}

// FetchTrustedRoot loads the Sigstore trusted root from the TUF cache.
func FetchTrustedRoot() (*root.TrustedRoot, error) {
return root.FetchTrustedRootWithOptions(TUFOptions())
}

// GetCTLogPubs returns CT log public keys from the trusted root.
func GetCTLogPubs(trustedRoot *root.TrustedRoot) (*cosign.TrustedTransparencyLogPubKeys, error) {
return transparencyLogPubKeys(trustedRoot.CTLogs())
}

// GetRekorPubs returns Rekor transparency log public keys from the trusted root.
func GetRekorPubs(trustedRoot *root.TrustedRoot) (*cosign.TrustedTransparencyLogPubKeys, error) {
return transparencyLogPubKeys(trustedRoot.RekorLogs())
}

// FulcioCertificates extracts Fulcio root and intermediate certificates from the trusted root.
func FulcioCertificates(trustedRoot *root.TrustedRoot) ([]*x509.Certificate, error) {
cas := trustedRoot.FulcioCertificateAuthorities()
if len(cas) == 0 {
return nil, fmt.Errorf("no Fulcio certificate authorities found in trusted root")
}

var certs []*x509.Certificate
for _, ca := range cas {
fca, ok := ca.(*root.FulcioCertificateAuthority)
if !ok {
continue
}
if fca.Root != nil {
certs = append(certs, fca.Root)
}
certs = append(certs, fca.Intermediates...)
}
if len(certs) == 0 {
return nil, fmt.Errorf("no Fulcio certificates found in trusted root")
}
return certs, nil
}

func transparencyLogPubKeys(logs map[string]*root.TransparencyLog) (*cosign.TrustedTransparencyLogPubKeys, error) {
pubKeys := cosign.NewTrustedTransparencyLogPubKeys()

for logID, log := range logs {
if log.PublicKey == nil {
continue
}

status := sigstoretuf.Active
if !log.ValidityPeriodEnd.IsZero() && time.Now().After(log.ValidityPeriodEnd) {
status = sigstoretuf.Expired
}

pubKeys.Keys[logID] = cosign.TransparencyLogPubKey{
PubKey: log.PublicKey,
Status: status,
}
}

if len(pubKeys.Keys) == 0 {
return nil, fmt.Errorf("no transparency log public keys found")
}
return &pubKeys, nil
}

func readRemoteHint(cachePath string) (string, error) {
data, err := os.ReadFile(filepath.Join(cachePath, "remote.json"))
if err != nil {
return "", err
}
var remote struct {
Mirror string `json:"mirror"`
}
if err := json.Unmarshal(data, &remote); err != nil {
return "", err
}
return remote.Mirror, nil
}
Loading
Loading