CVE-2022-36056 (Go, published 2022-09-14T20:15Z)
Description: value: Cosign is a project under the sigstore organization which aims to make signatures invisible infrastructure. In versions prior to 1.12.0 a number of vulnerabilities have been found in cosign verify-blob, where Cosign would successfully verify an artifact when verification should have failed. First a cosign bundle can be crafted to successfully verify a blob even if the embedded rekorBundle does not reference the given signature. Second, when providing identity flags, the email and issuer of a certificate is not checked when verifying a Rekor bundle, and the GitHub Actions identity is never checked. Third, providing an invalid Rekor bundle without the experimental flag results in a successful verification. And fourth an invalid transparency log entry will result in immediate success for verification. Details and examples of these issues can be seen in the GHSA-8gw7-4j42-w388 advisory linked. Users are advised to upgrade to 1.12.0. There are no known workarounds for these issues.
Severity: nan (CVSS v3 base score: 5.5)
Weakness: CWE-347 \u2014 Improper Verification of Cryptographic Signature
The product does not verify, or incorrectly verifies, the cryptographic signature for data.
Repository: https://github.com/sigstore/cosign
Files changed:
cmd/cosign/cli/verify/verify_blob.go
cmd/cosign/cli/verify/verify_blob_test.go
internal/pkg/cosign/rekor/mock/mock_rekor_client.go
internal/pkg/cosign/rekor/signer_test.go
pkg/cosign/verify.go
pkg/cosign/verify_test.go
test/cert_utils.go
Commit message: Merge pull request from GHSA-8gw7-4j42-w388

* wip

Signed-off-by: Asra Ali <asraa@google.com>

more tests

Signed-off-by: Asra Ali <asraa@google.com>

finish tests

Signed-off-by: Asra Ali <asraa@google.com>

Add explicit testcase for failure in verifytlogentry

Signed-off-by: Asra Ali <asraa@google.com>

add testing for invalid provided bundle fails

Signed-off-by: Asra Ali <asraa@google.com>

update

Signed-off-by: Asra Ali <asraa@google.com>

address hayden comments

Signed-off-by: Asra Ali <asraa@google.com>

update

Signed-off-by: Asra Ali <asraa@google.com>

* fix: verify RekorBundle payload references blob

Co-authored-by: Cody Soyland <codysoyland@github.com>
Co-authored-by: Asra Ali <asraa@google.com>

* Add test for invalid blob signature causing error

Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>

* Add tests for checking identity flags

Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>

* address bob's comment

Signed-off-by: Asra Ali <asraa@google.com>

* add comment on intoto multisig

Signed-off-by: Asra Ali <asraa@google.com>

Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>
Co-authored-by: Cody Soyland <codysoyland@github.com>
Co-authored-by: Hayden Blauzvern <hblauzvern@google.com>

