diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 17fd63e8330..4c7b226ec5b 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -88,7 +88,7 @@ func (c *VerifyCommand) loadTSACertificates(ctx context.Context) (*cosign.TSACer if c.TSACertChainPath == "" && !c.UseSignedTimestamps { return nil, fmt.Errorf("TSA certificate chain path not provided and use-signed-timestamps not set") } - tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets) + tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath) if err != nil { return nil, fmt.Errorf("unable to load TSA certificates: %w", err) } diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index 93c27690455..9abf537cffe 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -76,7 +76,7 @@ func (c *VerifyAttestationCommand) loadTSACertificates(ctx context.Context) (*co if c.TSACertChainPath == "" && !c.UseSignedTimestamps { return nil, fmt.Errorf("TSA certificate chain path not provided and use-signed-timestamps not set") } - tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets) + tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath) if err != nil { return nil, fmt.Errorf("unable to load TSA certificates: %w", err) } diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index 79475c90d80..873b70f7a3f 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -73,7 +73,7 @@ func (c *VerifyBlobCmd) loadTSACertificates(ctx context.Context) (*cosign.TSACer if c.TSACertChainPath == "" && !c.UseSignedTimestamps { return nil, fmt.Errorf("either TSA certificate chain path must be provided or use-signed-timestamps must be set") } - tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets) + tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath) if err != nil { return nil, fmt.Errorf("unable to load TSA certificates: %w", err) } diff --git a/cmd/cosign/cli/verify/verify_blob_attestation.go b/cmd/cosign/cli/verify/verify_blob_attestation.go index 3f2c33cc63b..dac8e01cb6a 100644 --- a/cmd/cosign/cli/verify/verify_blob_attestation.go +++ b/cmd/cosign/cli/verify/verify_blob_attestation.go @@ -160,7 +160,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st } if c.TSACertChainPath != "" || c.UseSignedTimestamps { - tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets) + tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath) if err != nil { return fmt.Errorf("unable to load or get TSA certificates: %w", err) } diff --git a/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go b/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go index 3b44da88420..7f8acc2853c 100644 --- a/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go +++ b/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go @@ -16,15 +16,10 @@ package fulcioroots import ( - "bytes" "crypto/x509" - "fmt" - "os" "sync" - "github.com/sigstore/cosign/v2/pkg/cosign/env" - "github.com/sigstore/sigstore/pkg/cryptoutils" - "github.com/sigstore/sigstore/pkg/fulcioroots" + "github.com/sigstore/cosign/v2/pkg/cosign" ) var ( @@ -64,41 +59,5 @@ func ReInit() error { } func initRoots() (*x509.CertPool, *x509.CertPool, error) { - rootPool := x509.NewCertPool() - // intermediatePool should be nil if no intermediates are found - var intermediatePool *x509.CertPool - - rootEnv := env.Getenv(env.VariableSigstoreRootFile) - if rootEnv != "" { - raw, err := os.ReadFile(rootEnv) - if err != nil { - return nil, nil, fmt.Errorf("error reading root PEM file: %w", err) - } - certs, err := cryptoutils.UnmarshalCertificatesFromPEM(raw) - if err != nil { - return nil, nil, fmt.Errorf("error unmarshalling certificates: %w", err) - } - for _, cert := range certs { - // root certificates are self-signed - if bytes.Equal(cert.RawSubject, cert.RawIssuer) { - rootPool.AddCert(cert) - } else { - if intermediatePool == nil { - intermediatePool = x509.NewCertPool() - } - intermediatePool.AddCert(cert) - } - } - } else { - var err error - rootPool, err = fulcioroots.Get() - if err != nil { - return nil, nil, err - } - intermediatePool, err = fulcioroots.GetIntermediates() - if err != nil { - return nil, nil, err - } - } - return rootPool, intermediatePool, nil + return cosign.GetFulcioCerts() } diff --git a/pkg/cosign/ctlog.go b/pkg/cosign/ctlog.go index 9f2ebc3d5ec..402a488d94b 100644 --- a/pkg/cosign/ctlog.go +++ b/pkg/cosign/ctlog.go @@ -16,16 +16,18 @@ package cosign import ( "context" - "errors" "fmt" - "os" "github.com/sigstore/cosign/v2/pkg/cosign/env" - "github.com/sigstore/sigstore/pkg/tuf" + "github.com/sigstore/sigstore-go/pkg/root" + tufv1 "github.com/sigstore/sigstore/pkg/tuf" ) // This is the CT log public key target name -var ctPublicKeyStr = `ctfe.pub` +var ( + ctPublicKeyStr = `ctfe.pub` + ctPublicKeyDesc = `CT log public key` +) // GetCTLogPubs retrieves trusted CTLog public keys from the embedded or cached // TUF root. If expired, makes a network call to retrieve the updated targets. @@ -37,32 +39,37 @@ func GetCTLogPubs(ctx context.Context) (*TrustedTransparencyLogPubKeys, error) { altCTLogPub := env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) if altCTLogPub != "" { - raw, err := os.ReadFile(altCTLogPub) - if err != nil { - return nil, fmt.Errorf("error reading alternate CTLog public key file: %w", err) - } - if err := publicKeys.AddTransparencyLogPubKey(raw, tuf.Active); err != nil { - return nil, fmt.Errorf("AddCTLogPubKey: %w", err) + if err := addKeyFromFile(&publicKeys, altCTLogPub, ctPublicKeyDesc); err != nil { + return nil, fmt.Errorf("error adding key from environment variable: %w", err) } - } else { - tufClient, err := tuf.NewFromEnv(ctx) + return &publicKeys, nil + } + + if useNewTUFClient() { + opts, err := setTUFOpts() if err != nil { - return nil, err + return nil, fmt.Errorf("error setting TUF options: %w", err) } - targets, err := tufClient.GetTargetsByMeta(tuf.CTFE, []string{ctPublicKeyStr}) - if err != nil { - return nil, err + trustedRoot, _ := root.NewLiveTrustedRoot(opts) + if trustedRoot == nil { + if err = addKeyFromTUF(&publicKeys, opts, ctPublicKeyStr, ctPublicKeyDesc); err != nil { + return nil, fmt.Errorf("error adding CT log public key from TUF target: %w", err) + } + return &publicKeys, err } - for _, t := range targets { - if err := publicKeys.AddTransparencyLogPubKey(t.Target, t.Status); err != nil { - return nil, fmt.Errorf("AddCTLogPubKey: %w", err) + ctlogs := trustedRoot.CTLogs() + for _, ct := range ctlogs { + if err := publicKeys.AddTransparencyLogPubKey(ct.PublicKey); err != nil { + return nil, fmt.Errorf("error adding CT log public key from trusted root: %w", err) } } + return &publicKeys, err + } + if err := legacyAddKeyFromTUF(ctx, &publicKeys, tufv1.CTFE, []string{ctPublicKeyStr}, ctPublicKeyDesc); err != nil { + return nil, fmt.Errorf("error adding CT log public key from TUF (v1) target: %w", err) } - if len(publicKeys.Keys) == 0 { - return nil, errors.New("none of the CTLog public keys have been found") + return nil, fmt.Errorf("none of the CT log public keys have been found") } - return &publicKeys, nil } diff --git a/pkg/cosign/env/env.go b/pkg/cosign/env/env.go index 016687acb8e..536512a7e86 100644 --- a/pkg/cosign/env/env.go +++ b/pkg/cosign/env/env.go @@ -59,6 +59,10 @@ const ( VariableSigstoreRekorPublicKey Variable = "SIGSTORE_REKOR_PUBLIC_KEY" VariableSigstoreIDToken Variable = "SIGSTORE_ID_TOKEN" //nolint:gosec VariableSigstoreTSACertificateFile Variable = "SIGSTORE_TSA_CERTIFICATE_FILE" + VariableTUFMirror Variable = "SIGSTORE_TUF_MIRROR" + VariableTUFRootDir Variable = "SIGSTORE_TUF_ROOT" + VariableTUFRootJSON Variable = "SIGSTORE_TUF_ROOT_JSON" + VariableForceTrustedRoot Variable = "SIGSTORE_TUF_USE_TRUSTED_ROOT" // Other external environment variables VariableGitHubHost Variable = "GITHUB_HOST" @@ -145,6 +149,30 @@ var ( Sensitive: false, External: true, }, + VariableTUFMirror: { + Description: "URL of the TUF mirror. Use with TUF_ROOT_JSON to refresh TUF metadata during signing and verification commands. Setting this will cause cosign to attempt to use trusted_root.json if available and will ignore custom TUF metadata.", + Expects: "URL of the TUF mirror", + Sensitive: false, + External: true, + }, + VariableTUFRootDir: { + Description: "path to the TUF cache directory", + Expects: "path fo the TUF cache directory", + Sensitive: false, + External: true, + }, + VariableTUFRootJSON: { + Description: "path to the TUF root.json file used to initialize and update a local TUF repository. Use with TUF_MIRROR to refresh TUF metadata during signing and verification commands. Setting this will cause cosign to attempt to use trusted_root.json if available and will ignore custom TUF metadata.", + Expects: "path to root.json", + Sensitive: false, + External: true, + }, + VariableForceTrustedRoot: { + Description: "boolean, try to use trusted_root.json as the source of TUF metadata if available. If not available, cosign will fall back to fetching TUF targets by name. Setting this will cause cosign to ignore custom TUF metadata, which is deprecated.", + Expects: "set or unset", + Sensitive: false, + External: true, + }, VariableGitHubHost: { Description: "is URL of the GitHub Enterprise instance", Expects: "string with the URL of GitHub Enterprise instance", diff --git a/pkg/cosign/fulcio.go b/pkg/cosign/fulcio.go new file mode 100644 index 00000000000..0d22d05612b --- /dev/null +++ b/pkg/cosign/fulcio.go @@ -0,0 +1,161 @@ +// 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 cosign + +import ( + "bytes" + "crypto/x509" + "fmt" + "os" + + "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/tuf" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/fulcioroots" +) + +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` +) + +func GetFulcioCerts() (*x509.CertPool, *x509.CertPool, error) { + rootEnv := env.Getenv(env.VariableSigstoreRootFile) + if rootEnv != "" { + return getFulcioCertsFromFile(rootEnv) + } + + if useNewTUFClient() { + opts, err := setTUFOpts() + if err != nil { + return nil, nil, fmt.Errorf("error setting TUF options: %w", err) + } + trustedRoot, _ := root.NewLiveTrustedRoot(opts) + if trustedRoot == nil { + rootPool, intermediates, err := getFulcioCertsFromTUF(opts) + if err != nil { + return nil, nil, fmt.Errorf("error getting Fulcio certs from TUF targets: %w", err) + } + return rootPool, intermediates, nil + } + rootPool := x509.NewCertPool() + var intermediatePool *x509.CertPool + cas := trustedRoot.FulcioCertificateAuthorities() + if len(cas) < 1 { + return nil, nil, fmt.Errorf("could not find Fulcio certificate authorities in trusted root") + } + for _, ca := range cas { + rootPool.AddCert(ca.Root) + for _, i := range ca.Intermediates { + if intermediatePool == nil { + intermediatePool = x509.NewCertPool() + } + intermediatePool.AddCert(i) + } + } + return rootPool, intermediatePool, nil + } + + roots, intermediates, err := legacyGetFulcioCertsFromTUF() + if err != nil { + return nil, nil, fmt.Errorf("error getting Fulcio certs from TUF (v1) targets: %w", err) + } + return roots, intermediates, nil +} + +func getFulcioCertsFromFile(path string) (*x509.CertPool, *x509.CertPool, error) { + rootPool := x509.NewCertPool() + // intermediatePool should be nil if no intermediates are found + var intermediatePool *x509.CertPool + raw, err := os.ReadFile(path) + if err != nil { + return nil, nil, fmt.Errorf("error reading root PEM file: %w", err) + } + certs, err := cryptoutils.UnmarshalCertificatesFromPEM(raw) + if err != nil { + return nil, nil, fmt.Errorf("error unmarshalling certificates: %w", err) + } + for _, cert := range certs { + // root certificates are self-signed + if bytes.Equal(cert.RawSubject, cert.RawIssuer) { + rootPool.AddCert(cert) + } else { + if intermediatePool == nil { + intermediatePool = x509.NewCertPool() + } + intermediatePool.AddCert(cert) + } + } + return rootPool, intermediatePool, nil +} + +func getFulcioCertsFromTUF(opts *tuf.Options) (*x509.CertPool, *x509.CertPool, error) { + tufClient, err := tuf.New(opts) + if err != nil { + return nil, nil, fmt.Errorf("error creating TUF client: %w", err) + } + rootPool := x509.NewCertPool() + fulcioCertBytes, _ := tufClient.GetTarget(fulcioTargetStr) + fulcioV1CertBytes, _ := tufClient.GetTarget(fulcioV1TargetStr) + if len(fulcioCertBytes) > 0 { + fulcioCert, err := cryptoutils.UnmarshalCertificatesFromPEM(fulcioCertBytes) + if err != nil { + return nil, nil, fmt.Errorf("error unmarshalling Fulcio cert: %w", err) + } + for _, c := range fulcioCert { + rootPool.AddCert(c) + } + } + if len(fulcioV1CertBytes) > 0 { + fulcioV1Cert, err := cryptoutils.UnmarshalCertificatesFromPEM(fulcioV1CertBytes) + if err != nil { + return nil, nil, fmt.Errorf("error unmarshalling Fulcio v1 cert: %w", err) + } + for _, c := range fulcioV1Cert { + rootPool.AddCert(c) + } + } + + var intermediatePool *x509.CertPool + fulcioIntermediateBytes, _ := tufClient.GetTarget(fulcioV1IntermediateTargetStr) + if len(fulcioIntermediateBytes) == 0 { + fulcioIntermediate, err := cryptoutils.UnmarshalCertificatesFromPEM(fulcioIntermediateBytes) + if err != nil { + return nil, nil, fmt.Errorf("error unmarshalling Fulcio intermediate cert: %w", err) + } + intermediatePool = x509.NewCertPool() + for _, c := range fulcioIntermediate { + intermediatePool.AddCert(c) + } + } + return rootPool, intermediatePool, nil +} + +func legacyGetFulcioCertsFromTUF() (*x509.CertPool, *x509.CertPool, error) { + roots, err := fulcioroots.Get() + if err != nil { + return nil, nil, fmt.Errorf("error getting Fulcio roots: %w", err) + } + intermediates, err := fulcioroots.GetIntermediates() + if err != nil { + return nil, nil, fmt.Errorf("error getting Fulcio intermediates: %w", err) + } + return roots, intermediates, err +} diff --git a/pkg/cosign/tlog.go b/pkg/cosign/tlog.go index 83d6f61f179..0a43d9e6660 100644 --- a/pkg/cosign/tlog.go +++ b/pkg/cosign/tlog.go @@ -26,7 +26,6 @@ import ( "errors" "fmt" "hash" - "os" "strconv" "strings" @@ -47,18 +46,21 @@ import ( hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" "github.com/sigstore/rekor/pkg/types/intoto" intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" + "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore/pkg/cryptoutils" - "github.com/sigstore/sigstore/pkg/tuf" + tufv1 "github.com/sigstore/sigstore/pkg/tuf" ) // This is the rekor transparency log public key target name -var rekorTargetStr = `rekor.pub` +var ( + rekorTargetStr = `rekor.pub` + rekorDesc = `rekor public key` +) // TransparencyLogPubKey contains the ECDSA verification key and the current status // of the key according to TUF metadata, whether it's active or expired. type TransparencyLogPubKey struct { PubKey crypto.PublicKey - Status tuf.StatusKind } // This is a map of TransparencyLog public keys indexed by log ID that's used @@ -124,33 +126,37 @@ func GetRekorPubs(ctx context.Context) (*TrustedTransparencyLogPubKeys, error) { altRekorPub := env.Getenv(env.VariableSigstoreRekorPublicKey) if altRekorPub != "" { - raw, err := os.ReadFile(altRekorPub) - if err != nil { - return nil, fmt.Errorf("error reading alternate Rekor public key file: %w", err) - } - if err := publicKeys.AddTransparencyLogPubKey(raw, tuf.Active); err != nil { - return nil, fmt.Errorf("AddRekorPubKey: %w", err) + if err := addKeyFromFile(&publicKeys, altRekorPub, rekorDesc); err != nil { + return nil, fmt.Errorf("error adding key from environment variable: %w", err) } - } else { - tufClient, err := tuf.NewFromEnv(ctx) + return &publicKeys, nil + } + + if useNewTUFClient() { + opts, err := setTUFOpts() if err != nil { - return nil, err + return nil, fmt.Errorf("error setting TUF options: %w", err) } - targets, err := tufClient.GetTargetsByMeta(tuf.Rekor, []string{rekorTargetStr}) - if err != nil { - return nil, err + trustedRoot, _ := root.NewLiveTrustedRoot(opts) + if trustedRoot == nil { + if err := addKeyFromTUF(&publicKeys, opts, rekorTargetStr, rekorDesc); err != nil { + return nil, fmt.Errorf("error adding Rekor key from TUF target: %w", err) + } + return &publicKeys, err } - for _, t := range targets { - if err := publicKeys.AddTransparencyLogPubKey(t.Target, t.Status); err != nil { - return nil, fmt.Errorf("AddRekorPubKey: %w", err) + tlogs := trustedRoot.RekorLogs() + for _, t := range tlogs { + if err := publicKeys.AddTransparencyLogPubKey(t.PublicKey); err != nil { + return nil, fmt.Errorf("error adding Rekor public key from trusted root: %w", err) } } } - + if err := legacyAddKeyFromTUF(ctx, &publicKeys, tufv1.Rekor, []string{rekorTargetStr}, rekorDesc); err != nil { + return nil, fmt.Errorf("error adding Rekor key from TUF (v1) target: %w", err) + } if len(publicKeys.Keys) == 0 { - return nil, errors.New("none of the Rekor public keys have been found") + return nil, fmt.Errorf("no Rekor public keys found") } - return &publicKeys, nil } @@ -163,7 +169,7 @@ func rekorPubsFromClient(rekorClient *client.Rekor) (*TrustedTransparencyLogPubK if err != nil { return nil, fmt.Errorf("unable to fetch rekor public key from rekor: %w", err) } - if err := publicKeys.AddTransparencyLogPubKey([]byte(pubOK.Payload), tuf.Active); err != nil { + if err := publicKeys.AddTransparencyLogPubKey([]byte(pubOK.Payload)); err != nil { return nil, fmt.Errorf("constructRekorPubKey: %w", err) } return &publicKeys, nil @@ -219,7 +225,7 @@ func doUpload(ctx context.Context, rekorClient *client.Rekor, pe models.Proposed if err != nil { return nil, err } - return e, VerifyTLogEntryOffline(ctx, e, rekorPubsFromAPI) + return e, VerifyTLogEntryOffline(e, rekorPubsFromAPI) } return nil, err } @@ -443,7 +449,7 @@ func FindTlogEntry(ctx context.Context, rekorClient *client.Rekor, // VerifyTLogEntryOffline verifies a TLog entry against a map of trusted rekorPubKeys indexed // by log id. -func VerifyTLogEntryOffline(ctx context.Context, e *models.LogEntryAnon, rekorPubKeys *TrustedTransparencyLogPubKeys) error { +func VerifyTLogEntryOffline(e *models.LogEntryAnon, rekorPubKeys *TrustedTransparencyLogPubKeys) error { if e.Verification == nil || e.Verification.InclusionProof == nil { return errors.New("inclusion proof not provided") } @@ -493,9 +499,6 @@ func VerifyTLogEntryOffline(ctx context.Context, e *models.LogEntryAnon, rekorPu if err != nil { return fmt.Errorf("verifying signedEntryTimestamp: %w", err) } - if pubKey.Status != tuf.Active { - ui.Infof(ctx, "Successfully verified Rekor entry using an expired verification key") - } return nil } @@ -504,16 +507,22 @@ func NewTrustedTransparencyLogPubKeys() TrustedTransparencyLogPubKeys { } // constructRekorPubkey returns a log ID and RekorPubKey from a given -// byte-array representing the PEM-encoded Rekor key and a status. -func (t *TrustedTransparencyLogPubKeys) AddTransparencyLogPubKey(pemBytes []byte, status tuf.StatusKind) error { - pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(pemBytes) - if err != nil { - return err +// byte-array representing the PEM-encoded Rekor key. +func (t *TrustedTransparencyLogPubKeys) AddTransparencyLogPubKey(pem any) error { + var pubKey crypto.PublicKey + var err error + if pemBytes, ok := pem.([]byte); ok { + pubKey, err = cryptoutils.UnmarshalPEMToPublicKey(pemBytes) + if err != nil { + return err + } + } else { + pubKey = pem.(crypto.PublicKey) } keyID, err := GetTransparencyLogID(pubKey) if err != nil { return err } - t.Keys[keyID] = TransparencyLogPubKey{PubKey: pubKey, Status: status} + t.Keys[keyID] = TransparencyLogPubKey{PubKey: pubKey} return nil } diff --git a/pkg/cosign/tlog_test.go b/pkg/cosign/tlog_test.go index 55bd76bb9f6..7e90c347e48 100644 --- a/pkg/cosign/tlog_test.go +++ b/pkg/cosign/tlog_test.go @@ -28,7 +28,6 @@ import ( ttestdata "github.com/google/certificate-transparency-go/trillian/testdata" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore/pkg/cryptoutils" - "github.com/sigstore/sigstore/pkg/tuf" ) var ( @@ -169,11 +168,11 @@ func TestVerifyTLogEntryOfflineFailsWithInvalidPublicKey(t *testing.T) { t.Fatalf("Unable to marshal RSA test key: %v", err) } rekorPubKeys := NewTrustedTransparencyLogPubKeys() - if err = rekorPubKeys.AddTransparencyLogPubKey(rsaPEM, tuf.Active); err != nil { + if err = rekorPubKeys.AddTransparencyLogPubKey(rsaPEM); err != nil { t.Fatalf("failed to add RSA key to transparency log public keys: %v", err) } - err = VerifyTLogEntryOffline(context.Background(), &models.LogEntryAnon{Verification: &models.LogEntryAnonVerification{InclusionProof: &models.InclusionProof{}}}, &rekorPubKeys) + err = VerifyTLogEntryOffline(&models.LogEntryAnon{Verification: &models.LogEntryAnonVerification{InclusionProof: &models.InclusionProof{}}}, &rekorPubKeys) if err == nil { t.Fatal("Wanted error got none") } diff --git a/pkg/cosign/tsa.go b/pkg/cosign/tsa.go index 9d1c17a3339..780ff5f9e1d 100644 --- a/pkg/cosign/tsa.go +++ b/pkg/cosign/tsa.go @@ -22,8 +22,10 @@ import ( "os" "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/tuf" "github.com/sigstore/sigstore/pkg/cryptoutils" - "github.com/sigstore/sigstore/pkg/tuf" + tufv1 "github.com/sigstore/sigstore/pkg/tuf" ) const ( @@ -38,95 +40,73 @@ type TSACertificates struct { RootCert []*x509.Certificate } -type GetTargetStub func(ctx context.Context, usage tuf.UsageKind, names []string) ([]byte, error) - -func GetTufTargets(ctx context.Context, usage tuf.UsageKind, names []string) ([]byte, error) { - tufClient, err := tuf.NewFromEnv(ctx) - if err != nil { - return nil, fmt.Errorf("error creating TUF client: %w", err) - } - targets, err := tufClient.GetTargetsByMeta(usage, names) - if err != nil { - return nil, fmt.Errorf("error fetching targets by metadata with usage %v: %w", usage, err) - } - - var buffer bytes.Buffer - for _, target := range targets { - buffer.Write(target.Target) - buffer.WriteByte('\n') - } - return buffer.Bytes(), nil -} - -func isTufTargetExist(ctx context.Context, name string) (bool, error) { - tufClient, err := tuf.NewFromEnv(ctx) - if err != nil { - return false, fmt.Errorf("error creating TUF client: %w", err) - } - _, err = tufClient.GetTarget(name) - if err != nil { - return false, nil - } - return true, nil -} - // GetTSACerts retrieves trusted TSA certificates from the embedded or cached // TUF root. If expired, makes a network call to retrieve the updated targets. // By default, the certificates come from TUF, but you can override this for test // purposes by using an env variable `SIGSTORE_TSA_CERTIFICATE_FILE` or a file path // specified in `TSACertChainPath`. If using an alternate, the file should be in PEM format. -func GetTSACerts(ctx context.Context, certChainPath string, fn GetTargetStub) (*TSACertificates, error) { +func GetTSACerts(ctx context.Context, certChainPath string) (*TSACertificates, error) { altTSACert := env.Getenv(env.VariableSigstoreTSACertificateFile) var raw []byte var err error - var exists bool switch { case altTSACert != "": raw, err = os.ReadFile(altTSACert) case certChainPath != "": raw, err = os.ReadFile(certChainPath) - default: - certNames := []string{tsaLeafCertStr, tsaRootCertStr} - for i := 0; ; i++ { - intermediateCertStr := fmt.Sprintf(tsaIntermediateCertStrPattern, i) - exists, err = isTufTargetExist(ctx, intermediateCertStr) - if err != nil { - return nil, fmt.Errorf("error fetching TSA certificates: %w", err) - } - if !exists { - break - } - certNames = append(certNames, intermediateCertStr) - } - raw, err = fn(ctx, tuf.TSA, certNames) - if err != nil { - return nil, fmt.Errorf("error fetching TSA certificates: %w", err) - } } - if err != nil { return nil, fmt.Errorf("error reading TSA certificate file: %w", err) } + if len(raw) > 0 { + leaves, intermediates, roots, err := splitPEMCertificateChain(raw) + if err != nil { + return nil, fmt.Errorf("error splitting TSA certificates: %w", err) + } + if len(leaves) != 1 { + return nil, fmt.Errorf("TSA certificate chain must contain exactly one leaf certificate") + } - leaves, intermediates, roots, err := splitPEMCertificateChain(raw) - if err != nil { - return nil, fmt.Errorf("error splitting TSA certificates: %w", err) + if len(roots) == 0 { + return nil, fmt.Errorf("TSA certificate chain must contain at least one root certificate") + } + return &TSACertificates{ + LeafCert: leaves[0], + IntermediateCerts: intermediates, + RootCert: roots, + }, nil } - if len(leaves) != 1 { - return nil, fmt.Errorf("TSA certificate chain must contain exactly one leaf certificate") + if useNewTUFClient() { + opts, err := setTUFOpts() + if err != nil { + return nil, fmt.Errorf("error setting TUF options: %w", err) + } + trustedRoot, _ := root.NewLiveTrustedRoot(opts) + if trustedRoot == nil { + certs, err := getTSAKeysFromTUF(opts) + if err != nil { + return nil, fmt.Errorf("error adding TSA certs from TUF targets: %w", err) + } + return certs, nil + } + tsas := trustedRoot.TimestampingAuthorities() + if len(tsas) < 1 { + return nil, fmt.Errorf("could not find timestamp authorities in trusted root") + } + return &TSACertificates{ + LeafCert: tsas[0].Leaf, + IntermediateCerts: tsas[0].Intermediates, + RootCert: []*x509.Certificate{tsas[0].Root}, + }, nil } - if len(roots) == 0 { - return nil, fmt.Errorf("TSA certificate chain must contain at least one root certificate") + certs, err := legacyGetTSAKeysFromTUF(ctx) + if err != nil { + return nil, fmt.Errorf("error adding TSA certs from TUF (v1) targets: %w", err) } - - return &TSACertificates{ - LeafCert: leaves[0], - IntermediateCerts: intermediates, - RootCert: roots, - }, nil + return certs, nil } // splitPEMCertificateChain returns a list of leaf (non-CA) certificates, a certificate pool for @@ -152,3 +132,83 @@ func splitPEMCertificateChain(pem []byte) (leaves, intermediates, roots []*x509. return leaves, intermediates, roots, nil } + +func getTSAKeysFromTUF(opts *tuf.Options) (*TSACertificates, error) { + tufClient, err := tuf.New(opts) + if err != nil { + return nil, fmt.Errorf("error creating TUF client: %w", err) + } + leafCertBytes, err := tufClient.GetTarget(tsaLeafCertStr) + if err != nil { + return nil, fmt.Errorf("error fetching TSA leaf cert: %w", err) + } + rootCertBytes, err := tufClient.GetTarget(tsaRootCertStr) + if err != nil { + return nil, fmt.Errorf("error fetching TSA root CA cert: %w", err) + } + var intermediateChainBytes []byte + for i := 0; ; i++ { + intermediateCertStr := fmt.Sprintf(tsaIntermediateCertStrPattern, i) + intermediateCertBytes, _ := tufClient.GetTarget(intermediateCertStr) + if len(intermediateCertBytes) == 0 { + break + } + intermediateChainBytes = append(intermediateChainBytes, intermediateCertBytes...) + } + leafCert, err := cryptoutils.UnmarshalCertificatesFromPEM(leafCertBytes) + if err != nil { + return nil, fmt.Errorf("error unmarshalling TSA leaf cert: %w", err) + } + rootCert, err := cryptoutils.UnmarshalCertificatesFromPEM(rootCertBytes) + if err != nil { + return nil, fmt.Errorf("error unmarshalling TSA root CA cert: %w", err) + } + var intermediates []*x509.Certificate + if len(intermediateChainBytes) > 0 { + intermediates, err = cryptoutils.UnmarshalCertificatesFromPEM(intermediateChainBytes) + if err != nil { + return nil, fmt.Errorf("error unmarshalling intermediate certs: %w", err) + } + } + return &TSACertificates{ + LeafCert: leafCert[0], + IntermediateCerts: intermediates, + RootCert: rootCert, + }, nil +} + +func legacyGetTSAKeysFromTUF(ctx context.Context) (*TSACertificates, error) { + tufClient, err := tufv1.NewFromEnv(ctx) + if err != nil { + return nil, fmt.Errorf("error creating legacy TUF client: %w", err) + } + targets, err := tufClient.GetTargetsByMeta(tufv1.TSA, []string{tsaLeafCertStr, tsaRootCertStr}) + if err != nil { + return nil, fmt.Errorf("error fetching TSA certs: %w", err) + } + var buffer bytes.Buffer + for _, t := range targets { + buffer.Write(t.Target) + buffer.WriteByte('\n') + } + for i := 0; ; i++ { + target, err := tufClient.GetTarget(fmt.Sprintf(tsaIntermediateCertStrPattern, i)) + if err != nil { + break + } + buffer.Write(target) + buffer.WriteByte('\n') + } + if buffer.Len() == 0 { + return nil, fmt.Errorf("could not find TSA keys") + } + leaves, intermediates, roots, err := splitPEMCertificateChain(buffer.Bytes()) + if err != nil { + return nil, fmt.Errorf("error unmarshalling TSA certs: %w", err) + } + return &TSACertificates{ + LeafCert: leaves[0], + IntermediateCerts: intermediates, + RootCert: roots, + }, nil +} diff --git a/pkg/cosign/tsa_test.go b/pkg/cosign/tsa_test.go index 9487d1d25fa..4789d37cce5 100644 --- a/pkg/cosign/tsa_test.go +++ b/pkg/cosign/tsa_test.go @@ -68,7 +68,7 @@ func TestGetTSACertsFromEnv(t *testing.T) { os.Setenv("SIGSTORE_TSA_CERTIFICATE_FILE", tempFile.Name()) defer os.Unsetenv("SIGSTORE_TSA_CERTIFICATE_FILE") - tsaCerts, err := GetTSACerts(context.Background(), tempFile.Name(), GetTufTargets) + tsaCerts, err := GetTSACerts(context.Background(), tempFile.Name()) if err != nil { t.Fatalf("Failed to get TSA certs from env: %v", err) } @@ -86,7 +86,7 @@ func TestGetTSACertsFromPath(t *testing.T) { _, err = tempFile.Write([]byte(testLeafCert + "\n" + testRootCert)) require.NoError(t, err) - tsaCerts, err := GetTSACerts(context.Background(), tempFile.Name(), GetTufTargets) + tsaCerts, err := GetTSACerts(context.Background(), tempFile.Name()) if err != nil { t.Fatalf("Failed to get TSA certs from path: %v", err) } @@ -108,7 +108,7 @@ func TestGetTSACertsFromTUF(t *testing.T) { _, err = tempFile.Write([]byte(testLeafCert + "\n" + testRootCert)) require.NoError(t, err) - tsaCerts, err := GetTSACerts(context.Background(), tempFile.Name(), GetTufTargets) + tsaCerts, err := GetTSACerts(context.Background(), tempFile.Name()) if err != nil { t.Fatalf("Failed to get TSA certs from TUF: %v", err) } diff --git a/pkg/cosign/tuf.go b/pkg/cosign/tuf.go new file mode 100644 index 00000000000..3b2fe702bb7 --- /dev/null +++ b/pkg/cosign/tuf.go @@ -0,0 +1,137 @@ +// 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 cosign + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/sigstore-go/pkg/tuf" + tufv1 "github.com/sigstore/sigstore/pkg/tuf" +) + +func addKeyFromFile(publicKeys *TrustedTransparencyLogPubKeys, name, description string) error { + raw, err := os.ReadFile(name) + if err != nil { + return fmt.Errorf("error reading alternate %s file: %w", description, err) + } + if err := publicKeys.AddTransparencyLogPubKey(raw); err != nil { + return fmt.Errorf("error adding %s: %w", description, err) + } + return nil +} + +func setTUFOpts() (*tuf.Options, error) { + opts := tuf.DefaultOptions() + if tufCacheDir := env.Getenv(env.VariableTUFRootDir); tufCacheDir != "" { //nolint:forbidigo + opts.CachePath = tufCacheDir + } + err := setTUFMirror(opts) + if err != nil { + return nil, fmt.Errorf("error setting TUF mirror: %w", err) + } + err = setTUFRootJSON(opts) + if err != nil { + return nil, fmt.Errorf("error setting root: %w", err) + } + return opts, nil +} + +func addKeyFromTUF(publicKeys *TrustedTransparencyLogPubKeys, opts *tuf.Options, name, description string) error { + tufClient, err := tuf.New(opts) + if err != nil { + return fmt.Errorf("error creating TUF client: %w", err) + } + pubKeyBytes, err := tufClient.GetTarget(name) + if err != nil { + return fmt.Errorf("error fetching %s: %w", description, err) + } + if err := publicKeys.AddTransparencyLogPubKey(pubKeyBytes); err != nil { + return fmt.Errorf("error adding %s: %w", description, err) + } + return nil +} + +func legacyAddKeyFromTUF(ctx context.Context, publicKeys *TrustedTransparencyLogPubKeys, kind tufv1.UsageKind, names []string, description string) error { + tufClient, err := tufv1.NewFromEnv(ctx) + if err != nil { + return fmt.Errorf("error creating legacy TUF client: %w", err) + } + targets, err := tufClient.GetTargetsByMeta(kind, names) + if err != nil { + return fmt.Errorf("error fetching %s: %w", description, err) + } + for _, t := range targets { + if err := publicKeys.AddTransparencyLogPubKey(t.Target); err != nil { + return fmt.Errorf("error adding %s: %w", description, err) + } + } + return nil +} + +func setTUFMirror(opts *tuf.Options) error { + if tufMirror := env.Getenv(env.VariableTUFMirror); tufMirror != "" { //nolint:forbidigo + opts.RepositoryBaseURL = tufMirror + return nil + } + // try using the mirror set by `cosign initialize` + cachedRemote := filepath.Join(opts.CachePath, "remote.json") + remoteBytes, err := os.ReadFile(cachedRemote) + if os.IsNotExist(err) { + // could not find remote.json, use default mirror + return nil + } + if err != nil { + return fmt.Errorf("error reading remote.json: %w", err) + } + remote := make(map[string]string) + err = json.Unmarshal(remoteBytes, &remote) + if err != nil { + return fmt.Errorf("error unmarshalling remote.json: %w", err) + } + opts.RepositoryBaseURL = remote["mirror"] + return nil +} + +func setTUFRootJSON(opts *tuf.Options) error { + // TUF root set by TUF_ROOT_JSON + if tufRootJSON := env.Getenv(env.VariableTUFRootJSON); tufRootJSON != "" { //nolint:forbidigo + rootJSONBytes, err := os.ReadFile(tufRootJSON) + if err != nil { + return fmt.Errorf("error reading root.json given by TUF_ROOT_JSON") + } + opts.Root = rootJSONBytes + return nil + } + // Look for cached root.json + cachedRootJSON := filepath.Join(opts.CachePath, tuf.URLToPath(opts.RepositoryBaseURL), "root.json") + if _, err := os.Stat(cachedRootJSON); !os.IsNotExist(err) { + rootJSONBytes, err := os.ReadFile(cachedRootJSON) + if err != nil { + return fmt.Errorf("error reading cached root.json") + } + opts.Root = rootJSONBytes + } + // Use defaults + return nil +} + +func useNewTUFClient() bool { + return env.Getenv(env.VariableTUFMirror) != "" || env.Getenv(env.VariableTUFRootJSON) != "" || env.Getenv(env.VariableForceTrustedRoot) != "" +} diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 3ab5d76026a..09d9c433126 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -69,7 +69,6 @@ import ( "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" "github.com/sigstore/sigstore/pkg/signature/options" - "github.com/sigstore/sigstore/pkg/tuf" tsaverification "github.com/sigstore/timestamp-authority/pkg/verification" ) @@ -457,7 +456,7 @@ func tlogValidateEntry(ctx context.Context, client *client.Rekor, rekorPubKeys * entryVerificationErrs := make([]string, 0) for _, e := range tlogEntries { entry := e - if err := VerifyTLogEntryOffline(ctx, &entry, rekorPubKeys); err != nil { + if err := VerifyTLogEntryOffline(&entry, rekorPubKeys); err != nil { entryVerificationErrs = append(entryVerificationErrs, err.Error()) continue } @@ -1088,9 +1087,6 @@ func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) { if err != nil { return false, err } - if pubKey.Status != tuf.Active { - fmt.Fprintf(os.Stderr, "**Info** Successfully verified Rekor entry using an expired verification key\n") - } payload, err := sig.Payload() if err != nil { diff --git a/pkg/cosign/verify_sct.go b/pkg/cosign/verify_sct.go index 1b904c2c4fd..d3164ee0d4a 100644 --- a/pkg/cosign/verify_sct.go +++ b/pkg/cosign/verify_sct.go @@ -21,7 +21,6 @@ import ( "encoding/json" "errors" "fmt" - "os" ct "github.com/google/certificate-transparency-go" ctx509 "github.com/google/certificate-transparency-go/x509" @@ -29,7 +28,6 @@ import ( "github.com/sigstore/cosign/v2/pkg/cosign/fulcioverifier/ctutil" "github.com/sigstore/sigstore/pkg/cryptoutils" - "github.com/sigstore/sigstore/pkg/tuf" ) // ContainsSCT checks if the certificate contains embedded SCTs. cert can either be @@ -110,9 +108,6 @@ func VerifySCT(_ context.Context, certPEM, chainPEM, rawSCT []byte, pubKeys *Tru if err != nil { return fmt.Errorf("error verifying embedded SCT: %w", err) } - if pubKeyMetadata.Status != tuf.Active { - fmt.Fprintf(os.Stderr, "**Info** Successfully verified embedded SCT using an expired verification key\n") - } } return nil } @@ -134,9 +129,6 @@ func VerifySCT(_ context.Context, certPEM, chainPEM, rawSCT []byte, pubKeys *Tru if err != nil { return fmt.Errorf("error verifying SCT") } - if pubKeyMetadata.Status != tuf.Active { - fmt.Fprintf(os.Stderr, "**Info** Successfully verified SCT using an expired verification key\n") - } return nil } diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index 30586d4a759..deeddc18a36 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -60,7 +60,6 @@ import ( "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/options" - "github.com/sigstore/sigstore/pkg/tuf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/transparency-dev/merkle/rfc6962" @@ -306,7 +305,7 @@ func TestVerifyImageSignatureWithNoChain(t *testing.T) { rekorBundle := CreateTestBundle(ctx, t, sv, leaf) pemBytes, _ := cryptoutils.MarshalPublicKeyToPEM(sv.Public()) rekorPubKeys := NewTrustedTransparencyLogPubKeys() - rekorPubKeys.AddTransparencyLogPubKey(pemBytes, tuf.Active) + rekorPubKeys.AddTransparencyLogPubKey(pemBytes) opts := []static.Option{static.WithCertChain(pemLeaf, []byte{}), static.WithBundle(rekorBundle)} ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), opts...) @@ -350,7 +349,7 @@ func TestVerifyImageSignatureWithInvalidPublicKeyType(t *testing.T) { pemBytes, _ := cryptoutils.MarshalPublicKeyToPEM(sv.Public()) rekorPubKeys := NewTrustedTransparencyLogPubKeys() // Add one valid key here. - rekorPubKeys.AddTransparencyLogPubKey(pemBytes, tuf.Active) + rekorPubKeys.AddTransparencyLogPubKey(pemBytes) opts := []static.Option{static.WithCertChain(pemLeaf, []byte{}), static.WithBundle(rekorBundle)} ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), opts...) @@ -371,7 +370,7 @@ func TestVerifyImageSignatureWithInvalidPublicKeyType(t *testing.T) { if err != nil { t.Fatalf("Unable to marshal RSA test key: %v", err) } - if err = rekorPubKeys.AddTransparencyLogPubKey(rsaPEM, tuf.Active); err != nil { + if err = rekorPubKeys.AddTransparencyLogPubKey(rsaPEM); err != nil { t.Fatalf("failed to add RSA key to transparency log public keys: %v", err) } verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, @@ -564,7 +563,6 @@ func TestImageSignatureVerificationWithRekor(t *testing.T) { Keys: map[string]TransparencyLogPubKey{ logID: { PubKey: rekorPublicKey, - Status: tuf.Active, }, }, } @@ -575,7 +573,6 @@ func TestImageSignatureVerificationWithRekor(t *testing.T) { Keys: map[string]TransparencyLogPubKey{ logID: { PubKey: nonMatchingPublicKey, - Status: tuf.Active, }, }, }