Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to TUF v2 client #3844

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
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
20 changes: 18 additions & 2 deletions cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/internal/ui"
"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/sigstore/sigstore-go/pkg/verify"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
)

Expand All @@ -32,12 +34,26 @@ func NewSigner(ctx context.Context, ko options.KeyOpts, signer signature.SignerV
return nil, err
}

// Grab the PublicKeys for the CTFE, either from tuf or env.
if ko.TrustedMaterial != nil && len(fs.SCT) == 0 {
// We assume that if a trusted_root.json was found, the fulcio chain was included in it.
// fs.Chain will be ignored as root.VerifySignedCertificateTimestamp relies on the trusted root.
// Detached SCTs cannot be verified with this function.
certs, err := cryptoutils.UnmarshalCertificatesFromPEM(fs.Cert)
if err != nil || len(certs) < 1 {
return nil, fmt.Errorf("unmarshalling SCT from PEM: %w", err)
Copy link
Member

Choose a reason for hiding this comment

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

Would "unmarshalling certificate from PEM for SCT verification" make more sense? or am I misreading this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, will fix

}
if err := verify.VerifySignedCertificateTimestamp(certs[0], 1, ko.TrustedMaterial); err != nil {
return nil, fmt.Errorf("verifying SCT using trusted root: %w", err)
}
ui.Infof(ctx, "Successfully verified SCT...")
return fs, nil
}

// There was no trusted_root.json or we need to verify a detached SCT, so grab the PublicKeys for the CTFE, either from tuf or env.
pubKeys, err := cosign.GetCTLogPubs(ctx)
if err != nil {
return nil, fmt.Errorf("getting CTFE public keys: %w", err)
}