Vulnerable code:
	_ "crypto/sha256" // for `crypto.SHA256`
	hashedrekord "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1"
	rekord "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
	"github.com/sigstore/sigstore/pkg/signature"
	var verifier signature.Verifier
	sig, b64sig, err := signatures(sigRef, ko.BundlePath)
		verifier, err = sigs.PublicKeyFromKeyRef(ctx, ko.KeyRef)
		pkcs11Key, ok := verifier.(*pkcs11key.Key)
		verifier, err = sk.Verifier()
		co := &cosign.CheckOpts{
			CertEmail:                    certEmail,
			CertOidcIssuer:               certOidcIssuer,
			CertGithubWorkflowTrigger:    certGithubWorkflowTrigger,
			CertGithubWorkflowSha:        certGithubWorkflowSha,
			CertGithubWorkflowName:       certGithubWorkflowName,
			CertGithubWorkflowRepository: certGithubWorkflowRepository,
			CertGithubWorkflowRef:        certGithubWorkflowRef,
			EnforceSCT:                   enforceSCT,
		}
			// If no certChain is passed, the Fulcio root certificate will be used
			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)
			}
			verifier, err = cosign.ValidateAndUnpackCert(cert, co)
			verifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co)
		// cert can either be a cert or public key
			verifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256)
			verifier, err = signature.LoadVerifier(cert.PublicKey, crypto.SHA256)
		rClient, err := rekor.NewClient(ko.RekorURL)
		if err != nil {
			return err
		}

		uuids, err := cosign.FindTLogEntriesByPayload(ctx, rClient, blobBytes)
		return verifySigByUUID(ctx, ko, rClient, certEmail, certOidcIssuer, sig, b64sig, uuids, blobBytes, enforceSCT)
	}
	// Use the DSSE verifier if the payload is a DSSE with the In-Toto format.
	if isIntotoDSSE(blobBytes) {
		verifier = dsse.WrapVerifier(verifier)
	}
	// verify the signature
	if err := verifier.VerifySignature(bytes.NewReader([]byte(sig)), bytes.NewReader(blobBytes)); err != nil {
		return err
	// verify the rekor entry
	if err := verifyRekorEntry(ctx, ko, nil, verifier, cert, b64sig, blobBytes); err != nil {
func verifySigByUUID(ctx context.Context, ko options.KeyOpts, rClient *client.Rekor, certEmail, certOidcIssuer, sig, b64sig string,
	uuids []string, blobBytes []byte, enforceSCT bool) error {
	var validSigExists bool
	for _, u := range uuids {
		tlogEntry, err := cosign.GetTlogEntry(ctx, rClient, u)
			continue
		certs, err := extractCerts(tlogEntry)
		if err != nil {
			continue
		}
		co := &cosign.CheckOpts{
			CertEmail:      certEmail,
			CertOidcIssuer: certOidcIssuer,
			EnforceSCT:     enforceSCT,
		}
		co.RootCerts, err = fulcio.GetRoots()
		if err != nil {
			return fmt.Errorf("getting Fulcio roots: %w", err)
		co.IntermediateCerts, err = fulcio.GetIntermediates()
			return fmt.Errorf("getting Fulcio intermediates: %w", err)
		cert := certs[0]
		verifier, err := cosign.ValidateAndUnpackCert(cert, co)
			continue
		}
		// Use the DSSE verifier if the payload is a DSSE with the In-Toto format.
		if isIntotoDSSE(blobBytes) {
			verifier = dsse.WrapVerifier(verifier)
		}
		// verify the signature
		if err := verifier.VerifySignature(bytes.NewReader([]byte(sig)), bytes.NewReader(blobBytes)); err != nil {
			continue
		// verify the rekor entry
		if err := verifyRekorEntry(ctx, ko, tlogEntry, verifier, cert, b64sig, blobBytes); err != nil {
			continue
		}
		validSigExists = true
	if !validSigExists {
		fmt.Fprintln(os.Stderr, `WARNING: No valid entries were found in rekor to verify this blob.
Transparency log support for blobs is experimental, and occasionally an entry isn't found even if one exists.
We recommend requesting the certificate/signature from the original signer of this blob and manually verifying with cosign verify-blob --cert [cert] --signature [signature].`)
		return fmt.Errorf("could not find a valid tlog entry for provided blob, found %d invalid entries", len(uuids))
	fmt.Fprintln(os.Stderr, "Verified OK")
	return nil
// signatures returns the raw signature and the base64 encoded signature
func signatures(sigRef string, bundlePath string) (string, string, error) {
				return "", "", err
			return "", "", err
		return "", "", fmt.Errorf("missing flag '--signature'")
	return sig, b64sig, nil
func verifyRekorEntry(ctx context.Context, ko options.KeyOpts, e *models.LogEntryAnon, pubKey signature.Verifier, cert *x509.Certificate, b64sig string, blobBytes []byte) error {
	// TODO: This can be moved below offline bundle verification when SIGSTORE_TRUST_REKOR_API_PUBLIC_KEY
	// is removed.
	rekorClient, err := rekor.NewClient(ko.RekorURL)
		return err
	}

	// If we have a bundle with a rekor entry, let's first try to verify offline
	if ko.BundlePath != "" {
		if err := verifyRekorBundle(ctx, ko.BundlePath, cert, rekorClient); err == nil {
			fmt.Fprintf(os.Stderr, "tlog entry verified offline\n")
			return nil
		}
	if !options.EnableExperimental() {
		return nil
	// Only fetch from rekor tlog if we don't already have the entry.
	if e == nil {
		var pubBytes []byte
		if pubKey != nil {
			pubBytes, err = sigs.PublicKeyPem(pubKey, signatureoptions.WithContext(ctx))
			if err != nil {
				return err
			}
		}
		if cert != nil {
			pubBytes, err = cryptoutils.MarshalCertificateToPEM(cert)
			if err != nil {
				return err
			}
		}
		e, err = cosign.FindTlogEntry(ctx, rekorClient, b64sig, blobBytes, pubBytes)
		if err != nil {
			return err
		}
	if err := cosign.VerifyTLogEntry(ctx, rekorClient, e); err != nil {
		return nil
	uuid, err := cosign.ComputeLeafHash(e)
		return err

	fmt.Fprintf(os.Stderr, "tlog entry verified with uuid: %s index: %d\n", hex.EncodeToString(uuid), *e.LogIndex)
	if cert == nil {
		return nil
	// if we have a cert, we should check expiry
	return cosign.CheckExpiry(cert, time.Unix(*e.IntegratedTime, 0))
func verifyRekorBundle(ctx context.Context, bundlePath string, cert *x509.Certificate, rekorClient *client.Rekor) error {
	b, err := cosign.FetchLocalSignedPayloadFromPath(bundlePath)
	if b.Bundle == nil {
		return fmt.Errorf("rekor entry is not available")
	}
	publicKeys, err := cosign.GetRekorPubs(ctx, rekorClient)
		return fmt.Errorf("retrieving rekor public key: %w", err)
	pubKey, ok := publicKeys[b.Bundle.Payload.LogID]
	if !ok {
		return errors.New("rekor log public key not found for payload")
	err = cosign.VerifySET(b.Bundle.Payload, b.Bundle.SignedEntryTimestamp, pubKey.PubKey)
		return err
	if pubKey.Status != tuf.Active {
		fmt.Fprintf(os.Stderr, "**Info** Successfully verified Rekor entry using an expired verification key\n")

	if cert == nil {
		return nil
	it := time.Unix(b.Bundle.Payload.IntegratedTime, 0)
	return cosign.CheckExpiry(cert, it)
func extractCerts(e *models.LogEntryAnon) ([]*x509.Certificate, error) {
	b, err := base64.StdEncoding.DecodeString(e.Body.(string))
		return nil, err
		return nil, err
	eimpl, err := types.NewEntry(pe)
	case *rekord.V001Entry:
	case *hashedrekord.V001Entry:
	"github.com/secure-systems-lab/go-securesystemslib/dsse"
			gotSig, gotb64Sig, err := signatures(test.sigRef, "")
			if gotb64Sig != b64sig {
				t.Fatalf("unexpected encoded signature, expected: %s got: %s", b64sig, gotb64Sig)
			}
	gotSig, gotb64Sig, err := signatures("", fp)
	if gotb64Sig != b64sig {
		t.Fatalf("unexpected encoded signature, expected: %s got: %s", b64sig, gotb64Sig)
	}
		envelope     dsse.Envelope
			envelope: dsse.Envelope{
				Signatures:  []dsse.Signature{},
	"encoding/base64"
	"encoding/hex"
	"github.com/transparency-dev/merkle/rfc6962"
var (
	lea = models.LogEntryAnon{
		Attestation:    &models.LogEntryAnonAttestation{},
		Body:           base64.StdEncoding.EncodeToString([]byte("asdf")),
		IntegratedTime: new(int64),
		LogID:          new(string),
		LogIndex:       new(int64),
		Verification: &models.LogEntryAnonVerification{
			InclusionProof: &models.InclusionProof{
				RootHash: new(string),
				TreeSize: new(int64),
				LogIndex: new(int64),
			},
		},
	}
	data = models.LogEntry{
		uuid(lea): lea,
	}
)

// uuid generates the UUID for the given LogEntry.
// This is effectively a reimplementation of
// pkg/cosign/tlog.go -> verifyUUID / ComputeLeafHash, but separated
// to avoid a circular dependency.
// TODO?: Perhaps we should refactor the tlog libraries into a separate
// package?
func uuid(e models.LogEntryAnon) string {
	entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string))
	if err != nil {
		panic(err)
	}
	return hex.EncodeToString(rfc6962.DefaultHasher.HashLeaf(entryBytes))
}

// mClient.entries = &EntriesClient{}
	Entries models.LogEntry
	return &entries.CreateLogEntryCreated{
		ETag:     "",
		Location: "",
		Payload:  data,
	}, nil
	return &entries.GetLogEntryByIndexOK{
		Payload: data,
	}, nil
	return &entries.GetLogEntryByUUIDOK{
		Payload: data,
	}, nil
		Payload: []models.LogEntry{data},
	mClient.Entries = &mock.EntriesClient{}
		return &VerificationError{"unable to verify"}
	mClient.Entries = &mock.EntriesClient{}
		NotBefore:             time.Now().Add(-5 * time.Minute),

Fixed code:
	"crypto/sha256"
	"strings"
	"github.com/sigstore/cosign/pkg/cosign/bundle"
	"github.com/sigstore/rekor/pkg/pki"
	"github.com/sigstore/rekor/pkg/types/hashedrekord"
	hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1"
	"github.com/sigstore/rekor/pkg/types/intoto"
	intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1"
	"github.com/sigstore/rekor/pkg/types/rekord"
	rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
	sig, err := signatures(sigRef, ko.BundlePath)
	co := &cosign.CheckOpts{
		CertEmail:                    certEmail,
		CertOidcIssuer:               certOidcIssuer,
		CertGithubWorkflowTrigger:    certGithubWorkflowTrigger,
		CertGithubWorkflowSha:        certGithubWorkflowSha,
		CertGithubWorkflowName:       certGithubWorkflowName,
		CertGithubWorkflowRepository: certGithubWorkflowRepository,
		CertGithubWorkflowRef:        certGithubWorkflowRef,
		EnforceSCT:                   enforceSCT,
	}
	if options.EnableExperimental() {
		if ko.RekorURL != "" {
			rekorClient, err := rekor.NewClient(ko.RekorURL)
			if err != nil {
				return fmt.Errorf("creating Rekor client: %w", err)
			}
			co.RekorClient = rekorClient
		}
	}
	if certRef == "" || options.EnableExperimental() {
		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)
		}
	}

		co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, ko.KeyRef)
		pkcs11Key, ok := co.SigVerifier.(*pkcs11key.Key)
		co.SigVerifier, err = sk.Verifier()
			co.SigVerifier, err = cosign.ValidateAndUnpackCert(cert, co)
			co.SigVerifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co)
		// b.Cert can either be a certificate or public key
			co.SigVerifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256)
			co.SigVerifier, err = cosign.ValidateAndUnpackCert(cert, co)
			if err != nil {
				return err
			}
	// No certificate is provided: search by artifact sha in the TLOG.
		uuids, err := cosign.FindTLogEntriesByPayload(ctx, co.RekorClient, blobBytes)
		// Iterate through and try to find a matching Rekor entry.
		// This does not support intoto properly! c/f extractCerts and
		// the verifier.
		for _, u := range uuids {
			tlogEntry, err := cosign.GetTlogEntry(ctx, co.RekorClient, u)
			if err != nil {
				continue
			}

			// Note that this will error out if the TLOG entry was signed with a
			// raw public key. Again, using search on artifact sha is unreliable.
			certs, err := extractCerts(tlogEntry)
			if err != nil {
				continue
			}

			cert := certs[0]
			co.SigVerifier, err = cosign.ValidateAndUnpackCert(cert, co)
			if err != nil {
				continue
			}

			if err := verifyBlob(ctx, co, blobBytes, sig, cert,
				ko.BundlePath, tlogEntry); err == nil {
				// We found a succesful Rekor entry!
				fmt.Fprintln(os.Stderr, "Verified OK")
				return nil
			}
		}

		// No successful Rekor entry found.
		fmt.Fprintln(os.Stderr, `WARNING: No valid entries were found in rekor to verify this blob.

Transparency log support for blobs is experimental, and occasionally an entry isn't found even if one exists.

We recommend requesting the certificate/signature from the original signer of this blob and manually verifying with cosign verify-blob --cert [cert] --signature [signature].`)
		return fmt.Errorf("could not find a valid tlog entry for provided blob, found %d invalid entries", len(uuids))
	// Performs all blob verification.
	if err := verifyBlob(ctx, co, blobBytes, sig, cert, ko.BundlePath, nil); err != nil {
/* Verify Blob main entry point. This will perform the following:
   1. Verifies the signature on the blob using the provided verifier.
   2. Checks for transparency log entry presence:
        a. Verifies the Rekor entry in the bundle, if provided. OR
        b. If we don't have a Rekor entry retrieved via cert, do an online lookup (assuming
           we are in experimental mode).
        c. Uses the provided Rekor entry (may have been retrieved through Rekor SearchIndex) OR
   3. If a certificate is provided, check it's expiration.
*/
// TODO: Make a version of this public. This could be VerifyBlobCmd, but we need to
// clean up the args into CheckOpts or use KeyOpts here to resolve different KeyOpts.
func verifyBlob(ctx context.Context, co *cosign.CheckOpts,
	blobBytes []byte, sig string, cert *x509.Certificate,
	bundlePath string, e *models.LogEntryAnon) error {
	if cert != nil {
		// This would have already be done in the main entrypoint, but do this for robustness.
		var err error
		co.SigVerifier, err = cosign.ValidateAndUnpackCert(cert, co)
			return fmt.Errorf("validating cert: %w", err)
	}
	// Use the DSSE verifier if the payload is a DSSE with the In-Toto format.
	// TODO: This verifier only supports verification of a single signer/signature on
	// the envelope. Either have the verifier validate that only one signature exists,
	// or use a multi-signature verifier.
	if isIntotoDSSE(blobBytes) {
		co.SigVerifier = dsse.WrapVerifier(co.SigVerifier)
	}
	// 1. Verify the signature.
	if err := co.SigVerifier.VerifySignature(strings.NewReader(sig), bytes.NewReader(blobBytes)); err != nil {
		return err
	}
	// This is the signature creation time. Without a transparency log entry timestamp,
	// we can only use the current time as a bound.
	var validityTime time.Time
	// 2. Checks for transparency log entry presence:
	switch {
	// a. We have a local bundle.
	case bundlePath != "":
		var svBytes []byte
		var err error
		if cert != nil {
			svBytes, err = cryptoutils.MarshalCertificateToPEM(cert)
			if err != nil {
				return fmt.Errorf("marshalling cert: %w", err)
			}
		} else {
			svBytes, err = sigs.PublicKeyPem(co.SigVerifier, signatureoptions.WithContext(ctx))
			if err != nil {
				return fmt.Errorf("marshalling pubkey: %w", err)
			}
		bundle, err := verifyRekorBundle(ctx, bundlePath, co.RekorClient, blobBytes, sig, svBytes)
			// Return when the provided bundle fails verification. (Do not fallback).
			return err
		}
		validityTime = time.Unix(bundle.IntegratedTime, 0)
		fmt.Fprintf(os.Stderr, "tlog entry verified offline\n")
	// b. We can make an online lookup to the transparency log since we don't have an entry.
	case co.RekorClient != nil && e == nil:
		var tlogFindErr error
		if cert == nil {
			pub, err := co.SigVerifier.PublicKey(co.PKOpts...)
			if err != nil {
				return err
			}
			e, tlogFindErr = tlogFindPublicKey(ctx, co.RekorClient, blobBytes, sig, pub)
		} else {
			e, tlogFindErr = tlogFindCertificate(ctx, co.RekorClient, blobBytes, sig, cert)
		}
		if tlogFindErr != nil {
			// TODO: Think about whether we should break here.
			// This is COSIGN_EXPERIMENTAL mode, but in the case where someone
			// provided a public key or still-valid cert,
			/// they don't need TLOG lookup for the timestamp.
			fmt.Fprintf(os.Stderr, "could not find entry in tlog: %s", tlogFindErr)
			return tlogFindErr
		}
		// Fallthrough here to verify the TLOG entry and compute the integrated time.
		fallthrough
	// We are provided a log entry, possibly from above, or search.
	case e != nil:
		if err := cosign.VerifyTLogEntry(ctx, co.RekorClient, e); err != nil {
			return err
		uuid, err := cosign.ComputeLeafHash(e)
			return err
		validityTime = time.Unix(*e.IntegratedTime, 0)
		fmt.Fprintf(os.Stderr, "tlog entry verified with uuid: %s index: %d\n", hex.EncodeToString(uuid), *e.LogIndex)
	// If we do not have access to a bundle, a Rekor entry, or the access to lookup,
	// then we can only use the current time as the signature creation time to verify
	// the signature was created when the certificate was valid.
	default:
		validityTime = time.Now()
	// 3. If a certificate is provided, check it's expiration.
	if cert == nil {
		return nil
	}
	return cosign.CheckExpiry(cert, validityTime)
}

func tlogFindPublicKey(ctx context.Context, rekorClient *client.Rekor,
	blobBytes []byte, sig string, pub crypto.PublicKey) (*models.LogEntryAnon, error) {
	pemBytes, err := cryptoutils.MarshalPublicKeyToPEM(pub)
	if err != nil {
		return nil, err
	return tlogFindEntry(ctx, rekorClient, blobBytes, sig, pemBytes)
}

func tlogFindCertificate(ctx context.Context, rekorClient *client.Rekor,
	blobBytes []byte, sig string, cert *x509.Certificate) (*models.LogEntryAnon, error) {
	pemBytes, err := cryptoutils.MarshalCertificateToPEM(cert)
	if err != nil {
		return nil, err
	}
	return tlogFindEntry(ctx, rekorClient, blobBytes, sig, pemBytes)
func tlogFindEntry(ctx context.Context, client *client.Rekor,
	blobBytes []byte, sig string, pem []byte) (*models.LogEntryAnon, error) {
	b64sig := base64.StdEncoding.EncodeToString([]byte(sig))
	return cosign.FindTlogEntry(ctx, client, b64sig, blobBytes, pem)
}

// signatures returns the raw signature
func signatures(sigRef string, bundlePath string) (string, error) {
				return "", err
			return "", err
		return "", fmt.Errorf("missing flag '--signature'")
	return sig, nil
// TODO: RekorClient can be removed when SIGSTORE_TRUST_REKOR_API_PUBLIC_KEY
// is removed.
func verifyRekorBundle(ctx context.Context, bundlePath string, rekorClient *client.Rekor,
	blobBytes []byte, sig string, pubKeyBytes []byte) (*bundle.RekorPayload, error) {
	b, err := cosign.FetchLocalSignedPayloadFromPath(bundlePath)
		return nil, err
	if b.Bundle == nil {
		return nil, fmt.Errorf("rekor entry could not be extracted from local bundle")
	if err := verifyBundleMatchesData(ctx, b.Bundle, blobBytes, pubKeyBytes, []byte(sig)); err != nil {
		return nil, err
	publicKeys, err := cosign.GetRekorPubs(ctx, rekorClient)
	if err != nil {
		return nil, fmt.Errorf("retrieving rekor public key: %w", err)
	pubKey, ok := publicKeys[b.Bundle.Payload.LogID]
	if !ok {
		return nil, errors.New("rekor log public key not found for payload")
	}
	err = cosign.VerifySET(b.Bundle.Payload, b.Bundle.SignedEntryTimestamp, pubKey.PubKey)
		return nil, err
	if pubKey.Status != tuf.Active {
		fmt.Fprintf(os.Stderr, "**Info** Successfully verified Rekor entry using an expired verification key\n")

	return &b.Bundle.Payload, nil
func verifyBundleMatchesData(ctx context.Context, bundle *bundle.RekorBundle, blobBytes, certBytes, sigBytes []byte) error {
	eimpl, kind, apiVersion, err := unmarshalEntryImpl(bundle.Payload.Body.(string))

	targetImpl, err := reconstructCanonicalizedEntry(ctx, kind, apiVersion, blobBytes, certBytes, sigBytes)
		return fmt.Errorf("recontructing rekorEntry for bundle comparison: %w", err)
	switch e := eimpl.(type) {
	case *rekord_v001.V001Entry:
		t := targetImpl.(*rekord_v001.V001Entry)
		data, err := e.RekordObj.Data.Content.MarshalText()
		if err != nil {
			return fmt.Errorf("invalid rekord data: %w", err)
		}
		tData, err := t.RekordObj.Data.Content.MarshalText()
		if err != nil {
			return fmt.Errorf("invalid rekord data: %w", err)
		}
		if !bytes.Equal(data, tData) {
			return fmt.Errorf("rekord data does not match blob")
		}
		if err := compareBase64Strings(e.RekordObj.Signature.Content.String(),
			t.RekordObj.Signature.Content.String()); err != nil {
			return fmt.Errorf("rekord signature does not match bundle %s", err)
		}
		if err := compareBase64Strings(e.RekordObj.Signature.PublicKey.Content.String(),
			t.RekordObj.Signature.PublicKey.Content.String()); err != nil {
			return fmt.Errorf("rekord public key does not match bundle")
		}
	case *hashedrekord_v001.V001Entry:
		t := targetImpl.(*hashedrekord_v001.V001Entry)
		if *e.HashedRekordObj.Data.Hash.Value != *t.HashedRekordObj.Data.Hash.Value {
			return fmt.Errorf("hashedRekord data does not match blob")
		}
		if err := compareBase64Strings(e.HashedRekordObj.Signature.Content.String(),
			t.HashedRekordObj.Signature.Content.String()); err != nil {
			return fmt.Errorf("hashedRekord signature does not match bundle %s", err)
		}
		if err := compareBase64Strings(e.HashedRekordObj.Signature.PublicKey.Content.String(),
			t.HashedRekordObj.Signature.PublicKey.Content.String()); err != nil {
			return fmt.Errorf("hashedRekord public key does not match bundle")
		}
	case *intoto_v001.V001Entry:
		t := targetImpl.(*intoto_v001.V001Entry)
		if *e.IntotoObj.Content.Hash.Value != *t.IntotoObj.Content.Hash.Value {
			return fmt.Errorf("intoto content hash does not match attestation")
		}
		if *e.IntotoObj.Content.PayloadHash.Value != *t.IntotoObj.Content.PayloadHash.Value {
			return fmt.Errorf("intoto payload hash does not match attestation")
		}
		if err := compareBase64Strings(e.IntotoObj.PublicKey.String(),
			t.IntotoObj.PublicKey.String()); err != nil {
			return fmt.Errorf("intoto public key does not match bundle")
		}
	default:
		return errors.New("unexpected tlog entry type")
	return nil
}

func reconstructCanonicalizedEntry(ctx context.Context, kind, apiVersion string, blobBytes, certBytes, sigBytes []byte) (types.EntryImpl, error) {
	props := types.ArtifactProperties{
		PublicKeyBytes: certBytes,
		PKIFormat:      string(pki.X509),
	}
	switch kind {
	case rekord.KIND:
		props.ArtifactBytes = blobBytes
		props.SignatureBytes = sigBytes
	case hashedrekord.KIND:
		blobHash := sha256.Sum256(blobBytes)
		props.ArtifactHash = strings.ToLower(hex.EncodeToString(blobHash[:]))
		props.SignatureBytes = sigBytes
	case intoto.KIND:
		props.ArtifactBytes = blobBytes
	default:
		return nil, fmt.Errorf("unexpected entry kind: %s", kind)
	}
	proposedEntry, err := types.NewProposedEntry(ctx, kind, apiVersion, props)
		return nil, err
	entry, err := types.NewEntry(proposedEntry)
	if err != nil {
		return nil, err
	can, err := entry.Canonicalize(ctx)
	if err != nil {
		return nil, err
	}
	proposedEntryCan, err := models.UnmarshalProposedEntry(bytes.NewReader(can), runtime.JSONConsumer())
	if err != nil {
		return nil, err
	return types.NewEntry(proposedEntryCan)
// unmarshalEntryImpl decodes the base64-encoded entry to a specific entry type (types.EntryImpl).
func unmarshalEntryImpl(e string) (types.EntryImpl, string, string, error) {
	b, err := base64.StdEncoding.DecodeString(e)
		return nil, "", "", err
		return nil, "", "", err
	}

	entry, err := types.NewEntry(pe)
	if err != nil {
		return nil, "", "", err
	return entry, pe.Kind(), entry.APIVersion(), nil
}
func extractCerts(e *models.LogEntryAnon) ([]*x509.Certificate, error) {
	eimpl, _, _, err := unmarshalEntryImpl(e.Body.(string))
	case *rekord_v001.V001Entry:
	case *hashedrekord_v001.V001Entry:

// TODO: Use this function to compare bundle signatures in OCI.
func compareBase64Strings(got string, expected string) error {
	decodeFirst, err := base64.StdEncoding.DecodeString(got)
	if err != nil {
		return fmt.Errorf("decoding base64 string %s", got)
	}
	decodeSecond, err := base64.StdEncoding.DecodeString(expected)
	if err != nil {
		return fmt.Errorf("decoding base64 string %s", expected)
	}
	if !bytes.Equal(decodeFirst, decodeSecond) {
		return fmt.Errorf("comparing base64 strings, expected %s, got %s", expected, got)
	}
	return nil
}
	"bytes"
	"context"
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"crypto/x509"
	"encoding/hex"
	"fmt"
	"strings"
	"time"
	"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
	"github.com/go-openapi/swag"
	ssldsse "github.com/secure-systems-lab/go-securesystemslib/dsse"
	"github.com/sigstore/cosign/cmd/cosign/cli/options"
	"github.com/sigstore/cosign/internal/pkg/cosign/rekor/mock"
	"github.com/sigstore/cosign/pkg/cosign/bundle"
	sigs "github.com/sigstore/cosign/pkg/signature"
	ctypes "github.com/sigstore/cosign/pkg/types"
	"github.com/sigstore/cosign/test"
	"github.com/sigstore/rekor/pkg/generated/client"
	"github.com/sigstore/rekor/pkg/generated/models"
	"github.com/sigstore/rekor/pkg/pki"
	"github.com/sigstore/rekor/pkg/types"
	"github.com/sigstore/rekor/pkg/types/hashedrekord"
	hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1"
	"github.com/sigstore/rekor/pkg/types/intoto"
	"github.com/sigstore/rekor/pkg/types/rekord"
	"github.com/sigstore/sigstore/pkg/cryptoutils"
	"github.com/sigstore/sigstore/pkg/signature"
	"github.com/sigstore/sigstore/pkg/signature/dsse"
	signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
			gotSig, err := signatures(test.sigRef, "")
	gotSig, err := signatures("", fp)
		envelope     ssldsse.Envelope
			envelope: ssldsse.Envelope{
				Signatures:  []ssldsse.Signature{},

// Does not test identity options, only blob verification with different
// options.
func TestVerifyBlob(t *testing.T) {
	ctx := context.Background()
	td := t.TempDir()

	leafPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	signer, err := signature.LoadECDSASignerVerifier(leafPriv, crypto.SHA256)
	if err != nil {
		t.Fatal(err)
	}
	pubKeyBytes, err := sigs.PublicKeyPem(signer, signatureoptions.WithContext(ctx))
	if err != nil {
		t.Fatal(err)
	}

	// Generate expired and unexpired certificates
	identity := "hello@foo.com"
	issuer := "issuer"
	rootCert, rootPriv, _ := test.GenerateRootCa()
	rootPool := x509.NewCertPool()
	rootPool.AddCert(rootCert)
	unexpiredLeafCert, _ := test.GenerateLeafCertWithExpiration(identity, issuer,
		time.Now(), leafPriv, rootCert, rootPriv)
	unexpiredCertPem, _ := cryptoutils.MarshalCertificateToPEM(unexpiredLeafCert)

	expiredLeafCert, _ := test.GenerateLeafCertWithExpiration(identity, issuer,
		time.Now().Add(-time.Hour), leafPriv, rootCert, rootPriv)
	expiredLeafPem, _ := cryptoutils.MarshalCertificateToPEM(expiredLeafCert)

	// Make rekor signer
	rekorPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	rekorSigner, err := signature.LoadECDSASignerVerifier(rekorPriv, crypto.SHA256)
	if err != nil {
		t.Fatal(err)
	}
	pemRekor, err := cryptoutils.MarshalPublicKeyToPEM(rekorSigner.Public())
	if err != nil {
		t.Fatal(err)
	}
	tmpRekorPubFile, err := os.CreateTemp(td, "cosign_rekor_pub_*.key")
	if err != nil {
		t.Fatalf("failed to create temp rekor pub file: %v", err)
	}
	defer tmpRekorPubFile.Close()
	if _, err := tmpRekorPubFile.Write(pemRekor); err != nil {
		t.Fatalf("failed to write rekor pub file: %v", err)
	}
	t.Setenv("SIGSTORE_REKOR_PUBLIC_KEY", tmpRekorPubFile.Name())

	var makeSignature = func(blob []byte) string {
		sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
		if err != nil {
			t.Fatal(err)
		}
		return string(sig)
	}
	blobBytes := []byte("foo")
	blobSignature := makeSignature(blobBytes)

	otherBytes := []byte("bar")
	otherSignature := makeSignature(otherBytes)

	tts := []struct {
		name        string
		blob        []byte
		signature   string
		sigVerifier signature.Verifier
		cert        *x509.Certificate
		bundlePath  string
		// If online lookups to Rekor are enabled
		experimental bool
		// The rekor entry response when Rekor is enabled
		rekorEntry *models.LogEntry
		shouldErr  bool
	}{
		{
			name:         "valid signature with public key",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: false,
			shouldErr:    false,
		},
		{
			name:         "valid signature with public key - experimental no rekor fail",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: true,
			rekorEntry:   nil,
			shouldErr:    true,
		},
		{
			name:         "valid signature with public key - experimental rekor entry success",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: true,
			rekorEntry: makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature),
				pubKeyBytes, true),
			shouldErr: false,
		},
		{
			name:         "valid signature with public key - good bundle provided",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: false,
			bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature),
				pubKeyBytes, true),
			shouldErr: false,
		},
		{
			name:         "valid signature with public key - bad bundle SET",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: false,
			bundlePath: makeLocalBundle(t, *signer, blobBytes, []byte(blobSignature),
				unexpiredCertPem, true),
			shouldErr: true,
		},
		{
			name:         "valid signature with public key - bad bundle cert mismatch",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: false,
			bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature),
				unexpiredCertPem, true),
			shouldErr: true,
		},
		{
			name:         "valid signature with public key - bad bundle signature mismatch",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: false,
			bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes)),
				pubKeyBytes, true),
			shouldErr: true,
		},
		{
			name:         "valid signature with public key - bad bundle msg & signature mismatch",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: false,
			bundlePath: makeLocalBundle(t, *rekorSigner, otherBytes, []byte(otherSignature),
				pubKeyBytes, true),
			shouldErr: true,
		},
		{
			name:         "invalid signature with public key",
			blob:         blobBytes,
			signature:    otherSignature,
			sigVerifier:  signer,
			experimental: false,
			shouldErr:    true,
		},
		{
			name:         "invalid signature with public key - experimental",
			blob:         blobBytes,
			signature:    otherSignature,
			sigVerifier:  signer,
			experimental: true,
			shouldErr:    true,
		},
		{
			name:         "valid signature with unexpired certificate",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			cert:         unexpiredLeafCert,
			experimental: false,
			shouldErr:    false,
		},
		{
			name:         "valid signature with unexpired certificate - bad bundle cert mismatch",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: false,
			cert:         unexpiredLeafCert,
			bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature),
				pubKeyBytes, true),
			shouldErr: true,
		},
		{
			name:         "valid signature with unexpired certificate - bad bundle signature mismatch",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: false,
			cert:         unexpiredLeafCert,
			bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes)),
				unexpiredCertPem, true),
			shouldErr: true,
		},
		{
			name:         "valid signature with unexpired certificate - bad bundle msg & signature mismatch",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			experimental: false,
			cert:         unexpiredLeafCert,
			bundlePath: makeLocalBundle(t, *rekorSigner, otherBytes, []byte(otherSignature),
				unexpiredCertPem, true),
			shouldErr: true,
		},
		{
			name:         "invalid signature with unexpired certificate",
			blob:         blobBytes,
			signature:    otherSignature,
			sigVerifier:  signer,
			cert:         unexpiredLeafCert,
			experimental: false,
			shouldErr:    true,
		},
		{
			name:         "valid signature with unexpired certificate - experimental",
			blob:         blobBytes,
			signature:    blobSignature,
			cert:         unexpiredLeafCert,
			sigVerifier:  signer,
			experimental: true,
			rekorEntry: makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature),
				unexpiredCertPem, true),
			shouldErr: false,
		},

		{
			name:         "valid signature with unexpired certificate - experimental & rekor entry found",
			blob:         blobBytes,
			signature:    blobSignature,
			cert:         unexpiredLeafCert,
			experimental: true,
			rekorEntry: makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature),
				unexpiredCertPem, true),
			shouldErr: false,
		},
		{
			name:         "valid signature with expired certificate",
			blob:         blobBytes,
			signature:    blobSignature,
			cert:         expiredLeafCert,
			sigVerifier:  signer,
			experimental: false,
			shouldErr:    true,
		},

		{
			name:         "valid signature with expired certificate - experimental good rekor lookup",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			cert:         expiredLeafCert,
			experimental: true,
			rekorEntry: makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature),
				expiredLeafPem, true),
			shouldErr: false,
		},

		{
			name:         "valid signature with expired certificate - experimental bad rekor integrated time",
			blob:         blobBytes,
			signature:    blobSignature,
			cert:         expiredLeafCert,
			sigVerifier:  signer,
			experimental: true,
			rekorEntry: makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature),
				expiredLeafPem, false),
			shouldErr: true,
		},

		{
			name:         "valid signature with unexpired certificate - good bundle, nonexperimental",
			blob:         blobBytes,
			signature:    blobSignature,
			cert:         unexpiredLeafCert,
			sigVerifier:  signer,
			experimental: false,
			bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature),
				unexpiredCertPem, true),
			shouldErr: false,
		},
		{
			name:         "valid signature with expired certificate - good bundle, nonexperimental",
			blob:         blobBytes,
			signature:    blobSignature,
			cert:         expiredLeafCert,
			sigVerifier:  signer,
			experimental: false,
			bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature),
				expiredLeafPem, true),
			shouldErr: false,
		},
		{
			name:         "valid signature with expired certificate - bundle with bad expiration",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			cert:         expiredLeafCert,
			experimental: false,
			bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature),
				expiredLeafPem, false),
			shouldErr: true,
		},
		{
			name:         "valid signature with expired certificate - bundle with bad SET",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			cert:         expiredLeafCert,
			experimental: false,
			bundlePath: makeLocalBundle(t, *signer, blobBytes, []byte(blobSignature),
				expiredLeafPem, true),
			shouldErr: true,
		},
		{
			name:         "valid signature with expired certificate - experimental good bundle",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			cert:         expiredLeafCert,
			experimental: true,
			bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature),
				expiredLeafPem, true),
			shouldErr: false,
		},
		{
			name:         "valid signature with expired certificate - experimental bad rekor entry",
			blob:         blobBytes,
			signature:    blobSignature,
			sigVerifier:  signer,
			cert:         expiredLeafCert,
			experimental: true,
			// This is the wrong signer for the SET!
			rekorEntry: makeRekorEntry(t, *signer, blobBytes, []byte(blobSignature),
				expiredLeafPem, true),
			shouldErr: true,
		},
	}
	for _, tt := range tts {
		t.Run(tt.name, func(t *testing.T) {
			tt := tt
			var mClient client.Rekor
			mClient.Entries = &mock.EntriesClient{Entries: tt.rekorEntry}
			co := &cosign.CheckOpts{
				SigVerifier: tt.sigVerifier,
				RootCerts:   rootPool,
			}
			// if expermental is enabled, add RekorClient to co.
			if tt.experimental {
				co.RekorClient = &mClient
			}

			err := verifyBlob(ctx, co, tt.blob, tt.signature, tt.cert, tt.bundlePath, nil)
			if (err != nil) != tt.shouldErr {
				t.Fatalf("verifyBlob()= %s, expected shouldErr=%t ", err, tt.shouldErr)
			}
		})
	}
}

