diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 17fd63e83303..4c7b226ec5b8 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 93c27690455e..9abf537cffea 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 79475c90d80b..873b70f7a3f3 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 3f2c33cc63bf..dac8e01cb6a0 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 3b44da884201..7f8acc2853c9 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 9f2ebc3d5ec2..73dd01a096e0 100644 --- a/pkg/cosign/ctlog.go +++ b/pkg/cosign/ctlog.go @@ -16,16 +16,17 @@ package cosign import ( "context" - "errors" "fmt" - "os" "github.com/sigstore/cosign/v2/pkg/cosign/env" - "github.com/sigstore/sigstore/pkg/tuf" + 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 +38,27 @@ 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) - } - } else { - tufClient, err := tuf.NewFromEnv(ctx) + err := addKeyFromFile(&publicKeys, altCTLogPub, ctPublicKeyDesc) if err != nil { - return nil, err - } - targets, err := tufClient.GetTargetsByMeta(tuf.CTFE, []string{ctPublicKeyStr}) - if err != nil { - return nil, err - } - for _, t := range targets { - if err := publicKeys.AddTransparencyLogPubKey(t.Target, t.Status); err != nil { - return nil, fmt.Errorf("AddCTLogPubKey: %w", err) - } + return nil, fmt.Errorf("error adding key from environment variable: %w", err) } + return &publicKeys, nil } - if len(publicKeys.Keys) == 0 { - return nil, errors.New("none of the CTLog public keys have been found") + opts, err := setTUFOpts() + if err != nil { + return nil, err } + _ = addKeyFromTUF(&publicKeys, opts, ctPublicKeyStr, ctPublicKeyDesc) + if len(publicKeys.Keys) == 0 { + err = legacyAddKeyFromTUF(ctx, &publicKeys, tufv1.CTFE, []string{ctPublicKeyStr}, ctPublicKeyDesc) + if err != nil { + return nil, fmt.Errorf("error adding ct log public key: %w", err) + } + if len(publicKeys.Keys) == 0 { + return nil, fmt.Errorf("none of the CTLog public keys have been found") + } + } return &publicKeys, nil } diff --git a/pkg/cosign/fulcio.go b/pkg/cosign/fulcio.go new file mode 100644 index 000000000000..c7ef93df18e7 --- /dev/null +++ b/pkg/cosign/fulcio.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 ( + "bytes" + "crypto/x509" + "fmt" + "os" + + "github.com/sigstore/cosign/v2/pkg/cosign/env" + "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) + } + + opts, err := setTUFOpts() + if err != nil { + return nil, nil, err + } + + roots, intermediates, _ := getFulcioCertsFromTUF(opts) + if roots == nil { + roots, intermediates, err = legacyGetFulcioCertsFromTUF() + if err != nil { + return nil, nil, fmt.Errorf("error getting fulcio certs: %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 83d6f61f1793..6793dd2c4943 100644 --- a/pkg/cosign/tlog.go +++ b/pkg/cosign/tlog.go @@ -26,7 +26,6 @@ import ( "errors" "fmt" "hash" - "os" "strconv" "strings" @@ -48,17 +47,19 @@ import ( "github.com/sigstore/rekor/pkg/types/intoto" intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" "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 +125,28 @@ func GetRekorPubs(ctx context.Context) (*TrustedTransparencyLogPubKeys, error) { altRekorPub := env.Getenv(env.VariableSigstoreRekorPublicKey) if altRekorPub != "" { - raw, err := os.ReadFile(altRekorPub) + err := addKeyFromFile(&publicKeys, altRekorPub, rekorDesc) 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) - } - } else { - tufClient, err := tuf.NewFromEnv(ctx) - if err != nil { - return nil, err - } - targets, err := tufClient.GetTargetsByMeta(tuf.Rekor, []string{rekorTargetStr}) - if err != nil { - return nil, err - } - for _, t := range targets { - if err := publicKeys.AddTransparencyLogPubKey(t.Target, t.Status); err != nil { - return nil, fmt.Errorf("AddRekorPubKey: %w", err) - } + return nil, fmt.Errorf("error adding key from environment variable: %w", err) } + return &publicKeys, nil } - if len(publicKeys.Keys) == 0 { - return nil, errors.New("none of the Rekor public keys have been found") + opts, err := setTUFOpts() + if err != nil { + return nil, err } + _ = addKeyFromTUF(&publicKeys, opts, rekorTargetStr, rekorDesc) + if len(publicKeys.Keys) == 0 { + err = legacyAddKeyFromTUF(ctx, &publicKeys, tufv1.Rekor, []string{rekorTargetStr}, rekorDesc) + if err != nil { + return nil, fmt.Errorf("error adding rekor key from TUF: %w", err) + } + if len(publicKeys.Keys) == 0 { + return nil, fmt.Errorf("no rekor public keys found") + } + } return &publicKeys, nil } @@ -163,7 +159,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 @@ -493,9 +489,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 +497,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 55bd76bb9f6f..cb36886c2d5c 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,7 +168,7 @@ 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) } diff --git a/pkg/cosign/tsa.go b/pkg/cosign/tsa.go index 9d1c17a33393..c766a4fda6e6 100644 --- a/pkg/cosign/tsa.go +++ b/pkg/cosign/tsa.go @@ -22,8 +22,9 @@ import ( "os" "github.com/sigstore/cosign/v2/pkg/cosign/env" + "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 +39,57 @@ 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") + opts, err := setTUFOpts() + if err != nil { + return nil, err } - if len(roots) == 0 { - return nil, fmt.Errorf("TSA certificate chain must contain at least one root certificate") + certs, err := getTSAKeysFromTUF(opts) + if err != nil { + certs, err = legacyGetTSAKeysFromTUF(ctx) } - - return &TSACertificates{ - LeafCert: leaves[0], - IntermediateCerts: intermediates, - RootCert: roots, - }, nil + if err != nil { + return nil, fmt.Errorf("could not find TSA certificates: %w", err) + } + return certs, nil } // splitPEMCertificateChain returns a list of leaf (non-CA) certificates, a certificate pool for @@ -152,3 +115,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 9487d1d25fa9..4789d37cce5b 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 000000000000..386a165fb80d --- /dev/null +++ b/pkg/cosign/tuf.go @@ -0,0 +1,138 @@ +// 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/sigstore-go/pkg/tuf" + tufv1 "github.com/sigstore/sigstore/pkg/tuf" +) + +const ( + tufMirrorEnvVar = "TUF_MIRROR" + tufRootDirEnvVar = "TUF_ROOT" + tufRootJSONEnvVar = "TUF_ROOT_JSON" +) + +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 := os.Getenv(tufRootDirEnvVar); 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 := os.Getenv(tufMirrorEnvVar); 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 := os.Getenv(tufRootJSONEnvVar); 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 +} diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 3ab5d76026ac..b4b849d6286e 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" ) @@ -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 1b904c2c4fd8..d3164ee0d4a7 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 30586d4a7595..deeddc18a361 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, }, }, }