CVE-2022-35929: Improper Verification of Cryptographic Signature (CWE-347)
value: cosign is a container signing and verification utility. In versions prior to 1.10.1 cosign can report a false positive if any attestation exists. `cosign verify-attestation` used with the `--type` flag will report a false positive verification when there is at least one attestation with a valid signature and there are NO attestations of the type being verified (--type defaults to "custom"). This can happen when signing with a standard keypair and with "keyless" signing with Fulcio. This vulnerability can be reproduced with the `distroless.dev/static@sha256:dd7614b5a12bc4d617b223c588b4e0c833402b8f4991fb5702ea83afad1986e2` image. This image has a `vuln` attestation but not an `spdx` attestation. However, if you run `cosign verify-attestation --type=spdx` on this image, it incorrectly succeeds. This issue has been addressed in version 1.10.1 of cosign. Users are advised to upgrade. There are no known workarounds for this issue.
CWE description: The product does not verify, or incorrectly verifies, the cryptographic signature for data.
CVSS v3 base score: 9.8
Published: 2022-08-04T19:15Z
Repository: https://github.com/sigstore/cosign
Language: Go
Files changed: cmd/cosign/cli/verify/verify_attestation.go
Commit date: 2022-08-04 09:05:27 -0700
Commit message: Merge pull request from GHSA-vjxv-45g9-9296

Today the verification logic:
1. Verifies signatures on attestations (at least one must verify, or it errors),
2. All attestations matching the specified `--type` must pass any specified Cue/Rego policies,
3. *All* signature-verified attestations are then printed.

However, if NONE of the attestations match the specified `--type` then `2.` is considered satisfied and we proceed to `3.`

This changes the above logic to:
1. Same.
2. Same, but these are put into a `checked` list,
3. `checked` must be non-empty (or an error is printed about no attestations matching `--type`),
4. *Just* the `checked` attestations are printed.

---

The bug at HEAD:
```shell
$ cosign verify-attestation --type spdx ghcr.io/distroless/static@sha256:dd7614b5a12bc4d617b223c588b4e0c833402b8f4991fb5702ea83afad1986e2

Verification for ghcr.io/distroless/static@sha256:dd7614b5a12bc4d617b223c588b4e0c833402b8f4991fb5702ea83afad1986e2 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - Any certificates were verified against the Fulcio roots.
Certificate subject:  https://github.com/distroless/static/.github/workflows/release.yaml@refs/heads/main
Certificate issuer URL:  https://token.actions.githubusercontent.com
Certificate extension GitHub Workflow Trigger: schedule
Certificate extension GitHub Workflow SHA: 7e7572e578de7c51a2f1a1791f025cf315503aa2
Certificate extension GitHub Workflow Name: Create Release
Certificate extension GitHub Workflow Trigger distroless/static
Certificate extension GitHub Workflow Ref: refs/heads/main
{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3Z1bG4vdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2hjci5pby9kaXN0cm9sZXNzL3N0YXRpYyIsImRpZ2VzdCI6eyJzaGEyNTYiOiJkZDc2MTRiNWExMmJjNGQ2MTdiMjIzYzU4OGI0ZTBjODMzNDAyYjhmNDk5MWZiNTcwMmVhODNhZmFkMTk4NmUyIn19XSwicHJlZGljYXRlIjp7Imludm9jYXRpb24iOnsicGFyYW1ldGVycyI6bnVsbCwidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2Rpc3Ryb2xlc3Mvc3RhdGljL2FjdGlvbnMvcnVucy8yNzc5MjEyNzA1IiwiZXZlbnRfaWQiOiIyNzc5MjEyNzA1IiwiYnVpbGRlci5pZCI6IkNyZWF0ZSBSZWxlYXNlIn0sInNjYW5uZXIiOnsidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2FxdWFzZWN1cml0eS90cml2eSIsInZlcnNpb24iOiIwLjI5LjIiLCJkYiI6eyJ1cmkiOiIiLCJ2ZXJzaW9uIjoiIn0sInJlc3VsdCI6eyIkc2NoZW1hIjoiaHR0cHM6Ly9qc29uLnNjaGVtYXN0b3JlLm9yZy9zYXJpZi0yLjEuMC1ydG0uNS5qc29uIiwicnVucyI6W3siY29sdW1uS2luZCI6InV0ZjE2Q29kZVVuaXRzIiwib3JpZ2luYWxVcmlCYXNlSWRzIjp7IlJPT1RQQVRIIjp7InVyaSI6ImZpbGU6Ly8vIn19LCJyZXN1bHRzIjpbXSwidG9vbCI6eyJkcml2ZXIiOnsiZnVsbE5hbWUiOiJUcml2eSBWdWxuZXJhYmlsaXR5IFNjYW5uZXIiLCJpbmZvcm1hdGlvblVyaSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hcXVhc2VjdXJpdHkvdHJpdnkiLCJuYW1lIjoiVHJpdnkiLCJydWxlcyI6W10sInZlcnNpb24iOiIwLjI5LjIifX19XSwidmVyc2lvbiI6IjIuMS4wIn19LCJtZXRhZGF0YSI6eyJzY2FuU3RhcnRlZE9uIjoiMjAyMi0wOC0wMlQwMjozMzo0N1oiLCJzY2FuRmluaXNoZWRPbiI6IjIwMjItMDgtMDJUMDI6MzM6NTNaIn19fQ==","signatures":[{"keyid":"","sig":"MEYCIQCovBtLOBXyB2zpvhp3j6QzqLtsH0/RC7fRINSApySqxAIhAIKlzu1fXuKPPOIheNnsPmBOB6XfZbRs5sDW1yFSch1A"}]}
```