func makeRekorEntry(t *testing.T, rekorSigner signature.ECDSASignerVerifier,
	pyld, sig, svBytes []byte, expiryValid bool) *models.LogEntry {
	ctx := context.Background()
	// Calculate log ID, the digest of the Rekor public key
	logID, err := getLogID(rekorSigner.Public())
	if err != nil {
		t.Fatal(err)
	}

	hashedrekord := &hashedrekord_v001.V001Entry{}
	h := sha256.Sum256(pyld)
	pe, err := hashedrekord.CreateFromArtifactProperties(ctx, types.ArtifactProperties{
		ArtifactHash:   hex.EncodeToString(h[:]),
		SignatureBytes: sig,
		PublicKeyBytes: svBytes,
		PKIFormat:      "x509",
	})
	if err != nil {
		t.Fatal(err)
	}
	entry, err := types.NewEntry(pe)
	if err != nil {
		t.Fatal(err)
	}
	leaf, err := entry.Canonicalize(ctx)
	if err != nil {
		t.Fatal(err)
	}

	integratedTime := time.Now()
	certs, _ := cryptoutils.UnmarshalCertificatesFromPEM(svBytes)
	if certs != nil && len(certs) > 0 {
		if expiryValid {
			integratedTime = certs[0].NotAfter.Add(-time.Second)
		} else {
			integratedTime = certs[0].NotAfter.Add(time.Second)
		}
	}
	e := models.LogEntryAnon{
		Body:           base64.StdEncoding.EncodeToString(leaf),
		IntegratedTime: swag.Int64(integratedTime.Unix()),
		LogIndex:       swag.Int64(0),
		LogID:          swag.String(logID),
	}
	// Marshal payload, sign, and set SET in Bundle
	jsonPayload, err := json.Marshal(e)
	if err != nil {
		t.Fatal(err)
	}
	canonicalized, err := jsoncanonicalizer.Transform(jsonPayload)
	if err != nil {
		t.Fatal(err)
	}
	bundleSig, err := rekorSigner.SignMessage(bytes.NewReader(canonicalized))
	if err != nil {
		t.Fatal(err)
	}
	uuid, _ := cosign.ComputeLeafHash(&e)

	e.Verification = &models.LogEntryAnonVerification{
		SignedEntryTimestamp: bundleSig,
		InclusionProof: &models.InclusionProof{
			LogIndex: swag.Int64(0),
			TreeSize: swag.Int64(1),
			RootHash: swag.String(hex.EncodeToString(uuid)),
			Hashes:   []string{},
		},
	}
	return &models.LogEntry{hex.EncodeToString(uuid): e}
}