// verify the sct
if err := cosign.VerifySCT(ctx, fs.Cert, fs.Chain, fs.SCT, pubKeys); err != nil {
return nil, fmt.Errorf("verifying SCT: %w", err)
Expand Down
36 changes: 33 additions & 3 deletions cmd/cosign/cli/initialize/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ import (
_ "embed" // To enable the `go:embed` directive.
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/sigstore/cosign/v2/internal/ui"
"github.com/sigstore/cosign/v2/pkg/blob"
"github.com/sigstore/sigstore/pkg/tuf"
tufroot "github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/tuf"
tufv1 "github.com/sigstore/sigstore/pkg/tuf"
)

func DoInitialize(ctx context.Context, root, mirror string) error {
Expand All @@ -36,11 +41,36 @@ func DoInitialize(ctx context.Context, root, mirror string) error {
}
}

if err := tuf.Initialize(ctx, mirror, rootFileBytes); err != nil {
opts := tuf.DefaultOptions()
if root != "" {
opts.Root = rootFileBytes
}
if mirror != "" {
opts.RepositoryBaseURL = mirror
}
trustedRoot, err := tufroot.NewLiveTrustedRoot(opts)
if err != nil {
ui.Warnf(ctx, "Could not find trusted_root.json in TUF mirror, falling back to individual targets. It is recommended to update your TUF metadata repository to include trusted_root.json.")
}
// Leave a hint for where the current remote is. Adopted from sigstore/sigstore TUF client.
remote := map[string]string{"remote": opts.RepositoryBaseURL}
remoteBytes, err := json.Marshal(remote)
if err != nil {
return err
}
if err := os.WriteFile(filepath.FromSlash(filepath.Join(opts.CachePath, "remote.json")), remoteBytes, 0o600); err != nil {
return fmt.Errorf("storing remote: %w", err)
}
if trustedRoot != nil {
return nil
}

// The mirror did not have a trusted_root.json, so initialize the legacy TUF targets.
if err := tufv1.Initialize(ctx, mirror, rootFileBytes); err != nil {
return err
}

status, err := tuf.GetRootStatus(ctx)
status, err := tufv1.GetRootStatus(ctx)
if err != nil {
return err
}
Expand Down
8 changes: 7 additions & 1 deletion cmd/cosign/cli/options/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@

package options

import "github.com/sigstore/cosign/v2/pkg/cosign"
import (
"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/sigstore/sigstore-go/pkg/root"
)

type KeyOpts struct {
Sk bool
Expand Down Expand Up @@ -53,4 +56,7 @@ type KeyOpts struct {
// Modeled after InsecureSkipVerify in tls.Config, this disables
// verifying the SCT.
InsecureSkipFulcioVerify bool

// TrustedMaterial contains trusted metadata for all Sigstore services. It is exclusive with RekorPubKeys, RootCerts, IntermediateCerts, CTLogPubKeys, and the TSA* cert fields.
TrustedMaterial root.TrustedMaterial
}
5 changes: 5 additions & 0 deletions cmd/cosign/cli/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/sigstore/cosign/v2/cmd/cosign/cli/generate"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
"github.com/sigstore/cosign/v2/pkg/cosign"
)

func Sign() *cobra.Command {
Expand Down Expand Up @@ -103,6 +104,9 @@ race conditions or (worse) malicious tampering.
if err != nil {
return err
}

trustedMaterial, _ := cosign.TrustedRoot()

ko := options.KeyOpts{
KeyRef: o.Key,
PassFunc: generate.GetPass,
Expand All @@ -126,6 +130,7 @@ race conditions or (worse) malicious tampering.
TSAServerName: o.TSAServerName,
TSAServerURL: o.TSAServerURL,
IssueCertificateForExistingKey: o.IssueCertificate,
TrustedMaterial: trustedMaterial,
}
if err := sign.SignCmd(ro, ko, *o, args); err != nil {
if o.Attachment == "" {
Expand Down
16 changes: 11 additions & 5 deletions cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import (
ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
"github.com/sigstore/cosign/v2/pkg/oci/walk"
sigs "github.com/sigstore/cosign/v2/pkg/signature"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
Expand Down Expand Up @@ -391,7 +392,7 @@ func signerFromSecurityKey(ctx context.Context, keySlot string) (*SignerVerifier
}, nil
}

func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef string, passFunc cosign.PassFunc) (*SignerVerifier, error) {
func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef string, passFunc cosign.PassFunc, trustedMaterial root.TrustedMaterial) (*SignerVerifier, error) {
k, err := sigs.SignerVerifierFromKeyRef(ctx, keyRef, passFunc)
if err != nil {
return nil, fmt.Errorf("reading key: %w", err)
Expand Down Expand Up @@ -505,9 +506,14 @@ func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef strin
return nil, err
}
if contains {
pubKeys, err := cosign.GetCTLogPubs(ctx)
if err != nil {
return nil, fmt.Errorf("getting CTLog public keys: %w", err)
var pubKeys any
if trustedMaterial != nil {
pubKeys = trustedMaterial.CTLogs()
} else {
pubKeys, err = cosign.GetCTLogPubs(ctx)
if err != nil {
return nil, fmt.Errorf("getting CTLog public keys: %w", err)
}
}
var chain []*x509.Certificate
chain = append(chain, leafCert)
Expand Down Expand Up @@ -567,7 +573,7 @@ func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath strin
case ko.Sk:
sv, err = signerFromSecurityKey(ctx, ko.Slot)
case ko.KeyRef != "":
sv, err = signerFromKeyRef(ctx, certPath, certChainPath, ko.KeyRef, ko.PassFunc)
sv, err = signerFromKeyRef(ctx, certPath, certChainPath, ko.KeyRef, ko.PassFunc, ko.TrustedMaterial)
default:
genKey = true
ui.Infof(ctx, "Generating ephemeral keys...")
Expand Down
10 changes: 5 additions & 5 deletions cmd/cosign/cli/sign/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func Test_signerFromKeyRefSuccess(t *testing.T) {
ctx := context.Background()
keyFile, certFile, chainFile, privKey, cert, chain := generateCertificateFiles(t, tmpDir, pass("foo"))

signer, err := signerFromKeyRef(ctx, certFile, chainFile, keyFile, pass("foo"))
signer, err := signerFromKeyRef(ctx, certFile, chainFile, keyFile, pass("foo"), nil)
if err != nil {
t.Fatalf("unexpected error generating signer: %v", err)
}
Expand Down Expand Up @@ -173,17 +173,17 @@ func Test_signerFromKeyRefFailure(t *testing.T) {
_, certFile2, chainFile2, _, _, _ := generateCertificateFiles(t, tmpDir2, pass("bar"))

// Public keys don't match
_, err := signerFromKeyRef(ctx, certFile2, chainFile2, keyFile, pass("foo"))
_, err := signerFromKeyRef(ctx, certFile2, chainFile2, keyFile, pass("foo"), nil)
if err == nil || err.Error() != "public key in certificate does not match the provided public key" {
t.Fatalf("expected mismatched keys error, got %v", err)
}
// Certificate chain cannot be verified
_, err = signerFromKeyRef(ctx, certFile, chainFile2, keyFile, pass("foo"))
_, err = signerFromKeyRef(ctx, certFile, chainFile2, keyFile, pass("foo"), nil)
if err == nil || !strings.Contains(err.Error(), "unable to validate certificate chain") {
t.Fatalf("expected chain verification error, got %v", err)
}
// Certificate chain specified without certificate
_, err = signerFromKeyRef(ctx, "", chainFile2, keyFile, pass("foo"))
_, err = signerFromKeyRef(ctx, "", chainFile2, keyFile, pass("foo"), nil)
if err == nil || !strings.Contains(err.Error(), "no leaf certificate found or provided while specifying chain") {
t.Fatalf("expected no leaf error, got %v", err)
}
Expand All @@ -203,7 +203,7 @@ func Test_signerFromKeyRefFailureEmptyChainFile(t *testing.T) {
t.Fatalf("failed to write chain file: %v", err)
}

_, err = signerFromKeyRef(ctx, certFile, tmpChainFile.Name(), keyFile, pass("foo"))
_, err = signerFromKeyRef(ctx, certFile, tmpChainFile.Name(), keyFile, pass("foo"), nil)
if err == nil || err.Error() != "no certificates in certificate chain" {
t.Fatalf("expected empty chain error, got %v", err)
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/cosign/cli/signblob.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/sigstore/cosign/v2/cmd/cosign/cli/generate"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
Expand Down Expand Up @@ -68,6 +69,7 @@ func SignBlob() *cobra.Command {
if err != nil {
return err
}
trustedMaterial, _ := cosign.TrustedRoot()
ko := options.KeyOpts{
KeyRef: o.Key,
PassFunc: generate.GetPass,
Expand All @@ -93,6 +95,7 @@ func SignBlob() *cobra.Command {
TSAServerURL: o.TSAServerURL,
RFC3161TimestampPath: o.RFC3161TimestampPath,
IssueCertificateForExistingKey: o.IssueCertificate,
TrustedMaterial: trustedMaterial,
}

for _, blob := range args {
Expand Down
42 changes: 29 additions & 13 deletions cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/sigstore/cosign/v2/internal/ui"
"github.com/sigstore/cosign/v2/pkg/blob"
"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/sigstore/cosign/v2/pkg/cosign/env"
"github.com/sigstore/cosign/v2/pkg/cosign/pivkey"
"github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key"
"github.com/sigstore/cosign/v2/pkg/oci"
Expand Down Expand Up @@ -128,6 +129,16 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
return fmt.Errorf("constructing client options: %w", err)
}

trustedMaterial, _ := cosign.TrustedRoot()
if options.NOf(c.CertChain, c.CARoots, c.CAIntermediates, c.TSACertChainPath) > 0 ||
env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) != "" ||
env.Getenv(env.VariableSigstoreRootFile) != "" ||
env.Getenv(env.VariableSigstoreRekorPublicKey) != "" ||
env.Getenv(env.VariableSigstoreTSACertificateFile) != "" {
// trusted_root.json was found, but a cert chain was explicitly provided, so don't overrule the user's intentions.
trustedMaterial = nil
}

co := &cosign.CheckOpts{
Annotations: c.Annotations.Annotations,
RegistryClientOpts: ociremoteOpts,
Expand All @@ -144,6 +155,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
IgnoreTlog: c.IgnoreTlog,
MaxWorkers: c.MaxWorkers,
ExperimentalOCI11: c.ExperimentalOCI11,
TrustedMaterial: trustedMaterial,
}
if c.CheckClaims {
co.ClaimVerifier = cosign.SimpleClaimVerifier
Expand All @@ -167,11 +179,13 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
}
co.RekorClient = rekorClient
}
// This performs an online fetch of the Rekor public keys, but this is needed
// for verifying tlog entries (both online and offline).
co.RekorPubKeys, err = cosign.GetRekorPubs(ctx)
if err != nil {
return fmt.Errorf("getting Rekor public keys: %w", err)
if co.TrustedMaterial == nil {
// This performs an online fetch of the Rekor public keys, but this is needed
// for verifying tlog entries (both online and offline).
co.RekorPubKeys, err = cosign.GetRekorPubs(ctx)
if err != nil {
return fmt.Errorf("getting Rekor public keys: %w", err)
}
}
}
if keylessVerification(c.KeyRef, c.Sk) {
Expand All @@ -184,7 +198,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
certRef := c.CertRef

// Ignore Signed Certificate Timestamp if the flag is set or a key is provided
if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) {
if co.TrustedMaterial == nil && shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) {
co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx)
if err != nil {
return fmt.Errorf("getting ctlog public keys: %w", err)
Expand Down Expand Up @@ -221,13 +235,15 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
switch {
case c.CertChain == "" && co.RootCerts == nil:
// If no certChain and no CARoots are passed, the Fulcio root certificate will be used
co.RootCerts, err = fulcio.GetRoots()
if err != nil {
return fmt.Errorf("getting Fulcio roots: %w", err)
}
co.IntermediateCerts, err = fulcio.GetIntermediates()
if err != nil {
return fmt.Errorf("getting Fulcio intermediates: %w", err)
if co.TrustedMaterial == nil {
co.RootCerts, err = fulcio.GetRoots()
if err != nil {
return fmt.Errorf("getting Fulcio roots: %w", err)
}
co.IntermediateCerts, err = fulcio.GetIntermediates()
if err != nil {
return fmt.Errorf("getting Fulcio intermediates: %w", err)
}
}
pubKey, err = cosign.ValidateAndUnpackCert(cert, co)
if err != nil {
Expand Down
Loading
Loading