The same with this change:
```shell
$ go run ./cmd/cosign verify-attestation --type spdx ghcr.io/distroless/static@sha256:dd7614b5a12bc4d617b223c588b4e0c833402b8f4991fb5702ea83afad1986e2
Error: none of the attestations matched the predicate type: spdx
main.go:62: error during command execution: none of the attestations matched the predicate type: spdx
exit status 1
```

A valid `--type` with this change:
```shell
$ go run ./cmd/cosign verify-attestation --type vuln ghcr.io/distroless/static@sha256:dd7614b5a12bc4d617b223c588b4e0c833402b8f4991fb5702ea83afad1986e2

Verification for ghcr.io/distroless/static@sha256:dd7614b5a12bc4d617b223c588b4e0c833402b8f4991fb5702ea83afad1986e2 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - Any certificates were verified against the Fulcio roots.
Certificate subject:  https://github.com/distroless/static/.github/workflows/release.yaml@refs/heads/main
Certificate issuer URL:  https://token.actions.githubusercontent.com
Certificate extension GitHub Workflow Trigger: schedule
Certificate extension GitHub Workflow SHA: 7e7572e578de7c51a2f1a1791f025cf315503aa2
Certificate extension GitHub Workflow Name: Create Release
Certificate extension GitHub Workflow Trigger distroless/static
Certificate extension GitHub Workflow Ref: refs/heads/main
{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3Z1bG4vdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2hjci5pby9kaXN0cm9sZXNzL3N0YXRpYyIsImRpZ2VzdCI6eyJzaGEyNTYiOiJkZDc2MTRiNWExMmJjNGQ2MTdiMjIzYzU4OGI0ZTBjODMzNDAyYjhmNDk5MWZiNTcwMmVhODNhZmFkMTk4NmUyIn19XSwicHJlZGljYXRlIjp7Imludm9jYXRpb24iOnsicGFyYW1ldGVycyI6bnVsbCwidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2Rpc3Ryb2xlc3Mvc3RhdGljL2FjdGlvbnMvcnVucy8yNzc5MjEyNzA1IiwiZXZlbnRfaWQiOiIyNzc5MjEyNzA1IiwiYnVpbGRlci5pZCI6IkNyZWF0ZSBSZWxlYXNlIn0sInNjYW5uZXIiOnsidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2FxdWFzZWN1cml0eS90cml2eSIsInZlcnNpb24iOiIwLjI5LjIiLCJkYiI6eyJ1cmkiOiIiLCJ2ZXJzaW9uIjoiIn0sInJlc3VsdCI6eyIkc2NoZW1hIjoiaHR0cHM6Ly9qc29uLnNjaGVtYXN0b3JlLm9yZy9zYXJpZi0yLjEuMC1ydG0uNS5qc29uIiwicnVucyI6W3siY29sdW1uS2luZCI6InV0ZjE2Q29kZVVuaXRzIiwib3JpZ2luYWxVcmlCYXNlSWRzIjp7IlJPT1RQQVRIIjp7InVyaSI6ImZpbGU6Ly8vIn19LCJyZXN1bHRzIjpbXSwidG9vbCI6eyJkcml2ZXIiOnsiZnVsbE5hbWUiOiJUcml2eSBWdWxuZXJhYmlsaXR5IFNjYW5uZXIiLCJpbmZvcm1hdGlvblVyaSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hcXVhc2VjdXJpdHkvdHJpdnkiLCJuYW1lIjoiVHJpdnkiLCJydWxlcyI6W10sInZlcnNpb24iOiIwLjI5LjIifX19XSwidmVyc2lvbiI6IjIuMS4wIn19LCJtZXRhZGF0YSI6eyJzY2FuU3RhcnRlZE9uIjoiMjAyMi0wOC0wMlQwMjozMzo0N1oiLCJzY2FuRmluaXNoZWRPbiI6IjIwMjItMDgtMDJUMDI6MzM6NTNaIn19fQ==","signatures":[{"keyid":"","sig":"MEYCIQCovBtLOBXyB2zpvhp3j6QzqLtsH0/RC7fRINSApySqxAIhAIKlzu1fXuKPPOIheNnsPmBOB6XfZbRs5sDW1yFSch1A"}]}
```