func makeLocalBundle(t *testing.T, rekorSigner signature.ECDSASignerVerifier,
	pyld []byte, sig []byte, svBytes []byte, expiryValid bool) string {
	td := t.TempDir()

	// Create bundle.
	entry := makeRekorEntry(t, rekorSigner, pyld, sig, svBytes, expiryValid)
	var e models.LogEntryAnon
	for _, v := range *entry {
		e = v
	}
	b := cosign.LocalSignedPayload{
		Base64Signature: base64.StdEncoding.EncodeToString(sig),
		Cert:            string(svBytes),
		Bundle: &bundle.RekorBundle{
			Payload: bundle.RekorPayload{
				Body:           e.Body,
				IntegratedTime: *e.IntegratedTime,
				LogIndex:       *e.LogIndex,
				LogID:          *e.LogID,
			},
			SignedEntryTimestamp: e.Verification.SignedEntryTimestamp,
		},
	}

	// Write bundle to disk
	jsonBundle, err := json.Marshal(b)
	if err != nil {
		t.Fatal(err)
	}
	bundlePath := filepath.Join(td, "bundle.sig")
	if err := os.WriteFile(bundlePath, jsonBundle, 0644); err != nil {
		t.Fatal(err)
	}
	return bundlePath
}

func TestVerifyBlobCmdWithBundle(t *testing.T) {
	keyless := newKeylessStack(t)

	t.Run("Normal verification", func(t *testing.T) {
		identity := "hello@foo.com"
		issuer := "issuer"
		leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)

		// Create blob
		blob := "someblob"

		// Sign blob with private key
		sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
		if err != nil {
			t.Fatal(err)
		}

		// Create bundle
		entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig)
		b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry)
		b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload)
		bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
		blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt")

		// Verify command
		err = VerifyBlobCmd(context.Background(),
			options.KeyOpts{BundlePath: bundlePath},
			"",       /*certRef*/ // Cert is fetched from bundle
			identity, /*certEmail*/
			issuer,   /*certOidcIssuer*/
			"",       /*certChain*/ // Chain is fetched from TUF/SIGSTORE_ROOT_FILE
			"",       /*sigRef*/    // Sig is fetched from bundle
			blobPath, /*blobRef*/
			// GitHub identity flags start
			"", "", "", "", "",
			// GitHub identity flags end
			false /*enforceSCT*/)
		if err != nil {
			t.Fatal(err)
		}
	})
	t.Run("Mismatched cert/sig", func(t *testing.T) {
		// This test ensures that the signature and cert at the top level in the LocalSignedPayload must be identical to the ones in the RekorBundle.
		identity := "hello@foo.com"
		issuer := "issuer"
		leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)
		_, _, leafPemCert2, signer2 := keyless.genLeafCert(t, identity, issuer)

		// Create blob
		blob := "someblob"

		sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
		if err != nil {
			t.Fatal(err)
		}

		sig2, err := signer2.SignMessage(bytes.NewReader([]byte(blob)))
		if err != nil {
			t.Fatal(err)
		}

		// Create bundle
		entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert2, sig2)
		b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry)
		b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload)
		bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
		blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt")

		// Verify command
		err = VerifyBlobCmd(context.Background(),
			options.KeyOpts{BundlePath: bundlePath},
			"",       /*certRef*/ // Cert is fetched from bundle
			"",       /*certEmail*/
			"",       /*certOidcIssuer*/
			"",       /*certChain*/ // Chain is fetched from TUF/SIGSTORE_ROOT_FILE
			"",       /*sigRef*/    // Sig is fetched from bundle
			blobPath, /*blobRef*/
			// GitHub identity flags start
			"", "", "", "", "",
			// GitHub identity flags end
			false /*enforceSCT*/)
		if err == nil {
			t.Fatal("expecting err due to mismatched signatures, got nil")
		}
	})
	t.Run("Expired cert", func(t *testing.T) {
		identity := "hello@foo.com"
		issuer := "issuer"
		leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)

		// Create blob
		blob := "someblob"

		// Sign blob with private key
		sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
		if err != nil {
			t.Fatal(err)
		}

		// Create bundle
		entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig)
		b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()-1, entry)
		b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload)
		bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
		blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt")

		// Verify command
		err = VerifyBlobCmd(context.Background(),
			options.KeyOpts{BundlePath: bundlePath},
			"",       /*certRef*/ // Cert is fetched from bundle
			"",       /*certEmail*/
			"",       /*certOidcIssuer*/
			"",       /*certChain*/ // Chain is fetched from TUF/SIGSTORE_ROOT_FILE
			"",       /*sigRef*/    // Sig is fetched from bundle
			blobPath, /*blobRef*/
			// GitHub identity flags start
			"", "", "", "", "",
			// GitHub identity flags end
			false /*enforceSCT*/)
		if err == nil {
			t.Fatal("expected error due to expired cert, received nil")
		}
	})
	t.Run("Attestation", func(t *testing.T) {
		identity := "hello@foo.com"
		issuer := "issuer"
		leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)

		stmt := `{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}`
		wrapped := dsse.WrapSigner(signer, ctypes.IntotoPayloadType)
		signedPayload, err := wrapped.SignMessage(bytes.NewReader([]byte(stmt)), signatureoptions.WithContext(context.Background()))
		if err != nil {
			t.Fatal(err)
		}
		// intoto sig = json-serialized dsse envelope
		sig := signedPayload

		// Create bundle
		entry := genRekorEntry(t, intoto.KIND, intoto.New().DefaultVersion(), signedPayload, leafPemCert, sig)
		b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry)
		b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload)
		bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
		blobPath := writeBlobFile(t, keyless.td, string(signedPayload), "attestation.txt")

		// Verify command
		err = VerifyBlobCmd(context.Background(),
			options.KeyOpts{BundlePath: bundlePath},
			"",       /*certRef*/ // Cert is fetched from bundle
			"",       /*certEmail*/
			"",       /*certOidcIssuer*/
			"",       /*certChain*/ // Chain is fetched from TUF/SIGSTORE_ROOT_FILE
			"",       /*sigRef*/    // Sig is fetched from bundle
			blobPath, /*blobRef*/
			// GitHub identity flags start
			"", "", "", "", "",
			// GitHub identity flags end
			false /*enforceSCT*/)
		if err != nil {
			t.Fatal(err)
		}
	})
	t.Run("Invalid blob signature", func(t *testing.T) {
		identity := "hello@foo.com"
		issuer := "issuer"
		leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)

		// Create blob
		blob := "someblob"

		// Sign blob with private key
		sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
		if err != nil {
			t.Fatal(err)
		}

		// Create bundle
		entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig)
		b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry)
		b.Bundle.SignedEntryTimestamp = []byte{'i', 'n', 'v', 'a', 'l', 'i', 'd'}
		bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
		blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt")

		// Verify command
		err = VerifyBlobCmd(context.Background(),
			options.KeyOpts{BundlePath: bundlePath},
			"",       /*certRef*/ // Cert is fetched from bundle
			"",       /*certEmail*/
			"",       /*certOidcIssuer*/
			"",       /*certChain*/ // Chain is fetched from TUF/SIGSTORE_ROOT_FILE
			"",       /*sigRef*/    // Sig is fetched from bundle
			blobPath, /*blobRef*/
			// GitHub identity flags start
			"", "", "", "", "",
			// GitHub identity flags end
			false /*enforceSCT*/)
		if err == nil || !strings.Contains(err.Error(), "unable to verify SET") {
			t.Fatalf("expected error verifying SET, got %v", err)
		}
	})
	t.Run("Mismatched certificate email", func(t *testing.T) {
		identity := "hello@foo.com"
		issuer := "issuer"
		leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)

		// Create blob
		blob := "someblob"

		// Sign blob with private key
		sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
		if err != nil {
			t.Fatal(err)
		}

		// Create bundle
		entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig)
		b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry)
		b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload)
		bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
		blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt")

		// Verify command
		err = VerifyBlobCmd(context.Background(),
			options.KeyOpts{BundlePath: bundlePath},
			"",                    /*certRef*/ // Cert is fetched from bundle
			"invalid@example.com", /*certEmail*/
			issuer,                /*certOidcIssuer*/
			"",                    /*certChain*/ // Chain is fetched from TUF/SIGSTORE_ROOT_FILE
			"",                    /*sigRef*/    // Sig is fetched from bundle
			blobPath,              /*blobRef*/
			// GitHub identity flags start
			"", "", "", "", "",
			// GitHub identity flags end
			false /*enforceSCT*/)
		if err == nil || !strings.Contains(err.Error(), "expected email not found in certificate") {
			t.Fatalf("expected error with mismatched identity, got %v", err)
		}
	})
	t.Run("Mismatched certificate issuer", func(t *testing.T) {
		identity := "hello@foo.com"
		issuer := "issuer"
		leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)

		// Create blob
		blob := "someblob"

		// Sign blob with private key
		sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
		if err != nil {
			t.Fatal(err)
		}

		// Create bundle
		entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig)
		b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry)
		b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload)
		bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
		blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt")

		// Verify command
		err = VerifyBlobCmd(context.Background(),
			options.KeyOpts{BundlePath: bundlePath},
			"",        /*certRef*/ // Cert is fetched from bundle
			identity,  /*certEmail*/
			"invalid", /*certOidcIssuer*/
			"",        /*certChain*/ // Chain is fetched from TUF/SIGSTORE_ROOT_FILE
			"",        /*sigRef*/    // Sig is fetched from bundle
			blobPath,  /*blobRef*/
			// GitHub identity flags start
			"", "", "", "", "",
			// GitHub identity flags end
			false /*enforceSCT*/)
		if err == nil || !strings.Contains(err.Error(), "expected oidc issuer not found in certificate") {
			t.Fatalf("expected error with mismatched issuer, got %v", err)
		}
	})
}

