Skip to content

Commit

Permalink
Upgrade to TUF v2 client
Browse files Browse the repository at this point in the history
Swap the use of the go-tuf v0.7.0 client from sigstore/sigstore to the
v2.0.0 client from sigstore/sigstore-go.

This change strictly adds logic to attempt to use the sigstore-go TUF
client if possible, and falls back to the old TUF client. The new client
can either fetch the trusted_root.json or fetch targets by name, but cannot query targets by custom metadata. Since this is different from the old client and not all deployments use trusted_root.json to rotate keys, environment variables are added so that users can opt into this new behavior.
The logic in this change works as follows:

- if a path fo a key is provided by a SIGSTORE_ environment variable,
  read that file and use it (same as previously)
- if new environment variables TUF_MIRROR, TUF_ROOT_JSON or
  TUF_USE_TRUSTED_ROOT are set, use
  those to instantiate a TUF v2 client.
  * if TUF_MIRROR or TUF_ROOT_JSON are not set but a v2 client is
    requested, try reading the mirror from remote.json, which is set by
    `cosign initialize`, and try reading the root.json from the mirror's
    cache directory which may have been created by a previous TUF v2
    run. Otherwise, use the default public mirror and embedded
    root.json.
- if TUF v2 was not opted into by setting an environment variable,
  fallback to the v1 client

Also not that the use of the "status" field in the custom TUF metadata
is removed, as it was only used for human-readable feedback.

TODO:
- e2e tests

Signed-off-by: Colleen Murphy <colleenmurphy@google.com>
  • Loading branch information
cmurphy committed Sep 24, 2024
1 parent 4393313 commit f5d1221
Show file tree
Hide file tree
Showing 16 changed files with 539 additions and 194 deletions.
2 changes: 1 addition & 1 deletion cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/verify/verify_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/verify/verify_blob_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
45 changes: 2 additions & 43 deletions internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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()
}
51 changes: 29 additions & 22 deletions pkg/cosign/ctlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
28 changes: 28 additions & 0 deletions pkg/cosign/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
161 changes: 161 additions & 0 deletions pkg/cosign/fulcio.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit f5d1221

Please sign in to comment.