Skip to content
Open
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
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 @@ -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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Don't blanket disable linting rules like this - prefer disabling per-line with a description (happy to resolve any of these in another PR).


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 2024 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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Plumb through the context to the tuf.Options

return func() ([]*x509.Certificate, error) {
tufClient, err := tuf.NewFromEnv(ctx)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think there's a regression here where we'd look for TUF_ROOT env var for a local root, though idk how documented this was in the broader sigstore clients. I don't think sigstore-go handles this natively though.

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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return nil, fmt.Errorf("initializing tuf: %w", err)
return nil, fmt.Errorf("fetching fulcio certs: %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
124 changes: 124 additions & 0 deletions internal/sigstoreroot/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2024 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 and
// cached root.json from the local TUF cache when a custom mirror is configured.
func TUFOptions() *tuf.Options {
opts := tuf.DefaultOptions()
if mirror, err := readRemoteHint(opts.CachePath); err == nil && mirror != "" {
opts.RepositoryBaseURL = mirror
}
if opts.RepositoryBaseURL != tuf.DefaultMirror {
cachedRoot := filepath.Join(opts.CachePath, tuf.URLToPath(opts.RepositoryBaseURL), "root.json")
if rootBytes, err := os.ReadFile(cachedRoot); err == nil {
opts.Root = rootBytes
}
}
return opts
Comment on lines +35 to +45

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I assume this is to replicate cosign behavior? I wonder if there's something we can do to standardize that 🤔 (for now link to other similar behavior to explain why we're doing this / why sigstore-go doesn't handle it).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this is more of a regression after 0.13, but it's possible that it makes sense, i haven't done a diff between 0.13 and 0.14... gitsign only started referencing that expired embedded root cert after gitsign version 0.13. So I rolled back and was testing with 0.13 and tufmirror defined and it would work fine until i referenced timestampserverurl (tsa) in the git config. All commits i did in mye testing repo was fine and tsa got correctly set inside the commits, however when i tried to verify them with gitsign verify, they always claimed to be invalid, even though their not before and not after parts where correct. This is where my proposal came in, i think that these fixups are very important for gitsign, since we are experiencing functionality breaking in 0.14 along with this, which was already an issue before 0.13 as well, at least it would seem:

diff --git a/internal/fork/ietf-cms/verify.go b/internal/fork/ietf-cms/verify.go
index fb5fb7e..ec3caf5 100644
--- a/internal/fork/ietf-cms/verify.go
+++ b/internal/fork/ietf-cms/verify.go
@@ -171,7 +171,7 @@ func (sd *SignedData) verify(econtent []byte, opts x509.VerifyOptions, tsOpts x5
                        // This check is slightly redundant, given that the cert validity times
                        // are checked by cert.Verify. We take the timestamp accuracy into account
                        // here though, whereas cert.Verify will not.
-                       if !tsti.Before(cert.NotAfter) || !tsti.After(cert.NotBefore) {
+                       if tsti.After(cert.NotAfter) || tsti.Before(cert.NotBefore) {
                                return nil, x509.CertificateInvalidError{Cert: cert, Reason: x509.Expired, Detail: "timestamp authority verification failed"}
                        }

}

// 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