type keylessStack struct {
	rootCert    *x509.Certificate
	rootPriv    *ecdsa.PrivateKey
	rootPemCert []byte
	subCert     *x509.Certificate
	subPriv     *ecdsa.PrivateKey
	subPemCert  []byte
	rekorSigner *signature.ECDSASignerVerifier
	rekorLogID  string
	td          string // temporary directory
}

func newKeylessStack(t *testing.T) *keylessStack {
	stack := &keylessStack{td: t.TempDir()}
	stack.rootCert, stack.rootPriv, _ = test.GenerateRootCa()
	stack.rootPemCert, _ = cryptoutils.MarshalCertificateToPEM(stack.rootCert)
	stack.subCert, stack.subPriv, _ = test.GenerateSubordinateCa(stack.rootCert, stack.rootPriv)
	stack.subPemCert, _ = cryptoutils.MarshalCertificateToPEM(stack.subCert)

	stack.genChainFile(t)
	stack.genRekor(t)
	return stack
}

func (s *keylessStack) genLeafCert(t *testing.T, subject string, issuer string) (*x509.Certificate, *ecdsa.PrivateKey, []byte, *signature.ECDSASignerVerifier) {
	cert, priv, _ := test.GenerateLeafCert(subject, issuer, s.subCert, s.subPriv)
	pemCert, _ := cryptoutils.MarshalCertificateToPEM(cert)
	signer, err := signature.LoadECDSASignerVerifier(priv, crypto.SHA256)
	if err != nil {
		t.Fatal(err)
	}
	return cert, priv, pemCert, signer
}

