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

Support trusted root in cosign verification #3854

Closed
wants to merge 4 commits into from
Closed
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
82 changes: 3 additions & 79 deletions cmd/conformance/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,15 @@
package main

import (
"crypto/sha256"
"encoding/base64"
"encoding/pem"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"

protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
"github.com/sigstore/sigstore-go/pkg/bundle"
"google.golang.org/protobuf/encoding/protojson"
)

var bundlePath *string
Expand Down Expand Up @@ -111,83 +106,12 @@ func main() {
case "verify":
args = append(args, "verify-blob")

// TODO: for now, we handle `verify` by constructing a bundle
// (see https://github.com/sigstore/cosign/issues/3700)
//
// Today cosign only supports `--trusted-root` with the new bundle
// format. When cosign supports `--trusted-root` with detached signed
// material, we can supply this content with `--certificate`
// and `--signature` instead.
fileBytes, err := os.ReadFile(os.Args[len(os.Args)-1])
if err != nil {
log.Fatal(err)
}

fileDigest := sha256.Sum256(fileBytes)

pb := protobundle.Bundle{
MediaType: "application/vnd.dev.sigstore.bundle+json;version=0.1",
}

if signaturePath != nil {
sig, err := os.ReadFile(*signaturePath)
if err != nil {
log.Fatal(err)
}

sigBytes, err := base64.StdEncoding.DecodeString(string(sig))
if err != nil {
log.Fatal(err)
}

pb.Content = &protobundle.Bundle_MessageSignature{
MessageSignature: &protocommon.MessageSignature{
MessageDigest: &protocommon.HashOutput{
Algorithm: protocommon.HashAlgorithm_SHA2_256,
Digest: fileDigest[:],
},
Signature: sigBytes,
},
}
}
if certPath != nil {
cert, err := os.ReadFile(*certPath)
if err != nil {
log.Fatal(err)
}

pemCert, _ := pem.Decode(cert)
if pemCert == nil {
log.Fatalf("unable to load cerficate from %s", *certPath)
}

signingCert := protocommon.X509Certificate{
RawBytes: pemCert.Bytes,
}

pb.VerificationMaterial = &protobundle.VerificationMaterial{
Content: &protobundle.VerificationMaterial_X509CertificateChain{
X509CertificateChain: &protocommon.X509CertificateChain{
Certificates: []*protocommon.X509Certificate{&signingCert},
},
},
}
args = append(args, "--certificate", *certPath)
}

bundleFile, err := os.CreateTemp(os.TempDir(), "bundle.sigstore.json")
if err != nil {
log.Fatal(err)
}
bundleFileName := bundleFile.Name()
pbBytes, err := protojson.Marshal(&pb)
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile(bundleFileName, pbBytes, 0600); err != nil {
log.Fatal(err)
if signaturePath != nil {
args = append(args, "--signature", *signaturePath)
}
bundlePath = &bundleFileName
args = append(args, "--insecure-ignore-tlog")

case "verify-bundle":
args = append(args, "verify-blob")
Expand Down
188 changes: 115 additions & 73 deletions cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import (
"github.com/sigstore/cosign/v2/pkg/oci/static"
sigs "github.com/sigstore/cosign/v2/pkg/signature"

sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)

Expand Down Expand Up @@ -81,7 +83,7 @@ func (c *VerifyBlobCmd) loadTSACertificates(ctx context.Context) (*cosign.TSACer
}

// nolint
func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) (err error) {
// Require a certificate/key OR a local bundle file that has the cert.
if options.NOf(c.KeyRef, c.CertRef, c.Sk, c.BundlePath) == 0 {
return fmt.Errorf("provide a key with --key or --sk, a certificate to verify against with --certificate, or a bundle with --bundle")
Expand All @@ -92,86 +94,16 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
return &options.PubKeyParseError{}
}

if c.KeyOpts.NewBundleFormat {
if options.NOf(c.RFC3161TimestampPath, c.TSACertChainPath, c.RekorURL, c.CertChain, c.CARoots, c.CAIntermediates, c.CertRef, c.SigRef, c.SCTRef) > 1 {
return fmt.Errorf("when using --new-bundle-format, please supply signed content with --bundle and verification content with --trusted-root")
}
err := verifyNewBundle(ctx, c.BundlePath, c.TrustedRootPath, c.KeyRef, c.Slot, c.CertVerifyOptions.CertOidcIssuer, c.CertVerifyOptions.CertOidcIssuerRegexp, c.CertVerifyOptions.CertIdentity, c.CertVerifyOptions.CertIdentityRegexp, c.CertGithubWorkflowTrigger, c.CertGithubWorkflowSHA, c.CertGithubWorkflowName, c.CertGithubWorkflowRepository, c.CertGithubWorkflowRef, blobRef, c.Sk, c.IgnoreTlog, c.UseSignedTimestamps, c.IgnoreSCT)
if err == nil {
ui.Infof(ctx, "Verified OK")
}
return err
} else if c.TrustedRootPath != "" {
return fmt.Errorf("--trusted-root only supported with --new-bundle-format")
}

var cert *x509.Certificate
opts := make([]static.Option, 0)

var identities []cosign.Identity
var err error
if c.KeyRef == "" {
identities, err = c.Identities()
if err != nil {
return err
}
}

sig, err := base64signature(c.SigRef, c.BundlePath)
if err != nil {
return err
}

blobBytes, err := payloadBytes(blobRef)
if err != nil {
return err
}

co := &cosign.CheckOpts{
CertGithubWorkflowTrigger: c.CertGithubWorkflowTrigger,
CertGithubWorkflowSha: c.CertGithubWorkflowSHA,
CertGithubWorkflowName: c.CertGithubWorkflowName,
CertGithubWorkflowRepository: c.CertGithubWorkflowRepository,
CertGithubWorkflowRef: c.CertGithubWorkflowRef,
IgnoreSCT: c.IgnoreSCT,
Identities: identities,
Offline: c.Offline,
IgnoreTlog: c.IgnoreTlog,
}
if c.RFC3161TimestampPath != "" && !(c.TSACertChainPath != "" || c.UseSignedTimestamps) {
return fmt.Errorf("either TSA certificate chain path must be provided or use-signed-timestamps must be set when using RFC3161 timestamp path")
}
if c.TSACertChainPath != "" || c.UseSignedTimestamps {
tsaCertificates, err := c.loadTSACertificates(ctx)
if err != nil {
return err
}
co.TSACertificate = tsaCertificates.LeafCert
co.TSARootCertificates = tsaCertificates.RootCert
co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts
}

if !c.IgnoreTlog {
if c.RekorURL != "" {
rekorClient, err := rekor.NewClient(c.RekorURL)
if err != nil {
return fmt.Errorf("creating Rekor client: %w", err)
}
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 keylessVerification(c.KeyRef, c.Sk) {
if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil {
return err
}
}

// Keys are optional!
switch {
Expand All @@ -194,12 +126,56 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
if err != nil {
return fmt.Errorf("loading public key from token: %w", err)
}
case c.CertRef != "":
}

var trustedroot *root.TrustedRoot
co.TrustedMaterial, trustedroot, err = makeTrustedMaterial(c.TrustedRootPath, &co.SigVerifier)
if err != nil {
return err
}

co.VerifierOptions = makeVerifierOptions(trustedroot, c.IgnoreTlog, c.UseSignedTimestamps, c.IgnoreSCT)

if c.KeyOpts.NewBundleFormat {
if options.NOf(c.RFC3161TimestampPath, c.TSACertChainPath, c.RekorURL, c.CertChain, c.CARoots, c.CAIntermediates, c.CertRef, c.SigRef, c.SCTRef) > 1 {
return fmt.Errorf("when using --new-bundle-format, please supply signed content with --bundle and verification content with --trusted-root")
}
b, err := sgbundle.LoadJSONFromPath(c.BundlePath)
if err != nil {
return err
}

co.IdentityPolicies, err = makeIdentityPolicy(b, c.CertVerifyOptions, c.CertGithubWorkflowTrigger, c.CertGithubWorkflowSHA, c.CertGithubWorkflowName, c.CertGithubWorkflowRepository, c.CertGithubWorkflowRef)
if err != nil {
return err
}

_, err = verifyNewBundle(b, co, blobRef)
if err == nil {
ui.Infof(ctx, "Verified OK")
}
return err
}

var cert *x509.Certificate
opts := make([]static.Option, 0)

sig, err := base64signature(c.SigRef, c.BundlePath)
if err != nil {
return err
}
sigBytes, err := base64.StdEncoding.DecodeString(sig)
if err != nil {
return err
}

if c.CertRef != "" {
cert, err = loadCertFromFileOrURL(c.CertRef)
if err != nil {
return err
}
}

if c.BundlePath != "" {
b, err := cosign.FetchLocalSignedPayloadFromPath(c.BundlePath)
if err != nil {
Expand Down Expand Up @@ -234,8 +210,9 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
}
opts = append(opts, static.WithBundle(b.Bundle))
}

var rfc3161Timestamp bundle.RFC3161Timestamp
if c.RFC3161TimestampPath != "" {
var rfc3161Timestamp bundle.RFC3161Timestamp
ts, err := blob.LoadFileOrURL(c.RFC3161TimestampPath)
if err != nil {
return err
Expand All @@ -245,6 +222,67 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
}
opts = append(opts, static.WithRFC3161Timestamp(&rfc3161Timestamp))
}

if !c.IgnoreTlog {
if c.RekorURL != "" {
rekorClient, err := rekor.NewClient(c.RekorURL)
if err != nil {
return fmt.Errorf("creating Rekor client: %w", err)
}
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 c.TrustedRootPath != "" {
b, err := assembleNewBundle(ctx, sigBytes, rfc3161Timestamp.SignedRFC3161Timestamp, nil, blobRef, cert, c.IgnoreTlog, co.SigVerifier, co.PKOpts, co.RekorClient)
if err != nil {
return err
}

co.IdentityPolicies, err = makeIdentityPolicy(b, c.CertVerifyOptions, c.CertGithubWorkflowTrigger, c.CertGithubWorkflowSHA, c.CertGithubWorkflowName, c.CertGithubWorkflowRepository, c.CertGithubWorkflowRef)

if err != nil {
return err
}
_, err = verifyNewBundle(b, co, blobRef)
if err == nil {
ui.Infof(ctx, "Verified OK")
}
return err
}

if c.KeyRef == "" {
co.Identities, err = c.Identities()
if err != nil {
return err
}
}

if c.RFC3161TimestampPath != "" && !(c.TSACertChainPath != "" || c.UseSignedTimestamps) {
return fmt.Errorf("either TSA certificate chain path must be provided or use-signed-timestamps must be set when using RFC3161 timestamp path")
}
if c.TSACertChainPath != "" || c.UseSignedTimestamps {
tsaCertificates, err := c.loadTSACertificates(ctx)
if err != nil {
return err
}
co.TSACertificate = tsaCertificates.LeafCert
co.TSARootCertificates = tsaCertificates.RootCert
co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts
}

if keylessVerification(c.KeyRef, c.Sk) {
if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil {
return err
}
}

// Set an SCT if provided via the CLI.
if c.SCTRef != "" {
sct, err := os.ReadFile(filepath.Clean(c.SCTRef))
Expand Down Expand Up @@ -300,6 +338,10 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
}
}

blobBytes, err := payloadBytes(blobRef)
if err != nil {
return err
}
signature, err := static.NewSignature(blobBytes, sig, opts...)
if err != nil {
return err
Expand Down
Loading
Loading