From 196fd4fd63b6ac37d372de44f53fe52b40670d3a Mon Sep 17 00:00:00 2001 From: "Dmitry S." Date: Tue, 9 Jul 2024 12:49:57 +0200 Subject: [PATCH] add handling of keyless verification for all verify commands Copy the handling of non-Fulcio keys from the verify to all other verify commands (verify-attestation, verify-blob, verify-blob-attestations). Fix #3759. Signed-off-by: Dmitry S. --- cmd/cosign/cli/verify.go | 18 ++++++++++++--- cmd/cosign/cli/verify/verify.go | 7 +++--- cmd/cosign/cli/verify/verify_attestation.go | 13 ++++------- cmd/cosign/cli/verify/verify_blob.go | 23 ++++++++----------- .../cli/verify/verify_blob_attestation.go | 22 ++++++------------ cmd/cosign/cli/verify/verify_test.go | 2 +- doc/cosign_verify-blob.md | 6 +++++ pkg/oci/static/options.go | 10 ++++++++ 8 files changed, 56 insertions(+), 45 deletions(-) diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index a2540923082f..c574b8a0f80e 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -114,14 +114,14 @@ against the transparency log.`, CheckClaims: o.CheckClaims, KeyRef: o.Key, CertRef: o.CertVerify.Cert, + CertChain: o.CertVerify.CertChain, + CAIntermediates: o.CertVerify.CAIntermediates, + CARoots: o.CertVerify.CARoots, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, CertGithubWorkflowRepository: o.CertVerify.CertGithubWorkflowRepository, CertGithubWorkflowRef: o.CertVerify.CertGithubWorkflowRef, - CAIntermediates: o.CertVerify.CAIntermediates, - CARoots: o.CertVerify.CARoots, - CertChain: o.CertVerify.CertChain, IgnoreSCT: o.CertVerify.IgnoreSCT, SCTRef: o.CertVerify.SCT, Sk: o.SecurityKey.Use, @@ -223,6 +223,8 @@ against the transparency log.`, CertVerifyOptions: o.CertVerify, CertRef: o.CertVerify.Cert, CertChain: o.CertVerify.CertChain, + CAIntermediates: o.CertVerify.CAIntermediates, + CARoots: o.CertVerify.CARoots, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, @@ -281,6 +283,12 @@ The blob may be specified as a path to a file or - for stdin.`, # Verify a simple blob and message cosign verify-blob --key cosign.pub (--signature | msg) +# Verify a signature with certificate and CA certificate chain + cosign verify-blob --certificate cert.pem --certificate-chain certchain.pem --signature $sig + + # Verify a signature with CA roots and optional intermediate certificates + cosign verify-blob --certificate cert.pem --ca-roots caroots.pem [--ca-intermediates caintermediates.pem] --signature $sig + # Verify a signature from an environment variable cosign verify-blob --key cosign.pub --signature $sig msg @@ -333,6 +341,8 @@ The blob may be specified as a path to a file or - for stdin.`, CertVerifyOptions: o.CertVerify, CertRef: o.CertVerify.Cert, CertChain: o.CertVerify.CertChain, + CARoots: o.CertVerify.CARoots, + CAIntermediates: o.CertVerify.CAIntermediates, SigRef: o.Signature, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSHA: o.CertVerify.CertGithubWorkflowSha, @@ -402,6 +412,8 @@ The blob may be specified as a path to a file.`, CertVerifyOptions: o.CertVerify, CertRef: o.CertVerify.Cert, CertChain: o.CertVerify.CertChain, + CARoots: o.CertVerify.CARoots, + CAIntermediates: o.CertVerify.CAIntermediates, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSHA: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 80649b929271..647b758960c2 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -179,6 +179,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { return err } } + keyRef := c.KeyRef certRef := c.CertRef @@ -511,15 +512,14 @@ func shouldVerifySCT(ignoreSCT bool, keyRef string, sk bool) bool { return true } -// loadCertsKeylessVerification loads certificates for the verification of keyless signatures -// for the verify command. +// loadCertsKeylessVerification loads certificates provided as a certificate chain or CA roots + CA intermediate +// certificate files. If both certChain and caRootsFile are empty strings, the Fulcio roots are loaded. // // TODO - mention additionally verify-attestation, verify-blob, verify-blob-attestation // commands when they are extended to call this function. // // The co *cosign.CheckOpts is both input and output parameter - it gets updated // with the root and intermediate certificates needed for verification. -// If both certChain and caRootsFile are empty strings, the Fulcio roots are loaded. func loadCertsKeylessVerification(certChainFile string, caRootsFile string, caIntermediatesFile string, @@ -574,5 +574,6 @@ func loadCertsKeylessVerification(certChainFile string, return fmt.Errorf("getting Fulcio intermediates: %w", err) } } + return nil } diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index 09fcc7185169..5a9ed7e880a0 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -52,6 +52,8 @@ type VerifyAttestationCommand struct { CertGithubWorkflowName string CertGithubWorkflowRepository string CertGithubWorkflowRef string + CAIntermediates string + CARoots string CertChain string IgnoreSCT bool SCTRef string @@ -156,15 +158,8 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e } if keylessVerification(c.KeyRef, c.Sk) { - // This performs an online fetch of the Fulcio roots. This is needed - // for verifying keyless certificates (both online and offline). - co.RootCerts, err = fulcio.GetRoots() - if err != nil { - return fmt.Errorf("getting Fulcio roots: %w", err) - } - co.IntermediateCerts, err = fulcio.GetIntermediates() - if err != nil { - return fmt.Errorf("getting Fulcio intermediates: %w", err) + if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil { + return err } } diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index c58146ca0d15..6082a541a010 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -28,7 +28,6 @@ import ( "os" "path/filepath" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/internal/ui" @@ -53,6 +52,8 @@ type VerifyBlobCmd struct { options.KeyOpts options.CertVerifyOptions CertRef string + CAIntermediates string + CARoots string CertChain string SigRef string CertGithubWorkflowTrigger string @@ -151,19 +152,10 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { return fmt.Errorf("getting Rekor public keys: %w", err) } } + if keylessVerification(c.KeyRef, c.Sk) { - // Use default TUF roots if a cert chain is not provided. - // This performs an online fetch of the Fulcio roots. This is needed - // for verifying keyless certificates (both online and offline). - if c.CertChain == "" { - co.RootCerts, err = fulcio.GetRoots() - if err != nil { - return fmt.Errorf("getting Fulcio roots: %w", err) - } - co.IntermediateCerts, err = fulcio.GetIntermediates() - if err != nil { - return fmt.Errorf("getting Fulcio intermediates: %w", err) - } + if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil { + return err } } @@ -249,7 +241,8 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { } // Set a cert chain if provided. var chainPEM []byte - if c.CertChain != "" { + switch { + case c.CertChain != "": chain, err := loadCertChainFromFileOrURL(c.CertChain) if err != nil { return err @@ -269,6 +262,8 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { if err != nil { return err } + case c.CARoots != "": + // TODO insert CA roots + intermediates into the signature options for verification } // Gather the cert for the signature and add the cert along with the diff --git a/cmd/cosign/cli/verify/verify_blob_attestation.go b/cmd/cosign/cli/verify/verify_blob_attestation.go index 4c29501ce098..6b48a7695cb5 100644 --- a/cmd/cosign/cli/verify/verify_blob_attestation.go +++ b/cmd/cosign/cli/verify/verify_blob_attestation.go @@ -30,7 +30,6 @@ import ( "path/filepath" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" internal "github.com/sigstore/cosign/v2/internal/pkg/cosign" @@ -52,8 +51,10 @@ type VerifyBlobAttestationCommand struct { options.KeyOpts options.CertVerifyOptions - CertRef string - CertChain string + CertRef string + CertChain string + CAIntermediates string + CARoots string CertGithubWorkflowTrigger string CertGithubWorkflowSHA string @@ -170,20 +171,11 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st } } if keylessVerification(c.KeyRef, c.Sk) { - // Use default TUF roots if a cert chain is not provided. - // This performs an online fetch of the Fulcio roots. This is needed - // for verifying keyless certificates (both online and offline). - if c.CertChain == "" { - co.RootCerts, err = fulcio.GetRoots() - if err != nil { - return fmt.Errorf("getting Fulcio roots: %w", err) - } - co.IntermediateCerts, err = fulcio.GetIntermediates() - if err != nil { - return fmt.Errorf("getting Fulcio intermediates: %w", err) - } + if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil { + return err } } + // Ignore Signed Certificate Timestamp if the flag is set or a key is provided if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) { co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) diff --git a/cmd/cosign/cli/verify/verify_test.go b/cmd/cosign/cli/verify/verify_test.go index 620c841b6f9d..322ffa9fcb4c 100644 --- a/cmd/cosign/cli/verify/verify_test.go +++ b/cmd/cosign/cli/verify/verify_test.go @@ -264,7 +264,7 @@ func TestVerifyCertMissingIssuer(t *testing.T) { } } -func TestLoadCertsKeylessVerification(t *testing.T) { +func TestLoadCerts(t *testing.T) { certs := getTestCerts(t) certChainFile := makeCertChainFile(t, certs.RootCertPEM, certs.SubCertPEM, certs.LeafCertPEM) rootsFile, intermediatesFile := makeRootsIntermediatesFiles(t, certs.RootCertPEM, certs.SubCertPEM) diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md index e4fb5c16a225..f04ec98387d1 100644 --- a/doc/cosign_verify-blob.md +++ b/doc/cosign_verify-blob.md @@ -23,6 +23,12 @@ cosign verify-blob [flags] # Verify a simple blob and message cosign verify-blob --key cosign.pub (--signature | msg) +# Verify a signature with certificate and CA certificate chain + cosign verify-blob --certificate cert.pem --certificate-chain certchain.pem --signature $sig + + # Verify a signature with CA roots and optional intermediate certificates + cosign verify-blob --certificate cert.pem --ca-roots caroots.pem [--ca-intermediates caintermediates.pem] --signature $sig + # Verify a signature from an environment variable cosign verify-blob --key cosign.pub --signature $sig msg diff --git a/pkg/oci/static/options.go b/pkg/oci/static/options.go index b240fb228ae5..4a1abbc4e458 100644 --- a/pkg/oci/static/options.go +++ b/pkg/oci/static/options.go @@ -33,6 +33,8 @@ type options struct { RFC3161Timestamp *bundle.RFC3161Timestamp Cert []byte Chain []byte + RootCerts []byte + IntermediateCerts [][]byte Annotations map[string]string RecordCreationTimestamp bool } @@ -114,6 +116,14 @@ func WithCertChain(cert, chain []byte) Option { } } +// Wr sets the CA root and intermediates certificates for this signature. +func WithRootsIntermediates(roots []byte, intermediates [][]byte) Option { + return func(o *options) { + o.RootCerts = roots + o.IntermediateCerts = intermediates + } +} + // WithRecordCreationTimestamp sets the feature flag to honor the creation timestamp to time of running func WithRecordCreationTimestamp(rct bool) Option { return func(o *options) {