func (s *keylessStack) genChainFile(t *testing.T) {
	var chain []byte
	chain = append(chain, s.subPemCert...)
	chain = append(chain, s.rootPemCert...)
	tmpChainFile, err := os.CreateTemp(s.td, "cosign_fulcio_chain_*.cert")
	if err != nil {
		t.Fatalf("failed to create temp chain file: %v", err)
	}
	defer tmpChainFile.Close()
	if _, err := tmpChainFile.Write(chain); err != nil {
		t.Fatalf("failed to write chain file: %v", err)
	}
	// Override for Fulcio root so it doesn't use TUF
	t.Setenv("SIGSTORE_ROOT_FILE", tmpChainFile.Name())
}

func (s *keylessStack) genRekor(t *testing.T) {
	// Create Rekor private key and write to disk
	rekorPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	s.rekorSigner, err = signature.LoadECDSASignerVerifier(rekorPriv, crypto.SHA256)
	if err != nil {
		t.Fatal(err)
	}
	rekorPub := s.rekorSigner.Public()
	pemRekor, err := cryptoutils.MarshalPublicKeyToPEM(rekorPub)
	if err != nil {
		t.Fatal(err)
	}
	tmpRekorPubFile, err := os.CreateTemp(s.td, "cosign_rekor_pub_*.key")
	if err != nil {
		t.Fatalf("failed to create temp rekor pub file: %v", err)
	}
	defer tmpRekorPubFile.Close()
	if _, err := tmpRekorPubFile.Write(pemRekor); err != nil {
		t.Fatalf("failed to write rekor pub file: %v", err)
	}

	// Calculate log ID, the digest of the Rekor public key
	s.rekorLogID, err = getLogID(rekorPub)
	if err != nil {
		t.Fatal(err)
	}
	// Override for Rekor public key so it doesn't use TUF
	t.Setenv("SIGSTORE_REKOR_PUBLIC_KEY", tmpRekorPubFile.Name())
}
func (s *keylessStack) rekorSignPayload(t *testing.T, payload bundle.RekorPayload) []byte {
	// Marshal payload, sign, and return SET
	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		t.Fatal(err)
	}
	canonicalized, err := jsoncanonicalizer.Transform(jsonPayload)
	if err != nil {
		t.Fatal(err)
	}
	bundleSig, err := s.rekorSigner.SignMessage(bytes.NewReader(canonicalized))
	if err != nil {
		t.Fatal(err)
	}
	return bundleSig
}

