From 3943fef08a6d69939e61d4a302dd69b8e4096078 Mon Sep 17 00:00:00 2001 From: Dmitry S Date: Tue, 9 Jan 2024 16:04:10 +0100 Subject: [PATCH] Add --ca-roots flag for 'cosign verify' Add --ca-roots command-line flag for 'cosign verify' to enable verifying cosign signatures using PEM bundles of CA roots. Whether to also add --ca-intermediates flag is TBD. Unit tests will be added in the next commit(s). Fixes #3462. Signed-off-by: Dmitry S --- cmd/cosign/cli/options/certificate.go | 8 +-- cmd/cosign/cli/verify.go | 2 +- cmd/cosign/cli/verify/verify.go | 87 +++++++++++++++++---------- doc/cosign_dockerfile_verify.md | 4 +- doc/cosign_manifest_verify.md | 4 +- doc/cosign_verify-attestation.md | 4 +- doc/cosign_verify-blob-attestation.md | 4 +- doc/cosign_verify-blob.md | 4 +- doc/cosign_verify.md | 4 +- pkg/cosign/verify.go | 10 +++ 10 files changed, 83 insertions(+), 48 deletions(-) diff --git a/cmd/cosign/cli/options/certificate.go b/cmd/cosign/cli/options/certificate.go index b63fa68ea30..d5f3705bbce 100644 --- a/cmd/cosign/cli/options/certificate.go +++ b/cmd/cosign/cli/options/certificate.go @@ -33,7 +33,7 @@ type CertVerifyOptions struct { CertGithubWorkflowName string CertGithubWorkflowRepository string CertGithubWorkflowRef string - CertBundle string + CARoots string CertChain string SCT string IgnoreSCT bool @@ -76,7 +76,7 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.CertGithubWorkflowRef, "certificate-github-workflow-ref", "", "contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon.") // -- Cert extensions end -- - cmd.Flags().StringVar(&o.CertBundle, "certificate-bundle", "", + cmd.Flags().StringVar(&o.CARoots, "ca-roots", "", "path to a bundle file of CA certificates in PEM format which will be needed "+ "when building the certificate chains for the signing certificate. Conflicts with --certificate-chain.") _ = cmd.Flags().SetAnnotation("certificate-bundle", cobra.BashCompFilenameExt, []string{"cert"}) @@ -85,9 +85,9 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { "path to a list of CA certificates in PEM format which will be needed "+ "when building the certificate chain for the signing certificate. "+ "Must start with the parent intermediate CA certificate of the "+ - "signing certificate and end with the root certificate. Conflicts with --certificate-bundle.") + "signing certificate and end with the root certificate. Conflicts with --ca-roots.") _ = cmd.Flags().SetAnnotation("certificate-chain", cobra.BashCompFilenameExt, []string{"cert"}) - cmd.MarkFlagsMutuallyExclusive("certificate-bundle", "certificate-chain") + cmd.MarkFlagsMutuallyExclusive("ca-roots", "certificate-chain") cmd.Flags().StringVar(&o.SCT, "sct", "", "path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. "+ diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index af352e34ab1..536eb6818a6 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -115,7 +115,7 @@ against the transparency log.`, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, CertGithubWorkflowRepository: o.CertVerify.CertGithubWorkflowRepository, CertGithubWorkflowRef: o.CertVerify.CertGithubWorkflowRef, - CertBundle: o.CertVerify.CertBundle, + CARoots: o.CertVerify.CARoots, CertChain: o.CertVerify.CertChain, IgnoreSCT: o.CertVerify.IgnoreSCT, SCTRef: o.CertVerify.SCT, diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index b8ab47a695c..8b3f60e202b 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -60,7 +60,7 @@ type VerifyCommand struct { CertGithubWorkflowName string CertGithubWorkflowRepository string CertGithubWorkflowRef string - CertBundle string + CARoots string CertChain string CertOidcProvider string IgnoreSCT bool @@ -178,29 +178,47 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { } } if keylessVerification(c.KeyRef, c.Sk) { - if c.CertChain != "" { - chain, err := loadCertChainFromFileOrURL(c.CertChain) - if err != nil { - return err - } - co.RootCerts = x509.NewCertPool() - co.RootCerts.AddCert(chain[len(chain)-1]) - if len(chain) > 1 { - co.IntermediateCerts = x509.NewCertPool() - for _, cert := range chain[:len(chain)-1] { - co.IntermediateCerts.AddCert(cert) + switch { + case c.CertChain != "": + { + chain, err := loadCertChainFromFileOrURL(c.CertChain) + if err != nil { + return err + } + co.RootCerts = x509.NewCertPool() + co.RootCerts.AddCert(chain[len(chain)-1]) + if len(chain) > 1 { + co.IntermediateCerts = x509.NewCertPool() + for _, cert := range chain[:len(chain)-1] { + co.IntermediateCerts.AddCert(cert) + } } } - } else { - // 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) + case c.CARoots != "": + { + caRoots, err := loadCertChainFromFileOrURL(c.CARoots) + if err != nil { + return err + } + co.RootCerts = x509.NewCertPool() + if len(caRoots) > 0 { + for _, cert := range caRoots { + co.RootCerts.AddCert(cert) + } + } } - co.IntermediateCerts, err = fulcio.GetIntermediates() - if err != nil { - return fmt.Errorf("getting Fulcio intermediates: %w", err) + default: + { + // 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) + } } } } @@ -242,8 +260,8 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { if err != nil { return err } - if c.CertChain == "" { - // If no certChain is passed, the Fulcio root certificate will be used + if c.CertChain == "" && c.CARoots == "" { + // If no certChain and no CARoots are passed, the Fulcio root certificate will be used co.RootCerts, err = fulcio.GetRoots() if err != nil { return fmt.Errorf("getting Fulcio roots: %w", err) @@ -257,14 +275,21 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { return err } } else { - // Verify certificate with chain - chain, err := loadCertChainFromFileOrURL(c.CertChain) - if err != nil { - return err - } - pubKey, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co) - if err != nil { - return err + if c.CARoots == "" { + // Verify certificate with chain + chain, err := loadCertChainFromFileOrURL(c.CertChain) + if err != nil { + return err + } + pubKey, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co) + if err != nil { + return err + } + } else { + pubKey, err = cosign.ValidateAndUnpackCertWithCertPools(cert, co) + if err != nil { + return err + } } } if c.SCTRef != "" { diff --git a/doc/cosign_dockerfile_verify.md b/doc/cosign_dockerfile_verify.md index a19e9817458..c9f5d3d4116 100644 --- a/doc/cosign_dockerfile_verify.md +++ b/doc/cosign_dockerfile_verify.md @@ -55,9 +55,9 @@ cosign dockerfile verify [flags] --attachment string DEPRECATED, related image attachment to verify (sbom), default none --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --base-image-only only verify the base image (the last FROM image in the Dockerfile) + --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-bundle string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. - --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --certificate-bundle. + --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/doc/cosign_manifest_verify.md b/doc/cosign_manifest_verify.md index f6d568717b3..70556526124 100644 --- a/doc/cosign_manifest_verify.md +++ b/doc/cosign_manifest_verify.md @@ -49,9 +49,9 @@ cosign manifest verify [flags] -a, --annotations strings extra key=value pairs to sign --attachment string DEPRECATED, related image attachment to verify (sbom), default none --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] + --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-bundle string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. - --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --certificate-bundle. + --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/doc/cosign_verify-attestation.md b/doc/cosign_verify-attestation.md index b3c41f6f9f1..d0e4fab2683 100644 --- a/doc/cosign_verify-attestation.md +++ b/doc/cosign_verify-attestation.md @@ -59,9 +59,9 @@ cosign verify-attestation [flags] --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] + --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-bundle string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. - --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --certificate-bundle. + --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/doc/cosign_verify-blob-attestation.md b/doc/cosign_verify-blob-attestation.md index 572958d14b5..d8b038b91d8 100644 --- a/doc/cosign_verify-blob-attestation.md +++ b/doc/cosign_verify-blob-attestation.md @@ -29,9 +29,9 @@ cosign verify-blob-attestation [flags] ``` --bundle string path to bundle FILE + --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-bundle string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. - --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --certificate-bundle. + --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md index ca2c82e9861..630b44819e8 100644 --- a/doc/cosign_verify-blob.md +++ b/doc/cosign_verify-blob.md @@ -59,9 +59,9 @@ cosign verify-blob [flags] ``` --bundle string path to bundle FILE + --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-bundle string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. - --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --certificate-bundle. + --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md index dddf7e3eab0..d26e0a11a52 100644 --- a/doc/cosign_verify.md +++ b/doc/cosign_verify.md @@ -72,9 +72,9 @@ cosign verify [flags] -a, --annotations strings extra key=value pairs to sign --attachment string DEPRECATED, related image attachment to verify (sbom), default none --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] + --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-bundle string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. - --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --certificate-bundle. + --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 7a4b2b1115c..3094a415a53 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -423,6 +423,16 @@ func ValidateAndUnpackCertWithChain(cert *x509.Certificate, chain []*x509.Certif return ValidateAndUnpackCert(cert, co) } +// ValidateAndUnpackCertWithCertPools creates a Verifier from a certificate. Verifies that the certificate +// chains up to the provided root. CheckOpts should contain a pool of CA Roots and optionally the Intermediates +// Optionally verifies the subject and issuer of the certificate. +func ValidateAndUnpackCertWithCertPools(cert *x509.Certificate, co *CheckOpts) (signature.Verifier, error) { + if co.RootCerts == nil { + return nil, errors.New("no CA roots provided to validate certificate") + } + return ValidateAndUnpackCert(cert, co) +} + func tlogValidateEntry(ctx context.Context, client *client.Rekor, rekorPubKeys *TrustedTransparencyLogPubKeys, sig oci.Signature, pem []byte) (*models.LogEntryAnon, error) { b64sig, err := sig.Base64Signature()