Signed-off-by: Matt Moore <mattmoor@chainguard.dev>
Diff: diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go
index cde32f68..01ea6e04 100644
--- a/cmd/cosign/cli/verify/verify_attestation.go
+++ b/cmd/cosign/cli/verify/verify_attestation.go
@@ -199,10 +199,11 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
 			default:
 				return errors.New("invalid policy format, expected .cue or .rego")
 			}
 		}
 
+		var checked []oci.Signature
 		var validationErrors []error
 		for _, vp := range verified {
 			payload, err := policy.AttestationToPayloadJSON(ctx, c.PredicateType, vp)
 			if err != nil {
 				return fmt.Errorf("converting to consumable policy validation: %w", err)
@@ -215,33 +216,41 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
 			if len(cuePolicies) > 0 {
 				fmt.Fprintf(os.Stderr, "will be validating against CUE policies: %v\n", cuePolicies)
 				cueValidationErr := cue.ValidateJSON(payload, cuePolicies)
 				if cueValidationErr != nil {
 					validationErrors = append(validationErrors, cueValidationErr)
+					continue
 				}
 			}
 
 			if len(regoPolicies) > 0 {
 				fmt.Fprintf(os.Stderr, "will be validating against Rego policies: %v\n", regoPolicies)
 				regoValidationErrs := rego.ValidateJSON(payload, regoPolicies)
 				if len(regoValidationErrs) > 0 {
 					validationErrors = append(validationErrors, regoValidationErrs...)
+					continue
 				}
 			}
+
+			checked = append(checked, vp)
 		}
 
 		if len(validationErrors) > 0 {
 			fmt.Fprintf(os.Stderr, "There are %d number of errors occurred during the validation:\n", len(validationErrors))
 			for _, v := range validationErrors {
 				_, _ = fmt.Fprintf(os.Stderr, "- %v\n", v)
 			}
 			return fmt.Errorf("%d validation errors occurred", len(validationErrors))
 		}
 
+		if len(checked) == 0 {
+			return fmt.Errorf("none of the attestations matched the predicate type: %s", c.PredicateType)
+		}
+
 		// TODO: add CUE validation report to `PrintVerificationHeader`.
 		PrintVerificationHeader(imageRef, co, bundleVerified, fulcioVerified)
 		// The attestations are always JSON, so use the raw "text" mode for outputting them instead of conversion
-		PrintVerification(imageRef, verified, "text")
+		PrintVerification(imageRef, checked, "text")
 	}
 
 	return nil
 }

Vulnerable code: 		PrintVerification(imageRef, verified, "text")
Fixed code: 		var checked []oci.Signature
					continue
					continue

			checked = append(checked, vp)
		if len(checked) == 0 {
			return fmt.Errorf("none of the attestations matched the predicate type: %s", c.PredicateType)
		}

		PrintVerification(imageRef, checked, "text")