// getLogID calculates the digest of a PKIX-encoded public key
func getLogID(pub crypto.PublicKey) (string, error) {
	pubBytes, err := x509.MarshalPKIXPublicKey(pub)
	if err != nil {
		return "", err
	}
	digest := sha256.Sum256(pubBytes)
	return hex.EncodeToString(digest[:]), nil
}

func genRekorEntry(t *testing.T, kind, version string, artifact []byte, cert []byte, sig []byte) string {
	// Generate the Rekor Entry
	entryImpl, err := createEntry(context.Background(), kind, version, artifact, cert, sig)
	if err != nil {
		t.Fatal(err)
	}
	entryBytes, err := entryImpl.Canonicalize(context.Background())
	if err != nil {
		t.Fatal(err)
	}
	return base64.StdEncoding.EncodeToString(entryBytes)
}

func createBundle(t *testing.T, sig []byte, certPem []byte, logID string, integratedTime int64, rekorEntry string) *cosign.LocalSignedPayload {
	// Create bundle with:
	// * Blob signature
	// * Signing certificate
	// * Bundle with a payload and signature over the payload
	b := &cosign.LocalSignedPayload{
		Base64Signature: base64.StdEncoding.EncodeToString(sig),
		Cert:            string(certPem),
		Bundle: &bundle.RekorBundle{
			SignedEntryTimestamp: []byte{},
			Payload: bundle.RekorPayload{
				LogID:          logID,
				IntegratedTime: integratedTime,
				LogIndex:       1,
				Body:           rekorEntry,
			},
		},
	}

	return b
}

