diff --git a/.github/workflows/kind-verify-attestation.yaml b/.github/workflows/kind-verify-attestation.yaml index 41415399d0f..60176e06fa4 100644 --- a/.github/workflows/kind-verify-attestation.yaml +++ b/.github/workflows/kind-verify-attestation.yaml @@ -17,6 +17,7 @@ name: Test attest / verify-attestation on: pull_request: branches: [ 'main', 'release-*' ] + workflow_dispatch: defaults: run: @@ -118,12 +119,12 @@ jobs: - name: Verify with cosign run: | - ./cosign verify --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} + ./cosign verify --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" - name: Verify custom attestation with cosign, works run: | echo '::group:: test custom verify-attestation success' - if ! ./cosign verify-attestation --policy ./test/testdata/policies/cue-works.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then + if ! ./cosign verify-attestation --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" --policy ./test/testdata/policies/cue-works.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then echo Failed to verify attestation with a valid policy exit 1 else @@ -134,7 +135,7 @@ jobs: - name: Verify custom attestation with cosign, fails run: | echo '::group:: test custom verify-attestation success' - if ./cosign verify-attestation --policy ./test/testdata/policies/cue-fails.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then + if ./cosign verify-attestation --policy ./test/testdata/policies/cue-fails.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" ; then echo custom verify-attestation succeeded with cue policy that should not work exit 1 else @@ -144,7 +145,7 @@ jobs: - name: Verify a blob run: | - ./cosign verify-blob README.md --rekor-url ${{ env.REKOR_URL }} --certificate ./cert.pem --signature sig + ./cosign verify-blob README.md --rekor-url ${{ env.REKOR_URL }} --certificate ./cert.pem --signature sig --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" - name: Collect diagnostics if: ${{ failure() }} @@ -157,7 +158,7 @@ jobs: - name: Verify vuln attestation with cosign, works run: | echo '::group:: test vuln verify-attestation success' - if ! ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-works.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then + if ! ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-works.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" ; then echo Failed to verify attestation with a valid policy exit 1 else @@ -168,7 +169,7 @@ jobs: - name: Verify vuln attestation with cosign, fails run: | echo '::group:: test vuln verify-attestation success' - if ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-fails.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then + if ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-fails.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" ; then echo verify-attestation succeeded with cue policy that should not work exit 1 else diff --git a/cmd/cosign/cli/dockerfile.go b/cmd/cosign/cli/dockerfile.go index 64988fe5d56..efd69fa8f6d 100644 --- a/cmd/cosign/cli/dockerfile.go +++ b/cmd/cosign/cli/dockerfile.go @@ -87,11 +87,10 @@ Shell-like variables in the Dockerfile's FROM lines will be substituted with val v := &dockerfile.VerifyDockerfileCommand{ VerifyCommand: verify.VerifyCommand{ RegistryOptions: o.Registry, + CertVerifyOptions: o.CertVerify, CheckClaims: o.CheckClaims, KeyRef: o.Key, CertRef: o.CertVerify.Cert, - CertEmail: o.CertVerify.CertEmail, - CertOidcIssuer: o.CertVerify.CertOidcIssuer, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, diff --git a/cmd/cosign/cli/manifest.go b/cmd/cosign/cli/manifest.go index fce3d067e06..cc8c719e7b0 100644 --- a/cmd/cosign/cli/manifest.go +++ b/cmd/cosign/cli/manifest.go @@ -82,11 +82,10 @@ against the transparency log.`, v := &manifest.VerifyManifestCommand{ VerifyCommand: verify.VerifyCommand{ RegistryOptions: o.Registry, + CertVerifyOptions: o.CertVerify, CheckClaims: o.CheckClaims, KeyRef: o.Key, CertRef: o.CertVerify.Cert, - CertEmail: o.CertVerify.CertEmail, - CertOidcIssuer: o.CertVerify.CertOidcIssuer, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, diff --git a/cmd/cosign/cli/options/certificate.go b/cmd/cosign/cli/options/certificate.go index 9eef38fd9c8..e89f257f9e3 100644 --- a/cmd/cosign/cli/options/certificate.go +++ b/cmd/cosign/cli/options/certificate.go @@ -15,15 +15,19 @@ package options import ( + "errors" + + "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/spf13/cobra" ) // CertVerifyOptions is the wrapper for certificate verification. type CertVerifyOptions struct { Cert string - CertEmail string CertIdentity string + CertIdentityRegexp string CertOidcIssuer string + CertOidcIssuerRegexp string CertGithubWorkflowTrigger string CertGithubWorkflowSha string CertGithubWorkflowName string @@ -42,14 +46,17 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { "path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed.") _ = cmd.Flags().SetAnnotation("certificate", cobra.BashCompFilenameExt, []string{"cert"}) - cmd.Flags().StringVar(&o.CertEmail, "certificate-email", "", - "the email expected in a valid Fulcio certificate") - cmd.Flags().StringVar(&o.CertIdentity, "certificate-identity", "", - "the identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs.") + "The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows.") + + cmd.Flags().StringVar(&o.CertIdentityRegexp, "certificate-identity-regexp", "", + "A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows.") cmd.Flags().StringVar(&o.CertOidcIssuer, "certificate-oidc-issuer", "", - "the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth") + "The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows.") + + cmd.Flags().StringVar(&o.CertOidcIssuerRegexp, "certificate-oidc-issuer-regexp", "", + "A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows.") // -- Cert extensions begin -- // Source: https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md @@ -82,3 +89,13 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { "when set, verification will not check that a certificate contains an embedded SCT, a proof of "+ "inclusion in a certificate transparency log") } + +func (o *CertVerifyOptions) Identities() ([]cosign.Identity, error) { + if o.CertIdentity == "" && o.CertIdentityRegexp == "" { + return nil, errors.New("--certificate-identity or --certificate-identity-regexp is required for verification in keyless mode") + } + if o.CertOidcIssuer == "" && o.CertOidcIssuerRegexp == "" { + return nil, errors.New("--certificate-oidc-issuer or --certificate-oidc-issuer-regexp is required for verification in keyless mode") + } + return []cosign.Identity{{IssuerRegExp: o.CertOidcIssuerRegexp, Issuer: o.CertOidcIssuer, SubjectRegExp: o.CertIdentityRegexp, Subject: o.CertIdentity}}, nil +} diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index 10f290bff9e..84adf071b50 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -95,12 +95,10 @@ against the transparency log.`, v := &verify.VerifyCommand{ RegistryOptions: o.Registry, + CertVerifyOptions: o.CertVerify, CheckClaims: o.CheckClaims, KeyRef: o.Key, CertRef: o.CertVerify.Cert, - CertEmail: o.CertVerify.CertEmail, - CertIdentity: o.CertVerify.CertIdentity, - CertOidcIssuer: o.CertVerify.CertOidcIssuer, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, @@ -191,10 +189,8 @@ against the transparency log.`, v := &verify.VerifyAttestationCommand{ RegistryOptions: o.Registry, CheckClaims: o.CheckClaims, + CertVerifyOptions: o.CertVerify, CertRef: o.CertVerify.Cert, - CertEmail: o.CertVerify.CertEmail, - CertIdentity: o.CertVerify.CertIdentity, - CertOidcIssuer: o.CertVerify.CertOidcIssuer, CertChain: o.CertVerify.CertChain, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, @@ -287,10 +283,8 @@ The blob may be specified as a path to a file or - for stdin.`, } verifyBlobCmd := &verify.VerifyBlobCmd{ KeyOpts: ko, + CertVerifyOptions: o.CertVerify, CertRef: o.CertVerify.Cert, - CertEmail: o.CertVerify.CertEmail, - CertIdentity: o.CertVerify.CertIdentity, - CertOIDCIssuer: o.CertVerify.CertOidcIssuer, CertChain: o.CertVerify.CertChain, SigRef: o.Signature, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 16ff95ce8d8..c6dfb3cf65f 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -51,12 +51,10 @@ import ( // nolint type VerifyCommand struct { options.RegistryOptions + options.CertVerifyOptions CheckClaims bool KeyRef string CertRef string - CertEmail string - CertIdentity string - CertOidcIssuer string CertGithubWorkflowTrigger string CertGithubWorkflowSha string CertGithubWorkflowName string @@ -99,6 +97,14 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { c.HashAlgorithm = crypto.SHA256 } + var identities []cosign.Identity + if c.KeyRef == "" { + identities, err = c.Identities() + if err != nil { + return err + } + } + ociremoteOpts, err := c.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) @@ -107,9 +113,6 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { co := &cosign.CheckOpts{ Annotations: c.Annotations.Annotations, RegistryClientOpts: ociremoteOpts, - CertEmail: c.CertEmail, - CertIdentity: c.CertIdentity, - CertOidcIssuer: c.CertOidcIssuer, CertGithubWorkflowTrigger: c.CertGithubWorkflowTrigger, CertGithubWorkflowSha: c.CertGithubWorkflowSha, CertGithubWorkflowName: c.CertGithubWorkflowName, @@ -117,6 +120,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { CertGithubWorkflowRef: c.CertGithubWorkflowRef, IgnoreSCT: c.IgnoreSCT, SignatureRef: c.SignatureRef, + Identities: identities, Offline: c.Offline, SkipTlogVerify: c.SkipTlogVerify, } diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index 26e6eba3a1a..8caf7ebc9cf 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -43,12 +43,10 @@ import ( // nolint type VerifyAttestationCommand struct { options.RegistryOptions + options.CertVerifyOptions CheckClaims bool KeyRef string CertRef string - CertEmail string - CertIdentity string - CertOidcIssuer string CertGithubWorkflowTrigger string CertGithubWorkflowSha string CertGithubWorkflowName string @@ -81,21 +79,28 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return &options.KeyParseError{} } + var identities []cosign.Identity + if c.KeyRef == "" { + identities, err = c.Identities() + if err != nil { + return err + } + } + ociremoteOpts, err := c.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) } + co := &cosign.CheckOpts{ RegistryClientOpts: ociremoteOpts, - CertEmail: c.CertEmail, - CertIdentity: c.CertIdentity, - CertOidcIssuer: c.CertOidcIssuer, CertGithubWorkflowTrigger: c.CertGithubWorkflowTrigger, CertGithubWorkflowSha: c.CertGithubWorkflowSha, CertGithubWorkflowName: c.CertGithubWorkflowName, CertGithubWorkflowRepository: c.CertGithubWorkflowRepository, CertGithubWorkflowRef: c.CertGithubWorkflowRef, IgnoreSCT: c.IgnoreSCT, + Identities: identities, Offline: c.Offline, SkipTlogVerify: c.SkipTlogVerify, } diff --git a/cmd/cosign/cli/verify/verify_attestation_test.go b/cmd/cosign/cli/verify/verify_attestation_test.go new file mode 100644 index 00000000000..ee3c0d99659 --- /dev/null +++ b/cmd/cosign/cli/verify/verify_attestation_test.go @@ -0,0 +1,54 @@ +// Copyright 2022 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 verify + +import ( + "context" + "testing" + + "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" +) + +func TestVerifyAttestationMissingSubject(t *testing.T) { + ctx := context.Background() + + verifyAttestation := VerifyAttestationCommand{ + CertRef: "cert.pem", + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: "issuer", + }, + } + + err := verifyAttestation.Exec(ctx, []string{"foo", "bar", "baz"}) + if err == nil { + t.Fatal("verifyAttestation expected 'need --certificate-identity'") + } +} + +func TestVerifyAttestationMissingIssuer(t *testing.T) { + ctx := context.Background() + + verifyAttestation := VerifyAttestationCommand{ + CertRef: "cert.pem", + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "subject", + }, + } + + err := verifyAttestation.Exec(ctx, []string{"foo", "bar", "baz"}) + if err == nil { + t.Fatal("verifyAttestation expected 'need --certificate-oidc-issuer'") + } +} diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index a2ba5f5af22..2ec41751c20 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -53,10 +53,8 @@ func isb64(data []byte) bool { // nolint type VerifyBlobCmd struct { options.KeyOpts + options.CertVerifyOptions CertRef string - CertEmail string - CertIdentity string - CertOIDCIssuer string CertChain string SigRef string CertGithubWorkflowTrigger string @@ -85,6 +83,15 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { return &options.KeyParseError{} } + 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 @@ -96,15 +103,13 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { } co := &cosign.CheckOpts{ - CertEmail: c.CertEmail, - CertIdentity: c.CertIdentity, - CertOidcIssuer: c.CertOIDCIssuer, CertGithubWorkflowTrigger: c.CertGithubWorkflowTrigger, CertGithubWorkflowSha: c.CertGithubWorkflowSHA, CertGithubWorkflowName: c.CertGithubWorkflowName, CertGithubWorkflowRepository: c.CertGithubWorkflowRepository, CertGithubWorkflowRef: c.CertGithubWorkflowRef, IgnoreSCT: c.IgnoreSCT, + Identities: identities, Offline: c.Offline, SkipTlogVerify: c.SkipTlogVerify, } diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go index 676f467feab..e3670f65ad8 100644 --- a/cmd/cosign/cli/verify/verify_blob_test.go +++ b/cmd/cosign/cli/verify/verify_blob_test.go @@ -616,8 +616,10 @@ func TestVerifyBlob(t *testing.T) { RFC3161TimestampPath: tt.tsPath, TSACertChainPath: tt.tsChainPath, }, - CertEmail: identity, - CertOIDCIssuer: issuer, + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: identity, + CertOidcIssuer: issuer, + }, IgnoreSCT: true, CertChain: chainPath, SkipTlogVerify: tt.skipTlogVerify, @@ -648,6 +650,35 @@ func TestVerifyBlob(t *testing.T) { } } +func TestVerifyBlobCertMissingSubject(t *testing.T) { + ctx := context.Background() + + verifyBlob := VerifyBlobCmd{ + CertRef: "cert.pem", + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: "issuer", + }, + } + err := verifyBlob.Exec(ctx, "blob") + if err == nil { + t.Fatalf("verifyBlob() expected '--certificate-identity required'") + } +} + +func TestVerifyBlobCertMissingIssuer(t *testing.T) { + ctx := context.Background() + verifyBlob := VerifyBlobCmd{ + CertRef: "cert.pem", + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "subject", + }, + } + err := verifyBlob.Exec(ctx, "blob") + if err == nil { + t.Fatalf("verifyBlob() expected '--certificate-oidc-issuer required'") + } +} + func makeRekorEntry(t *testing.T, rekorSigner signature.ECDSASignerVerifier, pyld, sig, svBytes []byte, expiryValid bool) *models.LogEntry { ctx := context.Background() @@ -801,10 +832,12 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ - KeyOpts: options.KeyOpts{BundlePath: bundlePath}, - CertEmail: identity, - CertOIDCIssuer: issuer, - IgnoreSCT: true, + KeyOpts: options.KeyOpts{BundlePath: bundlePath}, + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: identity, + CertOidcIssuer: issuer, + }, + IgnoreSCT: true, } if err := cmd.Exec(context.Background(), blobPath); err != nil { t.Fatal(err) @@ -900,6 +933,10 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: identity, + CertOidcIssuer: issuer, + }, CertRef: "", // Cert is fetched from bundle CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SigRef: "", // Sig is fetched from bundle @@ -933,6 +970,10 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: identity, + CertOidcIssuer: issuer, + }, CertRef: "", // Cert is fetched from bundle CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SigRef: "", // Sig is fetched from bundle @@ -967,16 +1008,18 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ - KeyOpts: options.KeyOpts{BundlePath: bundlePath}, - CertRef: "", // Cert is fetched from bundle - CertOIDCIssuer: issuer, - CertEmail: "invalid@example.com", - CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE - SigRef: "", // Sig is fetched from bundle - IgnoreSCT: true, + KeyOpts: options.KeyOpts{BundlePath: bundlePath}, + CertRef: "", // Cert is fetched from bundle + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, + CertIdentity: "invalid@example.com", + }, + CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE + SigRef: "", // Sig is fetched from bundle + IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) - if err == nil || !strings.Contains(err.Error(), "expected identity not found in certificate") { + if err == nil || !strings.Contains(err.Error(), "none of the expected identities matched what was in the certificate") { t.Fatalf("expected error with mismatched identity, got %v", err) } }) @@ -1003,16 +1046,18 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ - CertRef: "", // Cert is fetched from bundle - CertOIDCIssuer: "invalid", - CertEmail: identity, - CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE - SigRef: "", // Sig is fetched from bundle - KeyOpts: options.KeyOpts{BundlePath: bundlePath}, - IgnoreSCT: true, + CertRef: "", // Cert is fetched from bundle + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: "invalid", + CertIdentity: identity, + }, + CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE + SigRef: "", // Sig is fetched from bundle + KeyOpts: options.KeyOpts{BundlePath: bundlePath}, + IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) - if err == nil || !strings.Contains(err.Error(), "expected oidc issuer not found in certificate") { + if err == nil || !strings.Contains(err.Error(), "none of the expected identities matched what was in the certificate") { t.Fatalf("expected error with mismatched issuer, got %v", err) } }) @@ -1040,13 +1085,15 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ - CertRef: certPath, - CertOIDCIssuer: issuer, - CertEmail: identity, - CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE - SigRef: "", // Sig is fetched from bundle - KeyOpts: options.KeyOpts{BundlePath: bundlePath}, - IgnoreSCT: true, + CertRef: certPath, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, + CertIdentity: identity, + }, + CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE + SigRef: "", // Sig is fetched from bundle + KeyOpts: options.KeyOpts{BundlePath: bundlePath}, + IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err != nil { @@ -1096,12 +1143,14 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ - CertOIDCIssuer: issuer, - CertEmail: identity, - CertChain: os.Getenv("SIGSTORE_ROOT_FILE"), - SigRef: "", // Sig is fetched from bundle - KeyOpts: options.KeyOpts{BundlePath: bundlePath, TSACertChainPath: tsaCertChainPath, RFC3161TimestampPath: tsPath}, - IgnoreSCT: true, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, + CertIdentity: identity, + }, + CertChain: os.Getenv("SIGSTORE_ROOT_FILE"), + SigRef: "", // Sig is fetched from bundle + KeyOpts: options.KeyOpts{BundlePath: bundlePath, TSACertChainPath: tsaCertChainPath, RFC3161TimestampPath: tsPath}, + IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err != nil { @@ -1131,12 +1180,14 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ - CertOIDCIssuer: issuer, - CertEmail: identity, - CertChain: os.Getenv("SIGSTORE_ROOT_FILE"), - SigRef: "", // Sig is fetched from bundle - KeyOpts: options.KeyOpts{BundlePath: bundlePath}, - IgnoreSCT: true, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, + CertIdentity: identity, + }, + CertChain: os.Getenv("SIGSTORE_ROOT_FILE"), + SigRef: "", // Sig is fetched from bundle + KeyOpts: options.KeyOpts{BundlePath: bundlePath}, + IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err != nil { @@ -1177,12 +1228,14 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ - CertOIDCIssuer: issuer, - CertEmail: identity, - CertChain: tmpChainFile.Name(), - SigRef: "", // Sig is fetched from bundle - KeyOpts: options.KeyOpts{BundlePath: bundlePath}, - IgnoreSCT: true, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, + CertIdentity: identity, + }, + CertChain: tmpChainFile.Name(), + SigRef: "", // Sig is fetched from bundle + KeyOpts: options.KeyOpts{BundlePath: bundlePath}, + IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err == nil || !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { @@ -1219,13 +1272,15 @@ func TestVerifyBlobCmdInvalidRootCA(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ - CertRef: certPath, - CertOIDCIssuer: issuer, - CertEmail: identity, - CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE - SigRef: "", // Sig is fetched from bundle - KeyOpts: options.KeyOpts{BundlePath: bundlePath}, - IgnoreSCT: true, + CertRef: certPath, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, + CertIdentity: identity, + }, + CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE + SigRef: "", // Sig is fetched from bundle + KeyOpts: options.KeyOpts{BundlePath: bundlePath}, + IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err == nil || !strings.Contains(err.Error(), "certificate signed by unknown authority") { @@ -1255,13 +1310,15 @@ func TestVerifyBlobCmdInvalidRootCA(t *testing.T) { // Verify command cmd := VerifyBlobCmd{ - CertRef: "", - CertOIDCIssuer: issuer, // Fetched from bundle - CertEmail: identity, - CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE - SigRef: "", // Sig is fetched from bundle - KeyOpts: options.KeyOpts{BundlePath: bundlePath}, - IgnoreSCT: true, + CertRef: "", + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, // Fetched from bundle + CertIdentity: identity, + }, + CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE + SigRef: "", // Sig is fetched from bundle + KeyOpts: options.KeyOpts{BundlePath: bundlePath}, + IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err == nil || !strings.Contains(err.Error(), "certificate signed by unknown authority") { diff --git a/cmd/cosign/cli/verify/verify_test.go b/cmd/cosign/cli/verify/verify_test.go index a50831cf22f..4081c35b58f 100644 --- a/cmd/cosign/cli/verify/verify_test.go +++ b/cmd/cosign/cli/verify/verify_test.go @@ -16,6 +16,7 @@ package verify import ( "bytes" + "context" "crypto" "crypto/rand" "crypto/sha256" @@ -33,6 +34,7 @@ import ( "testing" "github.com/google/go-containerregistry/pkg/name" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/test" @@ -160,3 +162,33 @@ func appendSlices(slices [][]byte) []byte { } return tmp } + +func TestVerifyCertMissingSubject(t *testing.T) { + ctx := context.Background() + verifyCommand := VerifyCommand{ + CertRef: "cert.pem", + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: "issuer", + }, + } + + err := verifyCommand.Exec(ctx, []string{"foo", "bar", "baz"}) + if err == nil { + t.Fatal("verify expected 'need --certificate-identity'") + } +} + +func TestVerifyCertMissingIssuer(t *testing.T) { + ctx := context.Background() + verifyCommand := VerifyCommand{ + CertRef: "cert.pem", + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "identity", + }, + } + + err := verifyCommand.Exec(ctx, []string{"foo", "bar", "baz"}) + if err == nil { + t.Fatal("verify expected 'need --certificate-oidc-issuer'") + } +} diff --git a/doc/cosign_dockerfile_verify.md b/doc/cosign_dockerfile_verify.md index dc180a31c5c..222b11a31c0 100644 --- a/doc/cosign_dockerfile_verify.md +++ b/doc/cosign_dockerfile_verify.md @@ -60,14 +60,15 @@ cosign dockerfile verify [flags] --base-image-only only verify the base image (the last FROM image in the Dockerfile) --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-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 - --certificate-email string the email expected in a valid Fulcio certificate --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 --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run - --certificate-identity string the identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. - --certificate-oidc-issuer string the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth + --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. + --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. + --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. + --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log diff --git a/doc/cosign_manifest_verify.md b/doc/cosign_manifest_verify.md index fedb0c3adae..62e5bf33bf8 100644 --- a/doc/cosign_manifest_verify.md +++ b/doc/cosign_manifest_verify.md @@ -54,14 +54,15 @@ cosign manifest verify [flags] --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] --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-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 - --certificate-email string the email expected in a valid Fulcio certificate --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 --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run - --certificate-identity string the identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. - --certificate-oidc-issuer string the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth + --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. + --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. + --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. + --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log diff --git a/doc/cosign_verify-attestation.md b/doc/cosign_verify-attestation.md index 035331bcd08..9179646a819 100644 --- a/doc/cosign_verify-attestation.md +++ b/doc/cosign_verify-attestation.md @@ -61,14 +61,15 @@ cosign verify-attestation [flags] --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] --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-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 - --certificate-email string the email expected in a valid Fulcio certificate --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 --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run - --certificate-identity string the identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. - --certificate-oidc-issuer string the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth + --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. + --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. + --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. + --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) -h, --help help for verify-attestation --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md index a7dc101f751..ebe087a28bd 100644 --- a/doc/cosign_verify-blob.md +++ b/doc/cosign_verify-blob.md @@ -64,14 +64,15 @@ cosign verify-blob [flags] --bundle string path to bundle FILE --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-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 - --certificate-email string the email expected in a valid Fulcio certificate --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 --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run - --certificate-identity string the identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. - --certificate-oidc-issuer string the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth + --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. + --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. + --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. + --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. -h, --help help for verify-blob --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log --insecure-skip-tlog-verify skip transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md index 6b0ad348bc7..6ff985f2c97 100644 --- a/doc/cosign_verify.md +++ b/doc/cosign_verify.md @@ -70,14 +70,15 @@ cosign verify [flags] --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] --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-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 - --certificate-email string the email expected in a valid Fulcio certificate --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 --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run - --certificate-identity string the identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. - --certificate-oidc-issuer string the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth + --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. + --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. + --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. + --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 3f715cf05a3..04343ee552e 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -98,12 +98,6 @@ type CheckOpts struct { RootCerts *x509.CertPool // IntermediateCerts are the optional intermediate CA certs used to verify a certificate chain. IntermediateCerts *x509.CertPool - // CertEmail is the email expected for a certificate to be valid. The empty string means any certificate can be valid. - CertEmail string - // CertIdentity is the identity expected for a certificate to be valid. - CertIdentity string - // CertOidcIssuer is the OIDC issuer expected for a certificate to be valid. The empty string means any certificate can be valid. - CertOidcIssuer string // CertGithubWorkflowTrigger is the GitHub Workflow Trigger name expected for a certificate to be valid. The empty string means any certificate can be valid. CertGithubWorkflowTrigger string @@ -130,7 +124,6 @@ type CheckOpts struct { // Identities is an array of Identity (Subject, Issuer) matchers that have // to be met for the signature to ve valid. - // Supercedes CertEmail / CertOidcIssuer Identities []Identity // Force offline verification of the signature @@ -276,14 +269,10 @@ func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Ver func CheckCertificatePolicy(cert *x509.Certificate, co *CheckOpts) error { ce := CertExtensions{Cert: cert} - if err := validateCertIdentity(cert, co); err != nil { - return err - } - if err := validateCertExtensions(ce, co); err != nil { return err } - issuer := ce.GetIssuer() + oidcIssuer := ce.GetIssuer() // If there are identities given, go through them and if one of them // matches, call that good, otherwise, return an error. if len(co.Identities) > 0 { @@ -294,11 +283,11 @@ func CheckCertificatePolicy(cert *x509.Certificate, co *CheckOpts) error { case identity.IssuerRegExp != "": if regex, err := regexp.Compile(identity.IssuerRegExp); err != nil { return fmt.Errorf("malformed issuer in identity: %s : %w", identity.IssuerRegExp, err) - } else if regex.MatchString(issuer) { + } else if regex.MatchString(oidcIssuer) { issuerMatches = true } case identity.Issuer != "": - if identity.Issuer == issuer { + if identity.Issuer == oidcIssuer { issuerMatches = true } default: @@ -342,12 +331,6 @@ func CheckCertificatePolicy(cert *x509.Certificate, co *CheckOpts) error { } func validateCertExtensions(ce CertExtensions, co *CheckOpts) error { - if co.CertOidcIssuer != "" { - if ce.GetIssuer() != co.CertOidcIssuer { - return &VerificationError{"expected oidc issuer not found in certificate"} - } - } - if co.CertGithubWorkflowTrigger != "" { if ce.GetCertExtensionGithubWorkflowTrigger() != co.CertGithubWorkflowTrigger { return &VerificationError{"expected GitHub Workflow Trigger not found in certificate"} @@ -380,41 +363,6 @@ func validateCertExtensions(ce CertExtensions, co *CheckOpts) error { return nil } -func validateCertIdentity(cert *x509.Certificate, co *CheckOpts) error { - // TODO: Make it mandatory to include one of these options. - if co.CertEmail == "" && co.CertIdentity == "" { - return nil - } - - for _, dns := range cert.DNSNames { - if co.CertIdentity == dns { - return nil - } - } - for _, em := range cert.EmailAddresses { - if co.CertIdentity == em || co.CertEmail == em { - return nil - } - } - for _, ip := range cert.IPAddresses { - if co.CertIdentity == ip.String() { - return nil - } - } - for _, uri := range cert.URIs { - if co.CertIdentity == uri.String() { - return nil - } - } - - otherName, _ := cryptoutils.UnmarshalOtherNameSAN(cert.Extensions) - if len(otherName) > 0 && co.CertIdentity == otherName { - return nil - } - - return &VerificationError{"expected identity not found in certificate"} -} - // getSubjectAlternateNames returns all of the following for a Certificate. // DNSNames // EmailAddresses diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index c946519f8ae..70760cc8802 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -137,7 +137,7 @@ func Test_verifyOCIAttestation(t *testing.T) { func TestVerifyImageSignature(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) - leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) + leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) @@ -152,7 +152,12 @@ func TestVerifyImageSignature(t *testing.T) { ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub, pemRoot}))) - verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool, IgnoreSCT: true, SkipTlogVerify: true}) + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, + &CheckOpts{ + RootCerts: rootPool, + IgnoreSCT: true, + SkipTlogVerify: true, + Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}}) if err != nil { t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err) } @@ -167,7 +172,7 @@ func TestVerifyImageSignatureMultipleSubs(t *testing.T) { subCert1, subKey1, _ := test.GenerateSubordinateCa(rootCert, rootKey) subCert2, subKey2, _ := test.GenerateSubordinateCa(subCert1, subKey1) subCert3, subKey3, _ := test.GenerateSubordinateCa(subCert2, subKey2) - leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert3, subKey3) + leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert3, subKey3) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub1 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert1.Raw}) pemSub2 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert2.Raw}) @@ -183,7 +188,10 @@ func TestVerifyImageSignatureMultipleSubs(t *testing.T) { ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub3, pemSub2, pemSub1, pemRoot}))) - verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool, IgnoreSCT: true, SkipTlogVerify: true}) + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{ + RootCerts: rootPool, + IgnoreSCT: true, SkipTlogVerify: true, + Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}}) if err != nil { t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err) } @@ -236,7 +244,7 @@ func TestVerifyImageSignatureWithNoChain(t *testing.T) { t.Fatalf("creating signer: %v", err) } - leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) + leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) rootPool := x509.NewCertPool() @@ -262,6 +270,7 @@ func TestVerifyImageSignatureWithNoChain(t *testing.T) { &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, + Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, RekorPubKeys: &rekorPubKeys}) if err != nil { t.Fatalf("unexpected error %v", err) @@ -273,7 +282,7 @@ func TestVerifyImageSignatureWithNoChain(t *testing.T) { func TestVerifyImageSignatureWithOnlyRoot(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() - leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) + leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) @@ -285,7 +294,12 @@ func TestVerifyImageSignatureWithOnlyRoot(t *testing.T) { signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, pemRoot)) - verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool, IgnoreSCT: true, SkipTlogVerify: true}) + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, + &CheckOpts{ + RootCerts: rootPool, + IgnoreSCT: true, + Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, + SkipTlogVerify: true}) if err != nil { t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err) } @@ -298,7 +312,7 @@ func TestVerifyImageSignatureWithOnlyRoot(t *testing.T) { func TestVerifyImageSignatureWithMissingSub(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) - leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) + leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) @@ -310,7 +324,12 @@ func TestVerifyImageSignatureWithMissingSub(t *testing.T) { signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, pemRoot)) - verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool, IgnoreSCT: true, SkipTlogVerify: true}) + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, + &CheckOpts{ + RootCerts: rootPool, + IgnoreSCT: true, + Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, + SkipTlogVerify: true}) if err == nil { t.Fatal("expected error while verifying signature") } @@ -326,7 +345,7 @@ func TestVerifyImageSignatureWithMissingSub(t *testing.T) { func TestVerifyImageSignatureWithExistingSub(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) - leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) + leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) @@ -346,7 +365,13 @@ func TestVerifyImageSignatureWithExistingSub(t *testing.T) { ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub, pemRoot}))) - verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool, IntermediateCerts: subPool, IgnoreSCT: true, SkipTlogVerify: true}) + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, + &CheckOpts{ + RootCerts: rootPool, + IntermediateCerts: subPool, + IgnoreSCT: true, + Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, + SkipTlogVerify: true}) if err == nil { t.Fatal("expected error while verifying signature") } @@ -422,6 +447,7 @@ func TestVerifyImageSignatureWithSigVerifierAndRekor(t *testing.T) { if _, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{ SigVerifier: sv, RekorClient: mClient, + Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, }); err == nil || !strings.Contains(err.Error(), "no valid tlog entries found no trusted rekor public keys provided") { // This is failing to validate the Rekor public key itself. // At the very least this ensures @@ -533,10 +559,9 @@ func TestValidateAndUnpackCertSuccess(t *testing.T) { rootPool.AddCert(rootCert) co := &CheckOpts{ - RootCerts: rootPool, - CertEmail: subject, - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + RootCerts: rootPool, + IgnoreSCT: true, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, } _, err := ValidateAndUnpackCert(leafCert, co) @@ -560,8 +585,9 @@ func TestValidateAndUnpackCertSuccessAllowAllValues(t *testing.T) { rootPool.AddCert(rootCert) co := &CheckOpts{ - RootCerts: rootPool, - IgnoreSCT: true, + RootCerts: rootPool, + IgnoreSCT: true, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, } _, err := ValidateAndUnpackCert(leafCert, co) @@ -585,9 +611,8 @@ func TestValidateAndUnpackCertWithoutRequiredSCT(t *testing.T) { rootPool.AddCert(rootCert) co := &CheckOpts{ - RootCerts: rootPool, - CertEmail: subject, - CertOidcIssuer: oidcIssuer, + RootCerts: rootPool, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, // explicitly set to false IgnoreSCT: false, } @@ -614,10 +639,9 @@ func TestValidateAndUnpackCertSuccessWithDnsSan(t *testing.T) { rootPool.AddCert(rootCert) co := &CheckOpts{ - RootCerts: rootPool, - CertIdentity: subject, - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + RootCerts: rootPool, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) @@ -648,10 +672,9 @@ func TestValidateAndUnpackCertSuccessWithEmailSan(t *testing.T) { rootPool.AddCert(rootCert) co := &CheckOpts{ - RootCerts: rootPool, - CertIdentity: subject, - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + RootCerts: rootPool, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) @@ -682,10 +705,9 @@ func TestValidateAndUnpackCertSuccessWithIpAddressSan(t *testing.T) { rootPool.AddCert(rootCert) co := &CheckOpts{ - RootCerts: rootPool, - CertIdentity: subject, - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + RootCerts: rootPool, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) @@ -716,10 +738,9 @@ func TestValidateAndUnpackCertSuccessWithUriSan(t *testing.T) { rootPool.AddCert(rootCert) co := &CheckOpts{ - RootCerts: rootPool, - CertIdentity: "scheme://userinfo@host", - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + RootCerts: rootPool, + Identities: []Identity{{Subject: "scheme://userinfo@host", Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) @@ -750,10 +771,9 @@ func TestValidateAndUnpackCertSuccessWithOtherNameSan(t *testing.T) { rootPool.AddCert(rootCert) co := &CheckOpts{ - RootCerts: rootPool, - CertIdentity: subject, - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + RootCerts: rootPool, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err = ValidateAndUnpackCert(leafCert, co) @@ -779,10 +799,9 @@ func TestValidateAndUnpackCertInvalidRoot(t *testing.T) { rootPool.AddCert(otherRoot) co := &CheckOpts{ - RootCerts: rootPool, - CertEmail: subject, - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + RootCerts: rootPool, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) @@ -800,16 +819,15 @@ func TestValidateAndUnpackCertInvalidOidcIssuer(t *testing.T) { rootPool.AddCert(rootCert) co := &CheckOpts{ - RootCerts: rootPool, - CertEmail: subject, - CertOidcIssuer: "other", - IgnoreSCT: true, + RootCerts: rootPool, + Identities: []Identity{{Subject: subject, Issuer: "other"}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) - require.Contains(t, err.Error(), "expected oidc issuer not found in certificate") + require.Contains(t, err.Error(), "none of the expected identities matched what was in the certificate") err = CheckCertificatePolicy(leafCert, co) - require.Contains(t, err.Error(), "expected oidc issuer not found in certificate") + require.Contains(t, err.Error(), "none of the expected identities matched what was in the certificate") } func TestValidateAndUnpackCertInvalidEmail(t *testing.T) { @@ -823,16 +841,15 @@ func TestValidateAndUnpackCertInvalidEmail(t *testing.T) { rootPool.AddCert(rootCert) co := &CheckOpts{ - RootCerts: rootPool, - CertEmail: "other", - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + RootCerts: rootPool, + Identities: []Identity{{Subject: "other", Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) - require.Contains(t, err.Error(), "expected identity not found in certificate") + require.Contains(t, err.Error(), "none of the expected identities matched what was in the certificate") err = CheckCertificatePolicy(leafCert, co) - require.Contains(t, err.Error(), "expected identity not found in certificate") + require.Contains(t, err.Error(), "none of the expected identities matched what was in the certificate") } func TestValidateAndUnpackCertInvalidGithubWorkflowTrigger(t *testing.T) { @@ -848,9 +865,8 @@ func TestValidateAndUnpackCertInvalidGithubWorkflowTrigger(t *testing.T) { co := &CheckOpts{ RootCerts: rootPool, - CertEmail: subject, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, CertGithubWorkflowTrigger: "otherTrigger", - CertOidcIssuer: oidcIssuer, IgnoreSCT: true, } @@ -873,9 +889,8 @@ func TestValidateAndUnpackCertInvalidGithubWorkflowSHA(t *testing.T) { co := &CheckOpts{ RootCerts: rootPool, - CertEmail: subject, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, CertGithubWorkflowSha: "otherSHA", - CertOidcIssuer: oidcIssuer, IgnoreSCT: true, } @@ -898,9 +913,8 @@ func TestValidateAndUnpackCertInvalidGithubWorkflowName(t *testing.T) { co := &CheckOpts{ RootCerts: rootPool, - CertEmail: subject, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, CertGithubWorkflowName: "otherName", - CertOidcIssuer: oidcIssuer, IgnoreSCT: true, } @@ -923,9 +937,8 @@ func TestValidateAndUnpackCertInvalidGithubWorkflowRepository(t *testing.T) { co := &CheckOpts{ RootCerts: rootPool, - CertEmail: subject, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, CertGithubWorkflowRepository: "otherRepository", - CertOidcIssuer: oidcIssuer, IgnoreSCT: true, } @@ -948,9 +961,8 @@ func TestValidateAndUnpackCertInvalidGithubWorkflowRef(t *testing.T) { co := &CheckOpts{ RootCerts: rootPool, - CertEmail: subject, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, CertGithubWorkflowRef: "otherRef", - CertOidcIssuer: oidcIssuer, IgnoreSCT: true, } @@ -969,9 +981,8 @@ func TestValidateAndUnpackCertWithChainSuccess(t *testing.T) { leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, subCert, subKey) co := &CheckOpts{ - CertEmail: subject, - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{subCert, leafCert}, co) @@ -988,9 +999,8 @@ func TestValidateAndUnpackCertWithChainSuccessWithRoot(t *testing.T) { leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) co := &CheckOpts{ - CertEmail: subject, - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{rootCert}, co) @@ -1007,9 +1017,8 @@ func TestValidateAndUnpackCertWithChainFailsWithoutChain(t *testing.T) { leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) co := &CheckOpts{ - CertEmail: subject, - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{}, co) @@ -1027,9 +1036,8 @@ func TestValidateAndUnpackCertWithChainFailsWithInvalidChain(t *testing.T) { rootCertOther, _, _ := test.GenerateRootCa() co := &CheckOpts{ - CertEmail: subject, - CertOidcIssuer: oidcIssuer, - IgnoreSCT: true, + Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, + IgnoreSCT: true, } _, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{rootCertOther}, co) @@ -1129,6 +1137,7 @@ func TestValidateAndUnpackCertWithIdentities(t *testing.T) { Identities: tc.identities, IgnoreSCT: true, } + _, err := ValidateAndUnpackCert(leafCert, co) if err == nil && tc.wantErrSubstring != "" { t.Errorf("Expected error %s got none", tc.wantErrSubstring) @@ -1192,7 +1201,7 @@ func TestCompareSigs(t *testing.T) { func TestTrustedCertSuccess(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) - leafCert, _, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) + leafCert, _, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) @@ -1213,7 +1222,7 @@ func TestTrustedCertSuccess(t *testing.T) { func TestTrustedCertSuccessNoIntermediates(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() - leafCert, _, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) + leafCert, _, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) @@ -1228,7 +1237,7 @@ func TestTrustedCertSuccessNoIntermediates(t *testing.T) { // present, but a chain is built with only the leaf and root certificates. func TestTrustedCertSuccessChainFromRoot(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() - leafCert, _, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) + leafCert, _, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) subCert, _, _ := test.GenerateSubordinateCa(rootCert, rootKey) rootPool := x509.NewCertPool() @@ -1252,7 +1261,7 @@ func Test_getSubjectAltnernativeNames(t *testing.T) { t.Fatalf("error marshalling SANs: %v", err) } exts := []pkix.Extension{*ext} - leafCert, _, _ := test.GenerateLeafCert("unused", "oidc-issuer", subCert, subKey, exts...) + leafCert, _, _ := test.GenerateLeafCert("unused@mail.com", "oidc-issuer", subCert, subKey, exts...) sans := getSubjectAlternateNames(leafCert) if len(sans) != 1 { diff --git a/test/e2e_test.sh b/test/e2e_test.sh index 594eba6503a..c7872ae420d 100755 --- a/test/e2e_test.sh +++ b/test/e2e_test.sh @@ -48,18 +48,18 @@ go test -tags=e2e -race $(go list ./... | grep -v third_party/) # Test `cosign dockerfile verify` export COSIGN_EXPERIMENTAL=true -./cosign dockerfile verify ./test/testdata/single_stage.Dockerfile -if (./cosign dockerfile verify ./test/testdata/unsigned_build_stage.Dockerfile); then false; fi -./cosign dockerfile verify --base-image-only ./test/testdata/unsigned_build_stage.Dockerfile -./cosign dockerfile verify ./test/testdata/fancy_from.Dockerfile -test_image="ghcr.io/distroless/alpine-base" ./cosign dockerfile verify ./test/testdata/with_arg.Dockerfile +./cosign dockerfile verify ./test/testdata/single_stage.Dockerfile --certificate-identity https://github.com/distroless/alpine-base/.github/workflows/release.yaml@refs/heads/main --certificate-oidc-issuer https://token.actions.githubusercontent.com +if (./cosign dockerfile verify ./test/testdata/unsigned_build_stage.Dockerfile --certificate-identity https://github.com/distroless/alpine-base/.github/workflows/release.yaml@refs/heads/main --certificate-oidc-issuer https://token.actions.githubusercontent.com); then false; fi +./cosign dockerfile verify --base-image-only ./test/testdata/unsigned_build_stage.Dockerfile --certificate-identity https://github.com/distroless/static/.github/workflows/release.yaml@refs/heads/main --certificate-oidc-issuer https://token.actions.githubusercontent.com +./cosign dockerfile verify ./test/testdata/fancy_from.Dockerfile --certificate-identity https://github.com/distroless/alpine-base/.github/workflows/release.yaml@refs/heads/main --certificate-oidc-issuer https://token.actions.githubusercontent.com +test_image="ghcr.io/distroless/alpine-base" ./cosign dockerfile verify ./test/testdata/with_arg.Dockerfile --certificate-identity https://github.com/distroless/alpine-base/.github/workflows/release.yaml@refs/heads/main --certificate-oidc-issuer https://token.actions.githubusercontent.com # Image exists, but is unsigned -if (test_image="ubuntu" ./cosign dockerfile verify ./test/testdata/with_arg.Dockerfile); then false; fi -./cosign dockerfile verify ./test/testdata/with_lowercase.Dockerfile +if (test_image="ubuntu" ./cosign dockerfile verify ./test/testdata/with_arg.Dockerfile --certificate-identity https://github.com/distroless/alpine-base/.github/workflows/release.yaml@refs/heads/main --certificate-oidc-issuer https://token.actions.githubusercontent.com); then false; fi +./cosign dockerfile verify ./test/testdata/with_lowercase.Dockerfile --certificate-identity https://github.com/distroless/alpine-base/.github/workflows/release.yaml@refs/heads/main --certificate-oidc-issuer https://token.actions.githubusercontent.com # Test `cosign manifest verify` -./cosign manifest verify ./test/testdata/signed_manifest.yaml -if (./cosign manifest verify ./test/testdata/unsigned_manifest.yaml); then false; fi +./cosign manifest verify ./test/testdata/signed_manifest.yaml --certificate-identity https://github.com/distroless/alpine-base/.github/workflows/release.yaml@refs/heads/main --certificate-oidc-issuer https://token.actions.githubusercontent.com +if (./cosign manifest verify ./test/testdata/unsigned_manifest.yaml --certificate-identity https://github.com/distroless/alpine-base/.github/workflows/release.yaml@refs/heads/main --certificate-oidc-issuer https://token.actions.githubusercontent.com); then false; fi # Run the built container to make sure it doesn't crash make ko-local diff --git a/test/sign_blob_test.sh b/test/sign_blob_test.sh index f7389488964..298cc791eb8 100755 --- a/test/sign_blob_test.sh +++ b/test/sign_blob_test.sh @@ -33,10 +33,9 @@ echo "Sign the blob with cosign first and upload to rekor" $COSIGN_CLI sign-blob --output-certificate blob.cert --output-signature blob.sig $BLOB echo "Verifying ..." -$COSIGN_CLI verify-blob --signature blob.sig --cert blob.cert $BLOB +$COSIGN_CLI verify-blob --signature blob.sig --cert blob.cert --certificate-identity-regexp '.*' --certificate-oidc-issuer-regexp '.*' $BLOB echo "Verifying using cosign ENV variables..." -COSIGN_SIGNATURE=blob.sig COSIGN_CERTIFICATE=blob.cert $COSIGN_CLI verify-blob $BLOB - +COSIGN_SIGNATURE=blob.sig COSIGN_CERTIFICATE=blob.cert $COSIGN_CLI verify-blob --certificate-identity-regexp '.*' --certificate-oidc-issuer-regexp '.*' $BLOB # Now, sign the blob with a self-signed certificate and upload to rekor SIG_FILE=verify-experimental-signature @@ -81,7 +80,7 @@ curl -X POST https://rekor.sigstore.dev/api/v1/log/entries -H 'Content-Type: app # Verifying should still work echo "Verifying ..." -$COSIGN_CLI verify-blob --signature "$SIG_FILE" --cert "$CERT_FILE" --certificate-chain "$CERT_FILE" --insecure-ignore-sct "$BLOB" +$COSIGN_CLI verify-blob --signature "$SIG_FILE" --cert "$CERT_FILE" --certificate-chain "$CERT_FILE" --insecure-ignore-sct --certificate-identity-regexp '.*' --certificate-oidc-issuer-regexp '.*' "$BLOB" echo "Verifying using cosign ENV variables ..." -COSIGN_SIGNATURE="$SIG_FILE" COSIGN_CERTIFICATE_CHAIN="$CERT_FILE" COSIGN_CERTIFICATE="$CERT_FILE" $COSIGN_CLI verify-blob --insecure-ignore-sct "$BLOB" +COSIGN_SIGNATURE="$SIG_FILE" COSIGN_CERTIFICATE_CHAIN="$CERT_FILE" COSIGN_CERTIFICATE="$CERT_FILE" $COSIGN_CLI verify-blob --insecure-ignore-sct --certificate-identity-regexp '.*' --certificate-oidc-issuer-regexp '.*' "$BLOB" diff --git a/test/testdata/README.md b/test/testdata/README.md index baf802653d7..e10e9d9b3db 100644 --- a/test/testdata/README.md +++ b/test/testdata/README.md @@ -4,22 +4,7 @@ If the `test/testdata/test_blob_cert.pem` expire you can generate a new certific following command: ```shell -$ openssl req -key test/testdata/test_blob_private_key -x509 -days 3650 -out cert.pem -new -nodes - -You are about to be asked to enter information that will be incorporated -into your certificate request. -What you are about to enter is what is called a Distinguished Name or a DN. -There are quite a few fields but you can leave some blank -For some fields there will be a default value, -If you enter '.', the field will be left blank. ------ -Country Name (2 letter code) []:US -State or Province Name (full name) []:CA -Locality Name (eg, city) []:SF -Organization Name (eg, company) []:Company -Organizational Unit Name (eg, section) []:Unit -Common Name (eg, fully qualified host name) []:www.example.org -Email Address []:email@email.com +$ openssl req -key test/testdata/test_blob_private_key -x509 -days 3650 -out cert.pem -new -nodes -subj "/" -addext "subjectAltName = email:foo@example.com" ``` and then you replace the old `test/testdata/test_blob_cert.pem` with the new certificate. diff --git a/test/testdata/test_blob_cert.pem b/test/testdata/test_blob_cert.pem index 9da992fc623..72d042b4983 100644 --- a/test/testdata/test_blob_cert.pem +++ b/test/testdata/test_blob_cert.pem @@ -1,13 +1,10 @@ -----BEGIN CERTIFICATE----- -MIIB9zCCAZwCCQCtaU3hib3CkTAKBggqhkjOPQQDAjCBgjELMAkGA1UEBhMCVVMx -CzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEQMA4GA1UECgwHQ29tcGFueTENMAsG -A1UECwwEVW5pdDEYMBYGA1UEAwwPd3d3LmV4YW1wbGUub3JnMR4wHAYJKoZIhvcN -AQkBFg9lbWFpbEBlbWFpbC5jb20wHhcNMjIwNzA2MTQyMzU5WhcNMzIwNzAzMTQy -MzU5WjCBgjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEQ -MA4GA1UECgwHQ29tcGFueTENMAsGA1UECwwEVW5pdDEYMBYGA1UEAwwPd3d3LmV4 -YW1wbGUub3JnMR4wHAYJKoZIhvcNAQkBFg9lbWFpbEBlbWFpbC5jb20wWTATBgcq -hkjOPQIBBggqhkjOPQMBBwNCAAR1Q4hB1jtagrdsVxygtDa/rli00U7n/1I/NSw8 -yoMRQ+MOAjRhg3gtcV0tha34L6150qJirQHbfocsao8X6wFmMAoGCCqGSM49BAMC -A0kAMEYCIQDperCsZxqQRZXSMk4DiJCxSQfT+gaX+aLbhOS1AoTbGQIhAO22bQ87 -9ngud/Klrih6bm4rde6oLtfVB+12wSetEqpd +MIIBdDCCARqgAwIBAgIUZw7gQ6T/IgmiMD1AWB2OTIIVH1owCgYIKoZIzj0EAwIw +ADAeFw0yMjEyMjEwMDIwNThaFw0zMjEyMTgwMDIwNThaMAAwWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAAR1Q4hB1jtagrdsVxygtDa/rli00U7n/1I/NSw8yoMRQ+MO +AjRhg3gtcV0tha34L6150qJirQHbfocsao8X6wFmo3IwcDAdBgNVHQ4EFgQUx3Wb +0LwCWoGsl0FUpeQb3M4MukkwHwYDVR0jBBgwFoAUx3Wb0LwCWoGsl0FUpeQb3M4M +ukkwEgYDVR0TAQH/BAgwBgEB/wIBATAaBgNVHREEEzARgQ9mb29AZXhhbXBsZS5j +b20wCgYIKoZIzj0EAwIDSAAwRQIhALXG7XS5TIFLp+jLSxjuRk1Tj5MfE+y9x92Z +YPMbi9GZAiAmfEe0+q5l3PnI6zliOG5kG6EcS80QQgQmPcFvRZWOvw== -----END CERTIFICATE-----