func createEntry(ctx context.Context, kind, apiVersion string, blobBytes, certBytes, sigBytes []byte) (types.EntryImpl, error) {
	props := types.ArtifactProperties{
		PublicKeyBytes: certBytes,
		PKIFormat:      string(pki.X509),
	}
	switch kind {
	case rekord.KIND:
		props.ArtifactBytes = blobBytes
		props.SignatureBytes = sigBytes
	case hashedrekord.KIND:
		blobHash := sha256.Sum256(blobBytes)
		props.ArtifactHash = strings.ToLower(hex.EncodeToString(blobHash[:]))
		props.SignatureBytes = sigBytes
	case intoto.KIND:
		props.ArtifactBytes = blobBytes
	default:
		return nil, fmt.Errorf("unexpected entry kind: %s", kind)
	}
	proposedEntry, err := types.NewProposedEntry(ctx, kind, apiVersion, props)
	if err != nil {
		return nil, err
	}
	return types.NewEntry(proposedEntry)
}

func writeBundleFile(t *testing.T, td string, b *cosign.LocalSignedPayload, name string) string {
	// Write bundle to disk
	jsonBundle, err := json.Marshal(b)
	if err != nil {
		t.Fatal(err)
	}
	bundlePath := filepath.Join(td, name)
	if err := os.WriteFile(bundlePath, jsonBundle, 0644); err != nil {
		t.Fatal(err)
	}
	return bundlePath
}

func writeBlobFile(t *testing.T, td string, blob string, name string) string {
	// Write blob to disk
	blobPath := filepath.Join(td, name)
	if err := os.WriteFile(blobPath, []byte(blob), 0644); err != nil {
		t.Fatal(err)
	}
	return blobPath
}
	"errors"
// mClient.Entries = &logEntry
	Entries *models.LogEntry
	if m.Entries != nil {
		return &entries.CreateLogEntryCreated{
			ETag:     "",
			Location: "",
			Payload:  *m.Entries,
		}, nil
	}
	return nil, errors.New("entry not provided")
	if m.Entries != nil {
		return &entries.GetLogEntryByIndexOK{
			Payload: *m.Entries,
		}, nil
	}
	return nil, errors.New("entry not provided")
	if m.Entries != nil {
		return &entries.GetLogEntryByUUIDOK{
			Payload: *m.Entries,
		}, nil
	}
	return nil, errors.New("entry not provided")
	resp := []models.LogEntry{}
	if m.Entries != nil {
		resp = append(resp, *m.Entries)
	}
		Payload: resp,
	"github.com/go-openapi/swag"
	"github.com/sigstore/rekor/pkg/generated/models"

	mClient.Entries = &mock.EntriesClient{
		Entries: &models.LogEntry{"123": models.LogEntryAnon{
			LogIndex: swag.Int64(123),
		}},
	}
		return &VerificationError{"unable to verify SET"}
	"encoding/hex"
	"github.com/sigstore/rekor/pkg/generated/models"
	"github.com/transparency-dev/merkle/rfc6962"
var (
	lea = models.LogEntryAnon{
		Attestation:    &models.LogEntryAnonAttestation{},
		Body:           base64.StdEncoding.EncodeToString([]byte("asdf")),
		IntegratedTime: new(int64),
		LogID:          new(string),
		LogIndex:       new(int64),
		Verification: &models.LogEntryAnonVerification{
			InclusionProof: &models.InclusionProof{
				RootHash: new(string),
				TreeSize: new(int64),
				LogIndex: new(int64),
			},
		},
	}
	data = models.LogEntry{
		uuid(lea): lea,
	}
)

// uuid generates the UUID for the given LogEntry.
// This is effectively a reimplementation of
// pkg/cosign/tlog.go -> verifyUUID / ComputeLeafHash, but separated
// to avoid a circular dependency.
// TODO?: Perhaps we should refactor the tlog libraries into a separate
// package?
func uuid(e models.LogEntryAnon) string {
	entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string))
	if err != nil {
		panic(err)
	}
	return hex.EncodeToString(rfc6962.DefaultHasher.HashLeaf(entryBytes))
}

	mClient.Entries = &mock.EntriesClient{
		Entries: &data,
	}
		NotBefore:             time.Now().Add(-5 * time.Hour),
func GenerateLeafCertWithExpiration(subject string, oidcIssuer string, expiration time.Time,
	priv *ecdsa.PrivateKey,
	parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, error) {
	certTemplate := &x509.Certificate{
		SerialNumber:   big.NewInt(1),
		EmailAddresses: []string{subject},
		NotBefore:      expiration,
		NotAfter:       expiration.Add(10 * time.Minute),
		KeyUsage:       x509.KeyUsageDigitalSignature,
		ExtKeyUsage:    []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
		IsCA:           false,
		ExtraExtensions: []pkix.Extension{{
			// OID for OIDC Issuer extension
			Id:       asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1},
			Critical: false,
			Value:    []byte(oidcIssuer),
		},
		},
	}

	cert, err := createCertificate(certTemplate, parentTemplate, &priv.PublicKey, parentPriv)
	if err != nil {
		return nil, err
	}

	return cert, nil
}