Repository: sigstore/cosign
Pull request #2449 (2022-11-11 19:40:03)

Better feedback if specifying incompatible argument on `cosign sign --attachment`
Signed-off-by: chaosinthecrd <thomas.meadows@jetstack.io>

This is a tiny change, but when using the `cosign sign --attachment` flag, it didn't seem clear why I was getting an error back. Of course the reason why is that the only attachment string it will accept is `sbom`, which is making me wonder whether at least temporarily this would be better as a boolean flag `--sbom-attachment` until there are other attachment types.

I have changed the error message, just as a suggestion as I feel it makes it more clear as to what is going on as opposed to 

```
Error: flag: help requested
main.go:62: error during command execution: flag: help requested
```


Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 7702a075..1dbed8b6 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -67,7 +67,7 @@ jobs:
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # v2.1.31
+      uses: github/codeql-action/init@4238421316c33d73aeea2801274dd286f157c2bb # v2.1.32
       with:
         languages: ${{ matrix.language }}
 
@@ -75,4 +75,4 @@ jobs:
       run: make cosign
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # v2.1.31
+      uses: github/codeql-action/analyze@4238421316c33d73aeea2801274dd286f157c2bb # v2.1.32
diff --git a/.github/workflows/scorecard_action.yml b/.github/workflows/scorecard_action.yml
index c1e6ccd4..3faab1a5 100644
--- a/.github/workflows/scorecard_action.yml
+++ b/.github/workflows/scorecard_action.yml
@@ -52,6 +52,6 @@ jobs:
 
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # v2.1.31
+        uses: github/codeql-action/upload-sarif@4238421316c33d73aeea2801274dd286f157c2bb # v2.1.32
         with:
           sarif_file: results.sarif
diff --git a/.gitignore b/.gitignore
index 5ef31f9f..dc48d1d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,4 +32,8 @@ policyControllerImagerefs
 sgetImagerefs
 policyImagerefs
 
+bundle
+signature
+certificate
+
 **verify-experimental*
diff --git a/cmd/cosign/cli/options/key.go b/cmd/cosign/cli/options/key.go
index 567faf58..d2ea87c2 100644
--- a/cmd/cosign/cli/options/key.go
+++ b/cmd/cosign/cli/options/key.go
@@ -33,6 +33,7 @@ type KeyOpts struct {
 	OIDCProvider         string // Specify which OIDC credential provider to use for keyless signer
 	BundlePath           string
 	SkipConfirmation     bool
+	TSAServerURL         string
 
 	// FulcioAuthFlow is the auth flow to use when authenticating against
 	// Fulcio. See https://pkg.go.dev/github.com/sigstore/cosign/cmd/cosign/cli/fulcio#pkg-constants
diff --git a/cmd/cosign/cli/options/policy.go b/cmd/cosign/cli/options/policy.go
index a7ee209a..f1bc5641 100644
--- a/cmd/cosign/cli/options/policy.go
+++ b/cmd/cosign/cli/options/policy.go
@@ -63,6 +63,7 @@ type PolicySignOptions struct {
 	Fulcio           FulcioOptions
 	Rekor            RekorOptions
 	SkipConfirmation bool
+	TlogUpload       bool
 
 	OIDC OIDCOptions
 }
@@ -80,6 +81,9 @@ func (o *PolicySignOptions) AddFlags(cmd *cobra.Command) {
 	cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false,
 		"skip confirmation prompts for non-destructive operations")
 
+	cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", false,
+		"whether or not to upload to the tlog")
+
 	o.Registry.AddFlags(cmd)
 	o.Fulcio.AddFlags(cmd)
 	o.Rekor.AddFlags(cmd)
diff --git a/cmd/cosign/cli/options/sign.go b/cmd/cosign/cli/options/sign.go
index d25db286..8f0c868f 100644
--- a/cmd/cosign/cli/options/sign.go
+++ b/cmd/cosign/cli/options/sign.go
@@ -33,6 +33,7 @@ type SignOptions struct {
 	Attachment        string
 	SkipConfirmation  bool
 	TlogUpload        bool
+	TSAServerURL      string
 
 	Rekor       RekorOptions
 	Fulcio      FulcioOptions
@@ -94,4 +95,7 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) {
 
 	cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", false,
 		"whether or not to upload to the tlog")
+
+	cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "",
+		"url to the Timestamp RFC3161 server, default none")
 }
diff --git a/cmd/cosign/cli/options/signblob.go b/cmd/cosign/cli/options/signblob.go
index 3d458bc4..1385b9ac 100644
--- a/cmd/cosign/cli/options/signblob.go
+++ b/cmd/cosign/cli/options/signblob.go
@@ -34,6 +34,7 @@ type SignBlobOptions struct {
 	Registry          RegistryOptions
 	BundlePath        string
 	SkipConfirmation  bool
+	TlogUpload        bool
 }
 
 var _ Interface = (*SignBlobOptions)(nil)
@@ -70,4 +71,7 @@ func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) {
 
 	cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false,
 		"skip confirmation prompts for non-destructive operations")
+
+	cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", false,
+		"whether or not to upload to the tlog")
 }
diff --git a/cmd/cosign/cli/options/verify.go b/cmd/cosign/cli/options/verify.go
index 72baa8b1..0a24e23c 100644
--- a/cmd/cosign/cli/options/verify.go
+++ b/cmd/cosign/cli/options/verify.go
@@ -19,6 +19,27 @@ import (
 	"github.com/spf13/cobra"
 )
 
+type CommonVerifyOptions struct {
+	Offline          bool // Force offline verification
+	TSACertChainPath string
+	TSAServerURL     string
+	SkipTlogVerify   bool
+}
+
+func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) {
+	cmd.Flags().BoolVar(&o.Offline, "offline", false,
+		"only allow offline verification")
+
+	cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "",
+		"url to a timestamp RFC3161 server, default none")
+
+	cmd.Flags().StringVar(&o.TSACertChainPath, "timestamp-cert-chain", "",
+		"path to certificate chain PEM file for the Timestamp Authority")
+
+	cmd.Flags().BoolVar(&o.SkipTlogVerify, "insecure-skip-tlog-verify", false,
+		"skip tlog verification")
+}
+
 // VerifyOptions is the top level wrapper for the `verify` command.
 type VerifyOptions struct {
 	Key          string
@@ -28,11 +49,13 @@ type VerifyOptions struct {
 	SignatureRef string
 	LocalImage   bool
 
-	SecurityKey     SecurityKeyOptions
-	CertVerify      CertVerifyOptions
-	Rekor           RekorOptions
-	Registry        RegistryOptions
-	SignatureDigest SignatureDigestOptions
+	CommonVerifyOptions CommonVerifyOptions
+	SecurityKey         SecurityKeyOptions
+	CertVerify          CertVerifyOptions
+	Rekor               RekorOptions
+	Registry            RegistryOptions
+	SignatureDigest     SignatureDigestOptions
+
 	AnnotationOptions
 }
 
@@ -46,6 +69,7 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) {
 	o.Registry.AddFlags(cmd)
 	o.SignatureDigest.AddFlags(cmd)
 	o.AnnotationOptions.AddFlags(cmd)
+	o.CommonVerifyOptions.AddFlags(cmd)
 
 	cmd.Flags().StringVar(&o.Key, "key", "",
 		"path to the public key file, KMS URI or Kubernetes Secret")
@@ -73,13 +97,14 @@ type VerifyAttestationOptions struct {
 	CheckClaims bool
 	Output      string
 
-	SecurityKey SecurityKeyOptions
-	Rekor       RekorOptions
-	CertVerify  CertVerifyOptions
-	Registry    RegistryOptions
-	Predicate   PredicateRemoteOptions
-	Policies    []string
-	LocalImage  bool
+	CommonVerifyOptions CommonVerifyOptions
+	SecurityKey         SecurityKeyOptions
+	Rekor               RekorOptions
+	CertVerify          CertVerifyOptions
+	Registry            RegistryOptions
+	Predicate           PredicateRemoteOptions
+	Policies            []string
+	LocalImage          bool
 }
 
 var _ Interface = (*VerifyAttestationOptions)(nil)
@@ -91,6 +116,7 @@ func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) {
 	o.CertVerify.AddFlags(cmd)
 	o.Registry.AddFlags(cmd)
 	o.Predicate.AddFlags(cmd)
+	o.CommonVerifyOptions.AddFlags(cmd)
 
 	cmd.Flags().StringVar(&o.Key, "key", "",
 		"path to the public key file, KMS URI or Kubernetes Secret")
@@ -114,10 +140,11 @@ type VerifyBlobOptions struct {
 	Signature  string
 	BundlePath string
 
-	SecurityKey SecurityKeyOptions
-	CertVerify  CertVerifyOptions
-	Rekor       RekorOptions
-	Registry    RegistryOptions
+	SecurityKey         SecurityKeyOptions
+	CertVerify          CertVerifyOptions
+	Rekor               RekorOptions
+	Registry            RegistryOptions
+	CommonVerifyOptions CommonVerifyOptions
 }
 
 var _ Interface = (*VerifyBlobOptions)(nil)
@@ -128,6 +155,7 @@ func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) {
 	o.Rekor.AddFlags(cmd)
 	o.CertVerify.AddFlags(cmd)
 	o.Registry.AddFlags(cmd)
+	o.CommonVerifyOptions.AddFlags(cmd)
 
 	cmd.Flags().StringVar(&o.Key, "key", "",
 		"path to the public key file, KMS URI or Kubernetes Secret")
diff --git a/cmd/cosign/cli/policy_init.go b/cmd/cosign/cli/policy_init.go
index 02428e0a..1ba62d03 100644
--- a/cmd/cosign/cli/policy_init.go
+++ b/cmd/cosign/cli/policy_init.go
@@ -181,7 +181,7 @@ func signPolicy() *cobra.Command {
 			if err != nil {
 				return err
 			}
-			sv, err := sign.SignerFromKeyOpts(ctx, "", "", options.KeyOpts{
+			ko := options.KeyOpts{
 				FulcioURL:                o.Fulcio.URL,
 				IDToken:                  o.Fulcio.IdentityToken,
 				InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify,
@@ -192,7 +192,8 @@ func signPolicy() *cobra.Command {
 				OIDCRedirectURL:          o.OIDC.RedirectURL,
 				OIDCProvider:             o.OIDC.Provider,
 				SkipConfirmation:         o.SkipConfirmation,
-			})
+			}
+			sv, err := sign.SignerFromKeyOpts(ctx, "", "", ko)
 
 			if err != nil {
 				return err
@@ -260,7 +261,7 @@ func signPolicy() *cobra.Command {
 			}
 
 			// Upload to rekor
-			if options.EnableExperimental() {
+			if sign.ShouldUploadToTlog(ctx, ko, ref, ko.SkipConfirmation, o.TlogUpload) {
 				// TODO: Refactor with sign.go
 				rekorBytes := sv.Cert
 				rekorClient, err := rekor.NewClient(o.Rekor.URL)
diff --git a/cmd/cosign/cli/sign.go b/cmd/cosign/cli/sign.go
index 8b345df5..2b2ee6ee 100644
--- a/cmd/cosign/cli/sign.go
+++ b/cmd/cosign/cli/sign.go
@@ -16,7 +16,6 @@
 package cli
 
 import (
-	"flag"
 	"fmt"
 
 	"github.com/spf13/cobra"
@@ -86,7 +85,7 @@ race conditions or (worse) malicious tampering.
 			case "sbom", "":
 				break
 			default:
-				return flag.ErrHelp
+				return fmt.Errorf("specified image attachment %s not specified. Can be 'sbom'", o.Attachment)
 			}
 			oidcClientSecret, err := o.OIDC.ClientSecret()
 			if err != nil {
@@ -108,6 +107,7 @@ race conditions or (worse) malicious tampering.
 				OIDCDisableProviders:     o.OIDC.DisableAmbientProviders,
 				OIDCProvider:             o.OIDC.Provider,
 				SkipConfirmation:         o.SkipConfirmation,
+				TSAServerURL:             o.TSAServerURL,
 			}
 			if err := sign.SignCmd(ro, ko, *o, args); err != nil {
 				if o.Attachment == "" {
diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go
index 3400d729..535511e5 100644
--- a/cmd/cosign/cli/sign/sign.go
+++ b/cmd/cosign/cli/sign/sign.go
@@ -40,6 +40,7 @@ import (
 	ifulcio "github.com/sigstore/cosign/internal/pkg/cosign/fulcio"
 	ipayload "github.com/sigstore/cosign/internal/pkg/cosign/payload"
 	irekor "github.com/sigstore/cosign/internal/pkg/cosign/rekor"
+	"github.com/sigstore/cosign/internal/pkg/cosign/tsa"
 	"github.com/sigstore/cosign/pkg/cosign"
 	"github.com/sigstore/cosign/pkg/cosign/pivkey"
 	"github.com/sigstore/cosign/pkg/cosign/pkcs11key"
@@ -54,6 +55,8 @@ import (
 	signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
 	sigPayload "github.com/sigstore/sigstore/pkg/signature/payload"
 
+	tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
+
 	// Loads OIDC providers
 	_ "github.com/sigstore/cosign/pkg/providers/all"
 )
@@ -76,6 +79,11 @@ func ShouldUploadToTlog(ctx context.Context, ko options.KeyOpts, ref name.Refere
 		return true
 	}
 
+	// We don't need to validate the ref, just return true
+	if ref == nil {
+		return true
+	}
+
 	// Check if the image is public (no auth in Get)
 	if _, err := remote.Get(ref, remote.WithContext(ctx)); err != nil {
 		fmt.Fprintf(os.Stderr, "%q appears to be a private repository, please confirm uploading to the transparency log at %q [Y/N]: ", ref.Context().String(), ko.RekorURL)
@@ -226,6 +234,15 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti
 	if sv.Cert != nil {
 		s = ifulcio.NewSigner(s, sv.Cert, sv.Chain)
 	}
+
+	if ko.TSAServerURL != "" {
+		clientTSA, err := tsaclient.GetTimestampClient(ko.TSAServerURL)
+		if err != nil {
+			return fmt.Errorf("failed to create TSA client: %w", err)
+		}
+
+		s = tsa.NewSigner(s, clientTSA)
+	}
 	if ShouldUploadToTlog(ctx, ko, digest, ko.SkipConfirmation, tlogUpload) {
 		rClient, err := rekor.NewClient(ko.RekorURL)
 		if err != nil {
diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go
index 446959af..aaf5d14b 100644
--- a/cmd/cosign/cli/sign/sign_blob.go
+++ b/cmd/cosign/cli/sign/sign_blob.go
@@ -34,7 +34,7 @@ import (
 )
 
 // nolint
-func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, regOpts options.RegistryOptions, payloadPath string, b64 bool, outputSignature string, outputCertificate string) ([]byte, error) {
+func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, regOpts options.RegistryOptions, payloadPath string, b64 bool, outputSignature string, outputCertificate string, tlogUpload bool) ([]byte, error) {
 	var payload []byte
 	var err error
 	var rekorBytes []byte
@@ -65,7 +65,7 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, regOpts options.Re
 
 	signedPayload := cosign.LocalSignedPayload{}
 
-	if options.EnableExperimental() {
+	if ShouldUploadToTlog(ctx, ko, nil, ko.SkipConfirmation, tlogUpload) {
 		rekorBytes, err = sv.Bytes(ctx)
 		if err != nil {
 			return nil, err
diff --git a/cmd/cosign/cli/signblob.go b/cmd/cosign/cli/signblob.go
index 816062fa..fe45d2f3 100644
--- a/cmd/cosign/cli/signblob.go
+++ b/cmd/cosign/cli/signblob.go
@@ -36,7 +36,7 @@ func SignBlob() *cobra.Command {
 		Example: `  cosign sign-blob --key <key path>|<kms uri> <blob>
 
   # sign a blob with Google sign-in (experimental)
-  COSIGN_EXPERIMENTAL=1 cosign --timeout 90s sign-blob <FILE>
+  cosign sign-blob <FILE> --output-signature <FILE> --output-certificate <FILE>
 
   # sign a blob with a local key pair file
   cosign sign-blob --key cosign.key <FILE>
@@ -55,11 +55,8 @@ func SignBlob() *cobra.Command {
 		Args:             cobra.MinimumNArgs(1),
 		PersistentPreRun: options.BindViper,
 		PreRunE: func(cmd *cobra.Command, args []string) error {
-			// A key file is required unless we're in experimental mode!
-			if !options.EnableExperimental() {
-				if !options.OneOf(o.Key, o.SecurityKey.Use) {
-					return &options.KeyParseError{}
-				}
+			if options.NOf(o.Key, o.SecurityKey.Use) > 1 {
+				return &options.KeyParseError{}
 			}
 			return nil
 		},
@@ -91,7 +88,7 @@ func SignBlob() *cobra.Command {
 					fmt.Fprintln(os.Stderr, "WARNING: the '--output' flag is deprecated and will be removed in the future. Use '--output-signature'")
 					o.OutputSignature = o.Output
 				}
-				if _, err := sign.SignBlobCmd(ro, ko, o.Registry, blob, o.Base64Output, o.OutputSignature, o.OutputCertificate); err != nil {
+				if _, err := sign.SignBlobCmd(ro, ko, o.Registry, blob, o.Base64Output, o.OutputSignature, o.OutputCertificate, o.TlogUpload); err != nil {
 					return fmt.Errorf("signing %s: %w", blob, err)
 				}
 			}
diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go
index d016fefa..97fc8497 100644
--- a/cmd/cosign/cli/verify.go
+++ b/cmd/cosign/cli/verify.go
@@ -17,6 +17,7 @@ package cli
 
 import (
 	"fmt"
+	"os"
 
 	"github.com/google/go-containerregistry/pkg/name"
 
@@ -92,7 +93,7 @@ against the transparency log.`,
 				return err
 			}
 
-			v := verify.VerifyCommand{
+			v := &verify.VerifyCommand{
 				RegistryOptions:              o.Registry,
 				CheckClaims:                  o.CheckClaims,
 				KeyRef:                       o.Key,
@@ -117,12 +118,20 @@ against the transparency log.`,
 				HashAlgorithm:                hashAlgorithm,
 				SignatureRef:                 o.SignatureRef,
 				LocalImage:                   o.LocalImage,
+				Offline:                      o.CommonVerifyOptions.Offline,
+				TSAServerURL:                 o.CommonVerifyOptions.TSAServerURL,
+				TSACertChainPath:             o.CommonVerifyOptions.TSACertChainPath,
+				SkipTlogVerify:               o.CommonVerifyOptions.SkipTlogVerify,
 			}
 
 			if o.Registry.AllowInsecure {
 				v.NameOptions = append(v.NameOptions, name.Insecure)
 			}
 
+			if o.CommonVerifyOptions.SkipTlogVerify {
+				fmt.Fprintln(os.Stderr, "**Warning** Skipping tlog verification is an insecure practice that lacks of transparency and auditability verification for the signature.")
+			}
+
 			return v.Exec(cmd.Context(), args)
 		},
 	}
@@ -141,7 +150,7 @@ func VerifyAttestation() *cobra.Command {
 against the transparency log.`,
 		Example: `  cosign verify-attestation --key <key path>|<key url>|<kms uri> <image uri> [<image uri> ...]
 
-  # verify cosign attestations on the image
+  # verify cosign attestations on the image against the transparency log
   cosign verify-attestation <IMAGE>
 
   # verify multiple images
@@ -150,9 +159,6 @@ against the transparency log.`,
   # additionally verify specified annotations
   cosign verify-attestation -a key1=val1 -a key2=val2 <IMAGE>
 
-  # (experimental) additionally, verify with the transparency log
-  COSIGN_EXPERIMENTAL=1 cosign verify-attestation <IMAGE>
-
   # verify image with public key
   cosign verify-attestation --key cosign.pub <IMAGE>
 
@@ -183,7 +189,7 @@ against the transparency log.`,
 		Args:             cobra.MinimumNArgs(1),
 		PersistentPreRun: options.BindViper,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			v := verify.VerifyAttestationCommand{
+			v := &verify.VerifyAttestationCommand{
 				RegistryOptions:              o.Registry,
 				CheckClaims:                  o.CheckClaims,
 				CertRef:                      o.CertVerify.Cert,
@@ -207,6 +213,7 @@ against the transparency log.`,
 				Policies:                     o.Policies,
 				LocalImage:                   o.LocalImage,
 				NameOptions:                  o.Registry.NameOptions(),
+				Offline:                      o.CommonVerifyOptions.Offline,
 			}
 
 			return v.Exec(cmd.Context(), args)
@@ -262,7 +269,7 @@ The blob may be specified as a path to a file or - for stdin.`,
   cosign verify-blob --key gitlab://[PROJECT_ID]  --signature $sig <blob>
 
   # Verify a signature against a certificate
-  COSIGN_EXPERIMENTAL=1 cosign verify-blob --certificate <cert> --signature $sig <blob>
+  cosign verify-blob --certificate <cert> --signature $sig <blob>
 `,
 
 		Args:             cobra.ExactArgs(1),
@@ -275,7 +282,7 @@ The blob may be specified as a path to a file or - for stdin.`,
 				RekorURL:   o.Rekor.URL,
 				BundlePath: o.BundlePath,
 			}
-			verifyBlobCmd := verify.VerifyBlobCmd{
+			verifyBlobCmd := &verify.VerifyBlobCmd{
 				KeyOpts:                      ko,
 				CertRef:                      o.CertVerify.Cert,
 				CertEmail:                    o.CertVerify.CertEmail,
@@ -290,6 +297,7 @@ The blob may be specified as a path to a file or - for stdin.`,
 				CertGithubWorkflowRef:        o.CertVerify.CertGithubWorkflowRef,
 				IgnoreSCT:                    o.CertVerify.IgnoreSCT,
 				SCTRef:                       o.CertVerify.SCT,
+				Offline:                      o.CommonVerifyOptions.Offline,
 			}
 			if err := verifyBlobCmd.Exec(cmd.Context(), args[0]); err != nil {
 				return fmt.Errorf("verifying blob %s: %w", args, err)
diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go
index c0ebb1ce..06fd6ab5 100644
--- a/cmd/cosign/cli/verify/verify.go
+++ b/cmd/cosign/cli/verify/verify.go
@@ -43,6 +43,7 @@ import (
 	"github.com/sigstore/sigstore/pkg/cryptoutils"
 	"github.com/sigstore/sigstore/pkg/signature"
 	"github.com/sigstore/sigstore/pkg/signature/payload"
+	tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
 )
 
 // VerifyCommand verifies a signature on a supplied container image
@@ -74,6 +75,10 @@ type VerifyCommand struct {
 	HashAlgorithm                crypto.Hash
 	LocalImage                   bool
 	NameOptions                  []name.Option
+	Offline                      bool
+	TSAServerURL                 string
+	TSACertChainPath             string
+	SkipTlogVerify               bool
 }
 
 // Exec runs the verification command
@@ -111,12 +116,28 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
 		CertGithubWorkflowRef:        c.CertGithubWorkflowRef,
 		IgnoreSCT:                    c.IgnoreSCT,
 		SignatureRef:                 c.SignatureRef,
+		Offline:                      c.Offline,
+		TSACertChainPath:             c.TSACertChainPath,
+		SkipTlogVerify:               c.SkipTlogVerify,
 	}
 	if c.CheckClaims {
 		co.ClaimVerifier = cosign.SimpleClaimVerifier
 	}
 
-	if c.keylessVerification() {
+	if c.TSAServerURL != "" {
+		co.TSAClient, err = tsaclient.GetTimestampClient(c.TSAServerURL)
+		if err != nil {
+			return fmt.Errorf("failed to create TSA client: %w", err)
+		}
+		if c.TSACertChainPath != "" {
+			_, err := os.Stat(c.TSACertChainPath)
+			if err != nil {
+				return fmt.Errorf("unable to open timestamp certificate chain file: %w", err)
+			}
+		}
+	}
+
+	if keylessVerification(c.KeyRef, c.Sk) {
 		if c.RekorURL != "" {
 			rekorClient, err := rekor.NewClient(c.RekorURL)
 			if err != nil {
@@ -354,6 +375,12 @@ func PrintVerification(imgRef string, verified []oci.Signature, output string) {
 				}
 				ss.Optional["Bundle"] = bundle
 			}
+			if tsaBundle, err := sig.TSABundle(); err == nil && tsaBundle != nil {
+				if ss.Optional == nil {
+					ss.Optional = make(map[string]interface{})
+				}
+				ss.Optional["TSABundle"] = tsaBundle
+			}
 
 			outputKeys = append(outputKeys, ss)
 		}
@@ -406,11 +433,11 @@ func loadCertChainFromFileOrURL(path string) ([]*x509.Certificate, error) {
 	return certs, nil
 }
 
-func (c *VerifyCommand) keylessVerification() bool {
-	if c.KeyRef != "" {
+func keylessVerification(keyRef string, sk bool) bool {
+	if keyRef != "" {
 		return false
 	}
-	if c.Sk {
+	if sk {
 		return false
 	}
 	return true
diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go
index a99ae22b..4284aeaf 100644
--- a/cmd/cosign/cli/verify/verify_blob.go
+++ b/cmd/cosign/cli/verify/verify_blob.go
@@ -82,6 +82,7 @@ type VerifyBlobCmd struct {
 	CertGithubWorkflowRef        string
 	IgnoreSCT                    bool
 	SCTRef                       string
+	Offline                      bool
 }
 
 // nolint
@@ -90,8 +91,8 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
 	var bundle *bundle.RekorBundle
 
 	// Require a certificate/key OR a local bundle file that has the cert.
-	if !options.OneOf(c.KeyRef, c.Sk, c.CertRef) && c.BundlePath == "" {
-		return &options.PubKeyParseError{}
+	if options.NOf(c.KeyRef, c.Sk) > 1 {
+		return &options.KeyParseError{}
 	}
 
 	sig, err := signatures(c.SigRef, c.BundlePath)
@@ -114,8 +115,9 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
 		CertGithubWorkflowRepository: c.CertGithubWorkflowRepository,
 		CertGithubWorkflowRef:        c.CertGithubWorkflowRef,
 		IgnoreSCT:                    c.IgnoreSCT,
+		Offline:                      c.Offline,
 	}
-	if options.EnableExperimental() {
+	if keylessVerification(c.KeyRef, c.Sk) {
 		if c.RekorURL != "" {
 			rekorClient, err := rekor.NewClient(c.RekorURL)
 			if err != nil {
@@ -123,8 +125,6 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
 			}
 			co.RekorClient = rekorClient
 		}
-	}
-	if options.EnableExperimental() {
 		co.RootCerts, err = fulcio.GetRoots()
 		if err != nil {
 			return fmt.Errorf("getting Fulcio roots: %w", err)
@@ -240,6 +240,8 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
 			return fmt.Errorf("loading verifier from bundle: %w", err)
 		}
 		bundle = b.Bundle
+	default:
+		return fmt.Errorf("please provide a cert to verify against via --certificate or a bundle via --bundle")
 	}
 
 	// Performs all blob verification.
diff --git a/doc/cosign_dockerfile_verify.md b/doc/cosign_dockerfile_verify.md
index 9774518f..8d1974cc 100644
--- a/doc/cosign_dockerfile_verify.md
+++ b/doc/cosign_dockerfile_verify.md
@@ -71,9 +71,11 @@ cosign dockerfile verify [flags]
       --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
+      --insecure-skip-tlog-verify                                                                skip tlog verification
       --k8s-keychain                                                                             whether to use the kubernetes keychain instead of the default keychain (supports workload identity).
       --key string                                                                               path to the public key file, KMS URI or Kubernetes Secret
       --local-image                                                                              whether the specified image is a path to an image saved locally via 'cosign save'
+      --offline                                                                                  only allow offline verification
   -o, --output string                                                                            output format for the signing image information (json|text) (default "json")
       --rekor-url string                                                                         [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev")
       --sct string                                                                               path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs.
@@ -81,6 +83,8 @@ cosign dockerfile verify [flags]
       --signature-digest-algorithm string                                                        digest algorithm to use when processing a signature (sha224|sha256|sha384|sha512) (default "sha256")
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
+      --timestamp-cert-chain string                                                              path to certificate chain PEM file for the Timestamp Authority
+      --timestamp-server-url string                                                              url to a timestamp RFC3161 server, default none
 ```
 
 ### Options inherited from parent commands
diff --git a/doc/cosign_manifest_verify.md b/doc/cosign_manifest_verify.md
index f7299fe5..0c051a3c 100644
--- a/doc/cosign_manifest_verify.md
+++ b/doc/cosign_manifest_verify.md
@@ -65,9 +65,11 @@ cosign manifest verify [flags]
       --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
+      --insecure-skip-tlog-verify                                                                skip tlog verification
       --k8s-keychain                                                                             whether to use the kubernetes keychain instead of the default keychain (supports workload identity).
       --key string                                                                               path to the public key file, KMS URI or Kubernetes Secret
       --local-image                                                                              whether the specified image is a path to an image saved locally via 'cosign save'
+      --offline                                                                                  only allow offline verification
   -o, --output string                                                                            output format for the signing image information (json|text) (default "json")
       --rekor-url string                                                                         [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev")
       --sct string                                                                               path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs.
@@ -75,6 +77,8 @@ cosign manifest verify [flags]
       --signature-digest-algorithm string                                                        digest algorithm to use when processing a signature (sha224|sha256|sha384|sha512) (default "sha256")
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
+      --timestamp-cert-chain string                                                              path to certificate chain PEM file for the Timestamp Authority
+      --timestamp-server-url string                                                              url to a timestamp RFC3161 server, default none
 ```
 
 ### Options inherited from parent commands
diff --git a/doc/cosign_policy_sign.md b/doc/cosign_policy_sign.md
index 9bc85ad7..528c4ac8 100644
--- a/doc/cosign_policy_sign.md
+++ b/doc/cosign_policy_sign.md
@@ -32,6 +32,7 @@ cosign policy sign [flags]
       --oidc-redirect-url string                                                                 [EXPERIMENTAL] OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'.
       --out string                                                                               output policy locally (default "o")
       --rekor-url string                                                                         [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev")
+      --tlog-upload                                                                              whether or not to upload to the tlog
   -y, --yes                                                                                      skip confirmation prompts for non-destructive operations
 ```
 
diff --git a/doc/cosign_sign-blob.md b/doc/cosign_sign-blob.md
index 855fa824..fb7065f8 100644
--- a/doc/cosign_sign-blob.md
+++ b/doc/cosign_sign-blob.md
@@ -12,7 +12,7 @@ cosign sign-blob [flags]
   cosign sign-blob --key <key path>|<kms uri> <blob>
 
   # sign a blob with Google sign-in (experimental)
-  COSIGN_EXPERIMENTAL=1 cosign --timeout 90s sign-blob <FILE>
+  cosign sign-blob <FILE> --output-signature <FILE> --output-certificate <FILE>
 
   # sign a blob with a local key pair file
   cosign sign-blob --key cosign.key <FILE>
@@ -56,6 +56,7 @@ cosign sign-blob [flags]
       --rekor-url string                                                                         [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev")
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
+      --tlog-upload                                                                              whether or not to upload to the tlog
   -y, --yes                                                                                      skip confirmation prompts for non-destructive operations
 ```
 
diff --git a/doc/cosign_sign.md b/doc/cosign_sign.md
index c8695d04..36056dca 100644
--- a/doc/cosign_sign.md
+++ b/doc/cosign_sign.md
@@ -89,6 +89,7 @@ cosign sign [flags]
       --rekor-url string                                                                         [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev")
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
+      --timestamp-server-url string                                                              url to the Timestamp RFC3161 server, default none
       --tlog-upload                                                                              whether or not to upload to the tlog
       --upload                                                                                   whether to upload the signature (default true)
   -y, --yes                                                                                      skip confirmation prompts for non-destructive operations
diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md
index 620d1b82..8915299f 100644
--- a/doc/cosign_verify-blob.md
+++ b/doc/cosign_verify-blob.md
@@ -51,7 +51,7 @@ cosign verify-blob [flags]
   cosign verify-blob --key gitlab://[PROJECT_ID]  --signature $sig <blob>
 
   # Verify a signature against a certificate
-  COSIGN_EXPERIMENTAL=1 cosign verify-blob --certificate <cert> --signature $sig <blob>
+  cosign verify-blob --certificate <cert> --signature $sig <blob>
 
 ```
 
@@ -74,13 +74,17 @@ cosign verify-blob [flags]
       --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
   -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 tlog verification
       --k8s-keychain                                                                             whether to use the kubernetes keychain instead of the default keychain (supports workload identity).
       --key string                                                                               path to the public key file, KMS URI or Kubernetes Secret
+      --offline                                                                                  only allow offline verification
       --rekor-url string                                                                         [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev")
       --sct string                                                                               path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs.
       --signature string                                                                         signature content or path or remote URL
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
+      --timestamp-cert-chain string                                                              path to certificate chain PEM file for the Timestamp Authority
+      --timestamp-server-url string                                                              url to a timestamp RFC3161 server, default none
 ```
 
 ### Options inherited from parent commands
diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md
index bb59b56e..4a87a16a 100644
--- a/doc/cosign_verify.md
+++ b/doc/cosign_verify.md
@@ -81,9 +81,11 @@ cosign verify [flags]
       --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
+      --insecure-skip-tlog-verify                                                                skip tlog verification
       --k8s-keychain                                                                             whether to use the kubernetes keychain instead of the default keychain (supports workload identity).
       --key string                                                                               path to the public key file, KMS URI or Kubernetes Secret
       --local-image                                                                              whether the specified image is a path to an image saved locally via 'cosign save'
+      --offline                                                                                  only allow offline verification
   -o, --output string                                                                            output format for the signing image information (json|text) (default "json")
       --rekor-url string                                                                         [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev")
       --sct string                                                                               path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs.
@@ -91,6 +93,8 @@ cosign verify [flags]
       --signature-digest-algorithm string                                                        digest algorithm to use when processing a signature (sha224|sha256|sha384|sha512) (default "sha256")
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
+      --timestamp-cert-chain string                                                              path to certificate chain PEM file for the Timestamp Authority
+      --timestamp-server-url string                                                              url to a timestamp RFC3161 server, default none
 ```
 
 ### Options inherited from parent commands
diff --git a/go.mod b/go.mod
index 809048d3..21d70983 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
 	github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21
 	github.com/cyberphone/json-canonicalization v0.0.0-20210823021906-dc406ceaf94b
 	github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936
+	github.com/digitorus/timestamp v0.0.0-20221019182153-ef3b63b79b31
 	github.com/go-openapi/runtime v0.24.2
 	github.com/go-openapi/strfmt v0.21.3
 	github.com/go-openapi/swag v0.22.3
@@ -28,6 +29,7 @@ require (
 	github.com/sigstore/fulcio v1.0.0
 	github.com/sigstore/rekor v1.0.1
 	github.com/sigstore/sigstore v1.4.5
+	github.com/sigstore/timestamp-authority v0.1.3-0.20221114113831-cf271cea5d83
 	github.com/spf13/cobra v1.6.1
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.14.0
@@ -53,7 +55,8 @@ require (
 	cloud.google.com/go/compute v1.12.1 // indirect
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
 	cloud.google.com/go/iam v0.6.0 // indirect
-	cloud.google.com/go/kms v1.5.0 // indirect
+	cloud.google.com/go/kms v1.6.0 // indirect
+	filippo.io/edwards25519 v1.0.0 // indirect
 	github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect
 	github.com/Azure/azure-sdk-for-go v67.0.0+incompatible // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
@@ -83,6 +86,7 @@ require (
 	github.com/armon/go-metrics v0.4.1 // indirect
 	github.com/armon/go-radix v1.0.0 // indirect
 	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
+	github.com/aws/aws-sdk-go v1.44.132 // indirect
 	github.com/aws/aws-sdk-go-v2 v1.16.16 // indirect
 	github.com/aws/aws-sdk-go-v2/config v1.17.8 // indirect
 	github.com/aws/aws-sdk-go-v2/credentials v1.12.21 // indirect
@@ -98,8 +102,10 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.6 // indirect
 	github.com/aws/aws-sdk-go-v2/service/sts v1.16.19 // indirect
 	github.com/aws/smithy-go v1.13.3 // indirect
+	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/blang/semver v3.5.1+incompatible // indirect
 	github.com/cenkalti/backoff/v3 v3.2.2 // indirect
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
 	github.com/clbanning/mxj/v2 v2.5.6 // indirect
 	github.com/cockroachdb/apd/v2 v2.0.1 // indirect
@@ -108,11 +114,13 @@ require (
 	github.com/coreos/go-oidc/v3 v3.4.0 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/digitorus/pkcs7 v0.0.0-20221019075359-21b8b40e6bb4 // indirect
 	github.com/dimchansky/utfbom v1.1.1 // indirect
 	github.com/docker/cli v20.10.20+incompatible // indirect
 	github.com/docker/distribution v2.8.1+incompatible // indirect
 	github.com/docker/docker v20.10.20+incompatible // indirect
 	github.com/docker/docker-credential-helpers v0.7.0 // indirect
+	github.com/docker/go-units v0.5.0 // indirect
 	github.com/emicklei/proto v1.6.15 // indirect
 	github.com/fatih/color v1.13.0 // indirect
 	github.com/fsnotify/fsnotify v1.6.0 // indirect
@@ -138,6 +146,7 @@ require (
 	github.com/golang/snappy v0.0.4 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
+	github.com/google/tink/go v1.7.0 // indirect
 	github.com/google/trillian v1.5.1-0.20220819043421-0a389c4bb8d9 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
@@ -176,6 +185,7 @@ require (
 	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.16 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
 	github.com/mitchellh/copystructure v1.2.0 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/go-testing-interface v1.14.1 // indirect
@@ -193,8 +203,13 @@ require (
 	github.com/pelletier/go-toml/v2 v2.0.5 // indirect
 	github.com/pierrec/lz4 v2.6.1+incompatible // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/prometheus/client_golang v1.14.0 // indirect
+	github.com/prometheus/client_model v0.3.0 // indirect
+	github.com/prometheus/common v0.37.0 // indirect
+	github.com/prometheus/procfs v0.8.0 // indirect
 	github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc // indirect
 	github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
+	github.com/rs/cors v1.8.2 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/ryanuber/go-glob v1.0.0 // indirect
 	github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74 // indirect
@@ -212,13 +227,15 @@ require (
 	github.com/thales-e-security/pool v0.0.2 // indirect
 	github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
 	github.com/tjfoc/gmsm v1.3.2 // indirect
+	github.com/urfave/negroni v1.0.0 // indirect
 	github.com/vbatts/tar-split v0.11.2 // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/yashtewari/glob-intersection v0.1.0 // indirect
 	github.com/zeebo/errs v1.2.2 // indirect
-	go.mongodb.org/mongo-driver v1.10.0 // indirect
+	go.mongodb.org/mongo-driver v1.10.1 // indirect
 	go.opencensus.io v0.24.0 // indirect
+	go.step.sm/crypto v0.23.1 // indirect
 	go.uber.org/atomic v1.10.0 // indirect
 	go.uber.org/multierr v1.8.0 // indirect
 	go.uber.org/zap v1.23.0 // indirect
diff --git a/go.sum b/go.sum
index a8e17bb3..8efe814f 100644
--- a/go.sum
+++ b/go.sum
@@ -53,8 +53,8 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
 cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
 cloud.google.com/go/iam v0.6.0 h1:nsqQC88kT5Iwlm4MeNGTpfMWddp6NB/UOLFTH6m1QfQ=
 cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=
-cloud.google.com/go/kms v1.5.0 h1:uc58n3b/n/F2yDMJzHMbXORkJSh3fzO4/+jju6eR7Zg=
-cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=
+cloud.google.com/go/kms v1.6.0 h1:OWRZzrPmOZUzurjI2FBGtgY2mB1WaJkqhw6oIwSj0Yg=
+cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=
 cloud.google.com/go/longrunning v0.1.1 h1:y50CXG4j0+qvEukslYFBCrzaXX0qpFbBzc3PchSu/LE=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
@@ -70,6 +70,8 @@ cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq
 cuelang.org/go v0.4.3 h1:W3oBBjDTm7+IZfCKZAmC8uDG0eYfJL4Pp/xbbCMKaVo=
 cuelang.org/go v0.4.3/go.mod h1:7805vR9H+VoBNdWFdI7jyDR3QLUPp4+naHfbcgp55HI=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
+filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
 github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 h1:8+4G8JaejP8Xa6W46PzJEwisNgBXMvFcz78N6zG/ARw=
 github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs=
 github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
@@ -129,6 +131,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.2/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
@@ -182,7 +185,8 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
 github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
-github.com/aws/aws-sdk-go v1.44.119 h1:TPkpDsanBMcZaF5wHwpKhjkapRV/b7d2qdC+a+IPbmY=
+github.com/aws/aws-sdk-go v1.44.132 h1:+IjL9VoR0OXScQ5gyme9xjcolwUkd3uaH144f4Ao+4s=
+github.com/aws/aws-sdk-go v1.44.132/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250=
 github.com/aws/aws-sdk-go-v2 v1.14.0/go.mod h1:ZA3Y8V0LrlWj63MQAnRHgKf/5QB//LSZCPNWlWrNGLU=
 github.com/aws/aws-sdk-go-v2 v1.16.16 h1:M1fj4FE2lB4NzRb9Y0xdWsn2P0+2UHVxwKyOa4YJNjk=
@@ -249,6 +253,7 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21 h1:XlpL9EHrPOBJMLDDOf35/G4t5rGAFNNAZQ3cDcWavtc=
 github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21/go.mod h1:Zlre/PVxuSI9y6/UV4NwGixQ48RHQDSPiUkofr6rbMU=
 github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
@@ -307,6 +312,10 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
 github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
+github.com/digitorus/pkcs7 v0.0.0-20221019075359-21b8b40e6bb4 h1:MxNIia2F3bgFyNsOZy9UbNlpKAxbtCudkVmlJBNuvmg=
+github.com/digitorus/pkcs7 v0.0.0-20221019075359-21b8b40e6bb4/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc=
+github.com/digitorus/timestamp v0.0.0-20221019182153-ef3b63b79b31 h1:3go0tpsBpbs9L/oysk3jDwRprlLRRkpSU7YxKlTfU+o=
+github.com/digitorus/timestamp v0.0.0-20221019182153-ef3b63b79b31/go.mod h1:6V2ND8Yf8TOJ4h+9pmUlx8kXvNLBB2QplToVVZQ3rF0=
 github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
 github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
@@ -320,6 +329,8 @@ github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avu
 github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
 github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
@@ -364,8 +375,12 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
 github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -552,6 +567,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w=
+github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM=
 github.com/google/trillian v1.5.1-0.20220819043421-0a389c4bb8d9 h1:GFmzYtwUMi1S2mjLxfrJ/CZ9gWDG+zeLtZByg/QEBkk=
 github.com/google/trillian v1.5.1-0.20220819043421-0a389c4bb8d9/go.mod h1:vywkS3p2SgNmPL7oAWqU5PiiknzRMp+ol3a19jfY2PQ=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -674,15 +691,18 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
 github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
 github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
@@ -695,6 +715,7 @@ github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B
 github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -740,6 +761,7 @@ github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peK
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
+github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
 github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
@@ -782,6 +804,7 @@ github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9
 github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY=
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -840,22 +863,35 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
-github.com/prometheus/client_golang v1.13.1 h1:3gMjIY2+/hzmqhtUC/aQNYldJA6DtH3CgQvwS+02K1c=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
+github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
+github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
+github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
+github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc h1:gSVONBi2HWMFXCa9jFdYvYk7IwW/mTLxWOF7rXS4LO0=
 github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc/go.mod h1:KbKfKPy2I6ecOIGA9apfheFv14+P3RSmmQvshofQyMY=
@@ -870,6 +906,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
+github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
+github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -895,15 +933,19 @@ github.com/sigstore/rekor v1.0.1 h1:rcESXSNkAPRWFYZel9rarspdvneET60F2ngNkadi89c=
 github.com/sigstore/rekor v1.0.1/go.mod h1:ecTKdZWGWqE1pl3U1m1JebQJLU/hSjD9vYHOmHQ7w4g=
 github.com/sigstore/sigstore v1.4.5 h1:x3bJ5ZQZecsQysJjTmop8XMlAgifP+Id+bIxaFdkNkc=
 github.com/sigstore/sigstore v1.4.5/go.mod h1:mg/+e74CCjEdJpWNjWRAlxMUd39VWh5t1+JI9UcepoY=
+github.com/sigstore/timestamp-authority v0.1.3-0.20221114113831-cf271cea5d83 h1:9knkInHcIsx8m+2WNW/iCMdfGOT1rab1BOLnpJXXjgM=
+github.com/sigstore/timestamp-authority v0.1.3-0.20221114113831-cf271cea5d83/go.mod h1:k6n2UP+ruwsyT2RiajdhKMSAv44OoHIkNPicfh0/TGU=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
+github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@@ -978,6 +1020,8 @@ github.com/transparency-dev/merkle v0.0.1/go.mod h1:B8FIw5LTq6DaULoHsVFRzYIUDkl8
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
+github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
 github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
 github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
 github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
@@ -1010,6 +1054,7 @@ github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/zalando/go-keyring v0.1.0/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
 github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
 github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
@@ -1017,8 +1062,9 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
 go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
 go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
-go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg=
 go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
+go.mongodb.org/mongo-driver v1.10.1 h1:NujsPveKwHaWuKUer/ceo9DzEe7HIj1SlJ6uvXZG0S4=
+go.mongodb.org/mongo-driver v1.10.1/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -1029,6 +1075,8 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.step.sm/crypto v0.23.1 h1:Yr9vlzjGqIKVi88KcpZtEcNTcpDkt1nVR7tumW4h+CU=
+go.step.sm/crypto v0.23.1/go.mod h1:djAhDYpNAuWF2LkzbCVcf0JDy1UWgrxR3eQ7pQ8EQ/w=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
@@ -1103,6 +1151,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1153,6 +1202,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
 golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@@ -1162,7 +1212,9 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su
 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1202,6 +1254,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1231,6 +1284,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1246,6 +1300,8 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1267,6 +1323,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1279,6 +1336,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1289,15 +1347,18 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1393,6 +1454,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
 golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
diff --git a/internal/pkg/cosign/tsa/signer.go b/internal/pkg/cosign/tsa/signer.go
new file mode 100644
index 00000000..7d4f3a2e
--- /dev/null
+++ b/internal/pkg/cosign/tsa/signer.go
@@ -0,0 +1,137 @@
+// Copyright 2021 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 tsa
+
+import (
+	"bytes"
+	"context"
+	"crypto"
+	"encoding/base64"
+	"fmt"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/digitorus/timestamp"
+	"github.com/sigstore/cosign/internal/pkg/cosign"
+	"github.com/sigstore/cosign/pkg/cosign/bundle"
+	"github.com/sigstore/cosign/pkg/oci"
+	"github.com/sigstore/cosign/pkg/oci/mutate"
+
+	"github.com/sigstore/sigstore/pkg/cryptoutils"
+	tsaclient "github.com/sigstore/timestamp-authority/pkg/generated/client"
+	ts "github.com/sigstore/timestamp-authority/pkg/generated/client/timestamp"
+)
+
+func GetTimestampedSignature(sigBytes []byte, tsaClient *tsaclient.TimestampAuthority) ([]byte, error) {
+	requestBytes, err := createTimestampAuthorityRequest(sigBytes, crypto.SHA256, "")
+	if err != nil {
+		return nil, err
+	}
+	fmt.Fprintln(os.Stderr, "Calling TSA authority ...")
+	params := ts.NewGetTimestampResponseParams()
+	params.SetTimeout(time.Second * 10)
+	params.Request = io.NopCloser(bytes.NewReader(requestBytes))
+
+	var respBytes bytes.Buffer
+	_, err = tsaClient.Timestamp.GetTimestampResponse(params, &respBytes)
+	if err != nil {
+		return nil, fmt.Errorf("unable to get the response: %w", err)
+	}
+
+	// validate that timestamp is parseable
+	ts, err := timestamp.ParseResponse(respBytes.Bytes())
+	if err != nil {
+		return nil, err
+	}
+
+	fmt.Fprintln(os.Stderr, "Timestamp fetched with time:", ts.Time)
+
+	return respBytes.Bytes(), nil
+}
+
+// signerWrapper calls a wrapped, inner signer then uploads either the Cert or Pub(licKey) of the results to Rekor, then adds the resulting `Bundle`
+type signerWrapper struct {
+	inner cosign.Signer
+
+	tsaClient *tsaclient.TimestampAuthority
+}
+
+var _ cosign.Signer = (*signerWrapper)(nil)
+
+// Sign implements `cosign.Signer`
+func (rs *signerWrapper) Sign(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) {
+	sig, pub, err := rs.inner.Sign(ctx, payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	b64Sig, err := sig.Base64Signature()
+	if err != nil {
+		return nil, nil, err
+	}
+	sigBytes, err := base64.StdEncoding.DecodeString(b64Sig)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Here we get the response from the timestamped authority server
+	responseBytes, err := GetTimestampedSignature(sigBytes, rs.tsaClient)
+	if err != nil {
+		return nil, nil, err
+	}
+	bundle := bundle.TimestampToTSABundle(responseBytes)
+
+	newSig, err := mutate.Signature(sig, mutate.WithTSABundle(bundle))
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return newSig, pub, nil
+}
+
+func createTimestampAuthorityRequest(artifactBytes []byte, hash crypto.Hash, policyStr string) ([]byte, error) {
+	reqOpts := &timestamp.RequestOptions{
+		Hash:         hash,
+		Certificates: true, // if the timestamp response should contain the leaf certificate
+	}
+	// specify a pseudo-random nonce in the request
+	nonce, err := cryptoutils.GenerateSerialNumber()
+	if err != nil {
+		return nil, err
+	}
+	reqOpts.Nonce = nonce
+
+	if policyStr != "" {
+		var oidInts []int
+		for _, v := range strings.Split(policyStr, ".") {
+			i, _ := strconv.Atoi(v)
+			oidInts = append(oidInts, i)
+		}
+		reqOpts.TSAPolicyOID = oidInts
+	}
+
+	return timestamp.CreateRequest(bytes.NewReader(artifactBytes), reqOpts)
+}
+
+// NewSigner returns a `cosign.Signer` which uploads the signature to a TSA
+func NewSigner(inner cosign.Signer, tsaClient *tsaclient.TimestampAuthority) cosign.Signer {
+	return &signerWrapper{
+		inner:     inner,
+		tsaClient: tsaClient,
+	}
+}
diff --git a/pkg/cosign/bundle/tsa.go b/pkg/cosign/bundle/tsa.go
new file mode 100644
index 00000000..b2ba3937
--- /dev/null
+++ b/pkg/cosign/bundle/tsa.go
@@ -0,0 +1,33 @@
+// 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 bundle
+
+// TSABundle holds metadata about timestamp RFC3161 verification data.
+type TSABundle struct {
+	// SignedRFC3161Timestamp contains a RFC3161 signed timestamp provided by a time-stamping server.
+	// Clients MUST verify the hashed message in the message imprint
+	// against the signature in the bundle. This is encoded as base64.
+	SignedRFC3161Timestamp []byte
+}
+
+// TimestampToTSABundle receives a base64 encoded RFC3161 timestamp.
+func TimestampToTSABundle(timestampRFC3161 []byte) *TSABundle {
+	if timestampRFC3161 != nil {
+		return &TSABundle{
+			SignedRFC3161Timestamp: timestampRFC3161,
+		}
+	}
+	return nil
+}
diff --git a/pkg/cosign/fetch.go b/pkg/cosign/fetch.go
index 1fc76115..2f510d11 100644
--- a/pkg/cosign/fetch.go
+++ b/pkg/cosign/fetch.go
@@ -35,6 +35,7 @@ type SignedPayload struct {
 	Cert            *x509.Certificate
 	Chain           []*x509.Certificate
 	Bundle          *bundle.RekorBundle
+	TSABundle       *bundle.TSABundle
 }
 
 type LocalSignedPayload struct {
@@ -101,6 +102,12 @@ func FetchSignaturesForReference(ctx context.Context, ref name.Reference, opts .
 			if err != nil {
 				return err
 			}
+
+			signatures[i].TSABundle, err = sig.TSABundle()
+			if err != nil {
+				return err
+			}
+
 			signatures[i].Bundle, err = sig.Bundle()
 			return err
 		})
diff --git a/pkg/cosign/keys.go b/pkg/cosign/keys.go
index 0a4e9398..f224ab92 100644
--- a/pkg/cosign/keys.go
+++ b/pkg/cosign/keys.go
@@ -46,6 +46,7 @@ const (
 	// PEM-encoded PKCS #8 RSA, ECDSA or ED25519 private key
 	PrivateKeyPemType = "PRIVATE KEY"
 	BundleKey         = static.BundleAnnotationKey
+	TSABundleKey      = static.TSABundleAnnotationKey
 )
 
 // PassFunc is the function to be called to retrieve the signer password. If
diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go
index 97540d84..d07fa283 100644
--- a/pkg/cosign/verify.go
+++ b/pkg/cosign/verify.go
@@ -28,10 +28,12 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"path/filepath"
 	"regexp"
 	"strings"
 	"time"
 
+	"github.com/digitorus/timestamp"
 	"github.com/sigstore/cosign/cmd/cosign/cli/fulcio/fulcioverifier/ctl"
 	cbundle "github.com/sigstore/cosign/pkg/cosign/bundle"
 	"github.com/sigstore/sigstore/pkg/tuf"
@@ -55,6 +57,8 @@ import (
 	"github.com/sigstore/sigstore/pkg/signature/dsse"
 	"github.com/sigstore/sigstore/pkg/signature/options"
 	sigPayload "github.com/sigstore/sigstore/pkg/signature/payload"
+	tsaclient "github.com/sigstore/timestamp-authority/pkg/generated/client"
+	tsaverification "github.com/sigstore/timestamp-authority/pkg/verification"
 )
 
 // Identity specifies an issuer/subject to verify a signature against.
@@ -121,6 +125,18 @@ type CheckOpts struct {
 	// to be met for the signature to ve valid.
 	// Supercedes CertEmail / CertOidcIssuer
 	Identities []Identity
+
+	// Force offline verification of the signature
+	Offline bool
+
+	// TSAClient, if set, is used to verify signatures using a RFC3161 time-stamping server.
+	TSAClient *tsaclient.TimestampAuthority
+
+	// TSACertChainPath set the path to PEM-encoded certificate chain to act as a timestamping authority.
+	TSACertChainPath string
+
+	// SkipTlogVerify skip tlog verification
+	SkipTlogVerify bool
 }
 
 // This is a substitutable signature verification function that can be used for verifying
@@ -671,10 +687,24 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash,
 			return bundleVerified, err
 		}
 	}
+	if co.TSAClient != nil {
+		bundleVerified, err = VerifyTSABundle(ctx, sig, co.TSAClient, co.TSACertChainPath)
+		if err != nil {
+			return false, fmt.Errorf("unable to verify TSA bundle: %w", err)
+		}
+	}
+	if co.SkipTlogVerify {
+		return bundleVerified, nil
+	}
 
 	bundleVerified, err = VerifyBundle(ctx, sig, co.RekorClient)
-	if err != nil && co.RekorClient == nil {
-		return false, fmt.Errorf("unable to verify bundle: %w", err)
+	if err != nil {
+		return false, fmt.Errorf("error verifying bundle: %w", err)
+	}
+
+	// If the --offline flag was specified, fail here
+	if !bundleVerified && co.Offline {
+		return false, fmt.Errorf("offline verification failed")
 	}
 
 	if !bundleVerified && co.RekorClient != nil {
@@ -920,6 +950,61 @@ func VerifyBundle(ctx context.Context, sig oci.Signature, rekorClient *client.Re
 	return true, nil
 }
 
+func VerifyTSABundle(ctx context.Context, sig oci.Signature, tsaClient *tsaclient.TimestampAuthority, tsaCertChainPath string) (bool, error) {
+	bundle, err := sig.TSABundle()
+	if err != nil {
+		return false, err
+	} else if bundle == nil {
+		return false, nil
+	}
+
+	b64Sig, err := sig.Base64Signature()
+	if err != nil {
+		return false, fmt.Errorf("reading base64signature: %w", err)
+	}
+
+	cert, err := sig.Cert()
+	if err != nil {
+		return false, err
+	}
+
+	sigBytes, err := base64.StdEncoding.DecodeString(b64Sig)
+	if err != nil {
+		return false, fmt.Errorf("reading DecodeString: %w", err)
+	}
+	// TODO: Add support for TUF certificates.
+	pemBytes, err := os.ReadFile(filepath.Clean(tsaCertChainPath))
+	if err != nil {
+		return false, fmt.Errorf("error reading certification chain path file: %w", err)
+	}
+	// TODO: Update this logic once https://github.com/sigstore/timestamp-authority/issues/121 gets merged.
+	// This relies on untrusted leaf certificate.
+	certPool := x509.NewCertPool()
+	ok := certPool.AppendCertsFromPEM(pemBytes)
+	if !ok {
+		return false, fmt.Errorf("error parsing response into Timestamp while appending certs from PEM")
+	}
+
+	err = tsaverification.VerifyTimestampResponse(bundle.SignedRFC3161Timestamp, bytes.NewReader(sigBytes), certPool)
+	if err != nil {
+		return false, fmt.Errorf("unable to verify TimestampResponse: %w", err)
+	}
+
+	if cert != nil {
+		ts, err := timestamp.ParseResponse(bundle.SignedRFC3161Timestamp)
+		if err != nil {
+			return false, fmt.Errorf("error parsing response into timestamp: %w", err)
+		}
+		// Verify the cert against the integrated time.
+		// Note that if the caller requires the certificate to be present, it has to ensure that itself.
+		if err := CheckExpiry(cert, ts.Time); err != nil {
+			return false, fmt.Errorf("checking expiry on cert: %w", err)
+		}
+	}
+
+	return true, nil
+}
+
 // compare bundle signature to the signature we are verifying
 func compareSigs(bundleBody string, sig oci.Signature) error {
 	// TODO(nsmith5): modify function signature to make it more clear _why_
diff --git a/pkg/oci/internal/signature/layer.go b/pkg/oci/internal/signature/layer.go
index 3a815d3c..35b1c29e 100644
--- a/pkg/oci/internal/signature/layer.go
+++ b/pkg/oci/internal/signature/layer.go
@@ -29,10 +29,11 @@ import (
 )
 
 const (
-	sigkey    = "dev.cosignproject.cosign/signature"
-	certkey   = "dev.sigstore.cosign/certificate"
-	chainkey  = "dev.sigstore.cosign/chain"
-	BundleKey = "dev.sigstore.cosign/bundle"
+	sigkey       = "dev.cosignproject.cosign/signature"
+	certkey      = "dev.sigstore.cosign/certificate"
+	chainkey     = "dev.sigstore.cosign/chain"
+	BundleKey    = "dev.sigstore.cosign/bundle"
+	TSABundleKey = "dev.sigstore.cosign/3161timestamp"
 )
 
 type sigLayer struct {
@@ -115,3 +116,16 @@ func (s *sigLayer) Bundle() (*bundle.RekorBundle, error) {
 	}
 	return &b, nil
 }
+
+// TSABundle implements oci.Signature
+func (s *sigLayer) TSABundle() (*bundle.TSABundle, error) {
+	val := s.desc.Annotations[TSABundleKey]
+	if val == "" {
+		return nil, nil
+	}
+	var b bundle.TSABundle
+	if err := json.Unmarshal([]byte(val), &b); err != nil {
+		return nil, fmt.Errorf("unmarshaling tsa bundle: %w", err)
+	}
+	return &b, nil
+}
diff --git a/pkg/oci/mutate/options.go b/pkg/oci/mutate/options.go
index 0c19f3da..35e9282f 100644
--- a/pkg/oci/mutate/options.go
+++ b/pkg/oci/mutate/options.go
@@ -62,6 +62,7 @@ func WithReplaceOp(ro ReplaceOp) SignOption {
 type signatureOpts struct {
 	annotations map[string]string
 	bundle      *bundle.RekorBundle
+	tsaBundle   *bundle.TSABundle
 	cert        []byte
 	chain       []byte
 	mediaType   types.MediaType
@@ -83,6 +84,13 @@ func WithBundle(b *bundle.RekorBundle) SignatureOption {
 	}
 }
 
+// WithTSABundle specifies the new TSABundle the Signature should have.
+func WithTSABundle(b *bundle.TSABundle) SignatureOption {
+	return func(so *signatureOpts) {
+		so.tsaBundle = b
+	}
+}
+
 // WithCertChain specifies the new cert and chain the Signature should have.
 func WithCertChain(cert, chain []byte) SignatureOption {
 	return func(so *signatureOpts) {
diff --git a/pkg/oci/mutate/signature.go b/pkg/oci/mutate/signature.go
index ed07f454..0955a4af 100644
--- a/pkg/oci/mutate/signature.go
+++ b/pkg/oci/mutate/signature.go
@@ -34,6 +34,7 @@ type sigWrapper struct {
 
 	annotations map[string]string
 	bundle      *bundle.RekorBundle
+	tsaBundle   *bundle.TSABundle
 	cert        *x509.Certificate
 	chain       []*x509.Certificate
 	mediaType   types.MediaType
@@ -92,6 +93,14 @@ func (sw *sigWrapper) Bundle() (*bundle.RekorBundle, error) {
 	return sw.wrapped.Bundle()
 }
 
+// TSABundle implements oci.Signature.
+func (sw *sigWrapper) TSABundle() (*bundle.TSABundle, error) {
+	if sw.tsaBundle != nil {
+		return sw.tsaBundle, nil
+	}
+	return sw.wrapped.TSABundle()
+}
+
 // MediaType implements v1.Layer
 func (sw *sigWrapper) MediaType() (types.MediaType, error) {
 	if sw.mediaType != "" {
@@ -139,7 +148,7 @@ func Signature(original oci.Signature, opts ...SignatureOption) (oci.Signature,
 	if so.annotations != nil {
 		newAnn = copyAnnotations(so.annotations)
 		newAnn[static.SignatureAnnotationKey] = oldAnn[static.SignatureAnnotationKey]
-		for _, key := range []string{static.BundleAnnotationKey, static.CertificateAnnotationKey, static.ChainAnnotationKey} {
+		for _, key := range []string{static.BundleAnnotationKey, static.CertificateAnnotationKey, static.ChainAnnotationKey, static.TSABundleAnnotationKey} {
 			if val, isSet := oldAnn[key]; isSet {
 				newAnn[key] = val
 			} else {
@@ -159,6 +168,15 @@ func Signature(original oci.Signature, opts ...SignatureOption) (oci.Signature,
 		newAnn[static.BundleAnnotationKey] = string(b)
 	}
 
+	if so.tsaBundle != nil {
+		newSig.tsaBundle = so.tsaBundle
+		b, err := json.Marshal(so.tsaBundle)
+		if err != nil {
+			return nil, err
+		}
+		newAnn[static.TSABundleAnnotationKey] = string(b)
+	}
+
 	if so.cert != nil {
 		var cert *x509.Certificate
 		var chain []*x509.Certificate
diff --git a/pkg/oci/signature/layer.go b/pkg/oci/signature/layer.go
index 3a815d3c..35b1c29e 100644
--- a/pkg/oci/signature/layer.go
+++ b/pkg/oci/signature/layer.go
@@ -29,10 +29,11 @@ import (
 )
 
 const (
-	sigkey    = "dev.cosignproject.cosign/signature"
-	certkey   = "dev.sigstore.cosign/certificate"
-	chainkey  = "dev.sigstore.cosign/chain"
-	BundleKey = "dev.sigstore.cosign/bundle"
+	sigkey       = "dev.cosignproject.cosign/signature"
+	certkey      = "dev.sigstore.cosign/certificate"
+	chainkey     = "dev.sigstore.cosign/chain"
+	BundleKey    = "dev.sigstore.cosign/bundle"
+	TSABundleKey = "dev.sigstore.cosign/3161timestamp"
 )
 
 type sigLayer struct {
@@ -115,3 +116,16 @@ func (s *sigLayer) Bundle() (*bundle.RekorBundle, error) {
 	}
 	return &b, nil
 }
+
+// TSABundle implements oci.Signature
+func (s *sigLayer) TSABundle() (*bundle.TSABundle, error) {
+	val := s.desc.Annotations[TSABundleKey]
+	if val == "" {
+		return nil, nil
+	}
+	var b bundle.TSABundle
+	if err := json.Unmarshal([]byte(val), &b); err != nil {
+		return nil, fmt.Errorf("unmarshaling tsa bundle: %w", err)
+	}
+	return &b, nil
+}
diff --git a/pkg/oci/signatures.go b/pkg/oci/signatures.go
index e66b7c6c..cec3d5bb 100644
--- a/pkg/oci/signatures.go
+++ b/pkg/oci/signatures.go
@@ -59,4 +59,8 @@ type Signature interface {
 	// Bundle fetches the optional metadata that records the ephemeral
 	// Fulcio key in the transparency log.
 	Bundle() (*bundle.RekorBundle, error)
+
+	// TSABundle fetches the optional metadata that records a
+	// RFC3161 signed timestamp.
+	TSABundle() (*bundle.TSABundle, error)
 }
diff --git a/pkg/oci/static/options.go b/pkg/oci/static/options.go
index e00b6cdb..62f7960e 100644
--- a/pkg/oci/static/options.go
+++ b/pkg/oci/static/options.go
@@ -30,6 +30,7 @@ type options struct {
 	LayerMediaType  types.MediaType
 	ConfigMediaType types.MediaType
 	Bundle          *bundle.RekorBundle
+	TSABundle       *bundle.TSABundle
 	Cert            []byte
 	Chain           []byte
 	Annotations     map[string]string
@@ -59,6 +60,13 @@ func makeOptions(opts ...Option) (*options, error) {
 		o.Annotations[BundleAnnotationKey] = string(b)
 	}
 
+	if o.TSABundle != nil {
+		b, err := json.Marshal(o.TSABundle)
+		if err != nil {
+			return nil, err
+		}
+		o.Annotations[TSABundleAnnotationKey] = string(b)
+	}
 	return o, nil
 }
 
@@ -90,6 +98,13 @@ func WithBundle(b *bundle.RekorBundle) Option {
 	}
 }
 
+// WithTSABundle sets the time-stamping bundle to attach to the signature
+func WithTSABundle(b *bundle.TSABundle) Option {
+	return func(o *options) {
+		o.TSABundle = b
+	}
+}
+
 // WithCertChain sets the certificate chain for this signature.
 func WithCertChain(cert, chain []byte) Option {
 	return func(o *options) {
diff --git a/pkg/oci/static/signature.go b/pkg/oci/static/signature.go
index 1766102c..aa5044ec 100644
--- a/pkg/oci/static/signature.go
+++ b/pkg/oci/static/signature.go
@@ -32,6 +32,7 @@ const (
 	CertificateAnnotationKey = "dev.sigstore.cosign/certificate"
 	ChainAnnotationKey       = "dev.sigstore.cosign/chain"
 	BundleAnnotationKey      = "dev.sigstore.cosign/bundle"
+	TSABundleAnnotationKey   = "dev.sigstore.cosign/3161timestamp"
 )
 
 // NewSignature constructs a new oci.Signature from the provided options.
@@ -85,6 +86,12 @@ func Copy(sig oci.Signature) (oci.Signature, error) {
 	}
 	opts = append(opts, WithBundle(bundle))
 
+	tsaBundle, err := sig.TSABundle()
+	if err != nil {
+		return nil, err
+	}
+	opts = append(opts, WithTSABundle(tsaBundle))
+
 	cert, err := sig.Cert()
 	if err != nil {
 		return nil, err
@@ -162,6 +169,11 @@ func (l *staticLayer) Bundle() (*bundle.RekorBundle, error) {
 	return l.opts.Bundle, nil
 }
 
+// TSABundle implements oci.Signature
+func (l *staticLayer) TSABundle() (*bundle.TSABundle, error) {
+	return l.opts.TSABundle, nil
+}
+
 // Digest implements v1.Layer
 func (l *staticLayer) Digest() (v1.Hash, error) {
 	h, _, err := v1.SHA256(bytes.NewReader(l.b))


Test patch:
diff --git a/.github/workflows/kind-verify-attestation.yaml b/.github/workflows/kind-verify-attestation.yaml
index d37fd7ba..98022e4e 100644
--- a/.github/workflows/kind-verify-attestation.yaml
+++ b/.github/workflows/kind-verify-attestation.yaml
@@ -57,7 +57,7 @@ jobs:
     - uses: imjasonh/setup-ko@ace48d793556083a76f1e3e6068850c1f4a369aa # v0.6
 
     - name: Install yq
-      uses: mikefarah/yq@83ef7ee5cfd9e39d967130871def934042d94b21 # v4.30.1
+      uses: mikefarah/yq@ce9701ca3a1ac2779ff5f3857d04ee003acda066 # v4.30.2
 
     - name: build cosign
       run: |
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 5faf0e45..5f2c0477 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -181,5 +181,5 @@ jobs:
         uses: golangci/golangci-lint-action@0ad9a0988b3973e851ab0a07adf248ec2e100376 # v3.3.1
         with:
           # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
-          version: v1.49
+          version: v1.50.1
           args: --timeout=5m
diff --git a/cmd/cosign/cli/attest.go b/cmd/cosign/cli/attest.go
index 6b7e445a..400d1a58 100644
--- a/cmd/cosign/cli/attest.go
+++ b/cmd/cosign/cli/attest.go
@@ -33,8 +33,8 @@ func Attest() *cobra.Command {
 		Short: "Attest the supplied container image.",
 		Example: `  cosign attest --key <key path>|<kms uri> [--predicate <path>] [--a key=value] [--no-upload=true|false] [--f] [--r] <image uri>
 
-  # attach an attestation to a container image Google sign-in (experimental)
-  COSIGN_EXPERIMENTAL=1 cosign attest --timeout 90s --predicate <FILE> --type <TYPE> <IMAGE>
+  # attach an attestation to a container image Google sign-in
+  cosign attest --timeout 90s --predicate <FILE> --type <TYPE> <IMAGE>
 
   # attach an attestation to a container image with a local key pair file
   cosign attest --predicate <FILE> --type <TYPE> --key cosign.key <IMAGE>
diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go
index 5a6b969c..d9e10f3d 100644
--- a/cmd/cosign/cli/attest/attest.go
+++ b/cmd/cosign/cli/attest/attest.go
@@ -81,15 +81,9 @@ type AttestCommand struct {
 
 // nolint
 func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
-	// A key file or token is required unless we're in experimental mode!
-	if options.EnableExperimental() {
-		if options.NOf(c.KeyRef, c.Sk) > 1 {
-			return &options.KeyParseError{}
-		}
-	} else {
-		if !options.OneOf(c.KeyRef, c.Sk) {
-			return &options.KeyParseError{}
-		}
+	// We can't have both a key and a security key
+	if options.NOf(c.KeyRef, c.Sk) > 1 {
+		return &options.KeyParseError{}
 	}
 
 	predicateURI, err := options.ParsePredicateType(c.PredicateType)
diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go
index a7f7d406..2c83739d 100644
--- a/cmd/cosign/cli/verify/verify_attestation.go
+++ b/cmd/cosign/cli/verify/verify_attestation.go
@@ -64,6 +64,7 @@ type VerifyAttestationCommand struct {
 	Policies                     []string
 	LocalImage                   bool
 	NameOptions                  []name.Option
+	Offline                      bool
 }
 
 // Exec runs the verification command
@@ -72,8 +73,9 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
 		return flag.ErrHelp
 	}
 
-	if !options.OneOf(c.KeyRef, c.Sk, c.CertRef) && !options.EnableExperimental() {
-		return &options.PubKeyParseError{}
+	// We can't have both a key and a security key
+	if options.NOf(c.KeyRef, c.Sk) > 1 {
+		return &options.KeyParseError{}
 	}
 
 	ociremoteOpts, err := c.ClientOpts(ctx)
@@ -91,11 +93,12 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
 		CertGithubWorkflowRepository: c.CertGithubWorkflowRepository,
 		CertGithubWorkflowRef:        c.CertGithubWorkflowRef,
 		IgnoreSCT:                    c.IgnoreSCT,
+		Offline:                      c.Offline,
 	}
 	if c.CheckClaims {
 		co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
 	}
-	if options.EnableExperimental() {
+	if keylessVerification(c.KeyRef, c.Sk) {
 		if c.RekorURL != "" {
 			rekorClient, err := rekor.NewClient(c.RekorURL)
 			if err != nil {
diff --git a/doc/cosign_attest.md b/doc/cosign_attest.md
index 9c4096ca..88ae2da2 100644
--- a/doc/cosign_attest.md
+++ b/doc/cosign_attest.md
@@ -11,8 +11,8 @@ cosign attest [flags]
 ```
   cosign attest --key <key path>|<kms uri> [--predicate <path>] [--a key=value] [--no-upload=true|false] [--f] [--r] <image uri>
 
-  # attach an attestation to a container image Google sign-in (experimental)
-  COSIGN_EXPERIMENTAL=1 cosign attest --timeout 90s --predicate <FILE> --type <TYPE> <IMAGE>
+  # attach an attestation to a container image Google sign-in
+  cosign attest --timeout 90s --predicate <FILE> --type <TYPE> <IMAGE>
 
   # attach an attestation to a container image with a local key pair file
   cosign attest --predicate <FILE> --type <TYPE> --key cosign.key <IMAGE>
diff --git a/doc/cosign_verify-attestation.md b/doc/cosign_verify-attestation.md
index 12be0e2a..a07bcade 100644
--- a/doc/cosign_verify-attestation.md
+++ b/doc/cosign_verify-attestation.md
@@ -16,7 +16,7 @@ cosign verify-attestation [flags]
 ```
   cosign verify-attestation --key <key path>|<key url>|<kms uri> <image uri> [<image uri> ...]
 
-  # verify cosign attestations on the image
+  # verify cosign attestations on the image against the transparency log
   cosign verify-attestation <IMAGE>
 
   # verify multiple images
@@ -25,9 +25,6 @@ cosign verify-attestation [flags]
   # additionally verify specified annotations
   cosign verify-attestation -a key1=val1 -a key2=val2 <IMAGE>
 
-  # (experimental) additionally, verify with the transparency log
-  COSIGN_EXPERIMENTAL=1 cosign verify-attestation <IMAGE>
-
   # verify image with public key
   cosign verify-attestation --key cosign.pub <IMAGE>
 
@@ -75,15 +72,19 @@ cosign verify-attestation [flags]
       --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
+      --insecure-skip-tlog-verify                                                                skip tlog verification
       --k8s-keychain                                                                             whether to use the kubernetes keychain instead of the default keychain (supports workload identity).
       --key string                                                                               path to the public key file, KMS URI or Kubernetes Secret
       --local-image                                                                              whether the specified image is a path to an image saved locally via 'cosign save'
+      --offline                                                                                  only allow offline verification
   -o, --output string                                                                            output format for the signing image information (json|text) (default "json")
       --policy strings                                                                           specify CUE or Rego files will be using for validation
       --rekor-url string                                                                         [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev")
       --sct string                                                                               path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs.
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
+      --timestamp-cert-chain string                                                              path to certificate chain PEM file for the Timestamp Authority
+      --timestamp-server-url string                                                              url to a timestamp RFC3161 server, default none
       --type string                                                                              specify a predicate type (slsaprovenance|link|spdx|spdxjson|cyclonedx|vuln|custom) or an URI (default "custom")
 ```
 
diff --git a/internal/pkg/cosign/tsa/signer_test.go b/internal/pkg/cosign/tsa/signer_test.go
new file mode 100644
index 00000000..61f4f007
--- /dev/null
+++ b/internal/pkg/cosign/tsa/signer_test.go
@@ -0,0 +1,95 @@
+// 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 tsa
+
+import (
+	"bytes"
+	"context"
+	"crypto"
+	"encoding/base64"
+	"net/http/httptest"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/sigstore/cosign/internal/pkg/cosign/payload"
+	"github.com/sigstore/cosign/pkg/cosign"
+	"github.com/sigstore/sigstore/pkg/signature"
+	tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
+	"github.com/sigstore/timestamp-authority/pkg/server"
+	"github.com/spf13/viper"
+)
+
+func mustGetNewSigner(t *testing.T) signature.Signer {
+	t.Helper()
+	priv, err := cosign.GeneratePrivateKey()
+	if err != nil {
+		t.Fatalf("cosign.GeneratePrivateKey() failed: %v", err)
+	}
+	s, err := signature.LoadECDSASignerVerifier(priv, crypto.SHA256)
+	if err != nil {
+		t.Fatalf("signature.LoadECDSASignerVerifier(key, crypto.SHA256) failed: %v", err)
+	}
+	return s
+}
+
+func TestSigner(t *testing.T) {
+	// TODO: Replace with a full TSA mock client, related to https://github.com/sigstore/timestamp-authority/issues/146
+	viper.Set("timestamp-signer", "memory")
+	apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, 10*time.Second, 10*time.Second)
+	server := httptest.NewServer(apiServer.GetHandler())
+	t.Cleanup(server.Close)
+
+	// Need real cert and chain
+	payloadSigner := payload.NewSigner(mustGetNewSigner(t))
+
+	client, err := tsaclient.GetTimestampClient(server.URL)
+	if err != nil {
+		t.Error(err)
+	}
+
+	testSigner := NewSigner(payloadSigner, client)
+
+	testPayload := "test payload"
+
+	ociSig, pub, err := testSigner.Sign(context.Background(), strings.NewReader(testPayload))
+	if err != nil {
+		t.Fatalf("Sign() returned error: %v", err)
+	}
+
+	// Verify that the wrapped signer was called.
+	verifier, err := signature.LoadVerifier(pub, crypto.SHA256)
+	if err != nil {
+		t.Fatalf("signature.LoadVerifier(pub) returned error: %v", err)
+	}
+	b64Sig, err := ociSig.Base64Signature()
+	if err != nil {
+		t.Fatalf("ociSig.Base64Signature() returned error: %v", err)
+	}
+	sig, err := base64.StdEncoding.DecodeString(b64Sig)
+	if err != nil {
+		t.Fatalf("base64.StdEncoding.DecodeString(b64Sig) returned error: %v", err)
+	}
+	gotPayload, err := ociSig.Payload()
+	if err != nil {
+		t.Fatalf("ociSig.Payload() returned error: %v", err)
+	}
+	if string(gotPayload) != testPayload {
+		t.Errorf("ociSig.Payload() returned %q, wanted %q", string(gotPayload), testPayload)
+	}
+	if err = verifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(gotPayload)); err != nil {
+		t.Errorf("VerifySignature() returned error: %v", err)
+	}
+}
diff --git a/pkg/cosign/bundle/rekor_test.go b/pkg/cosign/bundle/rekor_test.go
new file mode 100644
index 00000000..eb0e26ad
--- /dev/null
+++ b/pkg/cosign/bundle/rekor_test.go
@@ -0,0 +1,77 @@
+// 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 bundle
+
+import (
+	"encoding/base64"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/sigstore/rekor/pkg/generated/models"
+)
+
+func TestRekorBundle(t *testing.T) {
+	testCases := []struct {
+		name                string
+		logEntry            *models.LogEntryAnon
+		expectedRekorBundle *RekorBundle
+	}{{
+		name: "tlog entry without verification - nil bundle",
+		logEntry: &models.LogEntryAnon{
+			Body:           base64.StdEncoding.EncodeToString([]byte("TEST")),
+			IntegratedTime: swag.Int64(time.Now().Unix()),
+			LogIndex:       swag.Int64(0),
+			LogID:          swag.String("c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"),
+		},
+		expectedRekorBundle: nil,
+	}, {
+		name: "tlog entry with verification",
+		logEntry: &models.LogEntryAnon{
+			Body:           base64.StdEncoding.EncodeToString([]byte("TEST")),
+			IntegratedTime: swag.Int64(time.Now().Unix()),
+			LogIndex:       swag.Int64(0),
+			LogID:          swag.String("c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"),
+			Verification: &models.LogEntryAnonVerification{
+				SignedEntryTimestamp: strfmt.Base64([]byte("signature")),
+				InclusionProof: &models.InclusionProof{
+					LogIndex: swag.Int64(0),
+					TreeSize: swag.Int64(1),
+					RootHash: swag.String("TEST"),
+					Hashes:   []string{},
+				},
+			},
+		},
+		expectedRekorBundle: &RekorBundle{
+			Payload: RekorPayload{
+				Body:           base64.StdEncoding.EncodeToString([]byte("TEST")),
+				IntegratedTime: time.Now().Unix(),
+				LogIndex:       0,
+				LogID:          "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d",
+			},
+			SignedEntryTimestamp: strfmt.Base64([]byte("signature")),
+		},
+	}}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			gotBundle := EntryToBundle(tc.logEntry)
+			if !reflect.DeepEqual(gotBundle, tc.expectedRekorBundle) {
+				t.Errorf("EntryToBundle returned %v, wanted %v", gotBundle, tc.expectedRekorBundle)
+			}
+		})
+	}
+}
diff --git a/pkg/cosign/bundle/tsa_test.go b/pkg/cosign/bundle/tsa_test.go
new file mode 100644
index 00000000..f73114fc
--- /dev/null
+++ b/pkg/cosign/bundle/tsa_test.go
@@ -0,0 +1,48 @@
+// 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 bundle
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/go-openapi/strfmt"
+)
+
+func TestTSABundle(t *testing.T) {
+	testCases := []struct {
+		name                  string
+		timestampRFC3161Entry []byte
+		expectedTSABundle     *TSABundle
+	}{{
+		name:                  "nil timestamp entry",
+		timestampRFC3161Entry: nil,
+		expectedTSABundle:     nil,
+	}, {
+		name:                  "timestamp entry",
+		timestampRFC3161Entry: strfmt.Base64([]byte("signature")),
+		expectedTSABundle: &TSABundle{
+			SignedRFC3161Timestamp: strfmt.Base64([]byte("signature")),
+		},
+	}}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			gotBundle := TimestampToTSABundle(tc.timestampRFC3161Entry)
+			if !reflect.DeepEqual(gotBundle, tc.expectedTSABundle) {
+				t.Errorf("TimestampToTSABundle returned %v, wanted %v", gotBundle, tc.expectedTSABundle)
+			}
+		})
+	}
+}
diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go
index 395bb74c..75c5b3d6 100644
--- a/pkg/cosign/verify_test.go
+++ b/pkg/cosign/verify_test.go
@@ -30,7 +30,9 @@ import (
 	"errors"
 	"io"
 	"net"
+	"net/http/httptest"
 	"net/url"
+	"os"
 	"strings"
 	"testing"
 	"time"
@@ -40,7 +42,9 @@ import (
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/in-toto/in-toto-golang/in_toto"
 	"github.com/secure-systems-lab/go-securesystemslib/dsse"
+	"github.com/sigstore/cosign/internal/pkg/cosign/payload"
 	"github.com/sigstore/cosign/internal/pkg/cosign/rekor/mock"
+	"github.com/sigstore/cosign/internal/pkg/cosign/tsa"
 	"github.com/sigstore/cosign/pkg/cosign/bundle"
 	"github.com/sigstore/cosign/pkg/oci/static"
 	"github.com/sigstore/cosign/pkg/types"
@@ -50,6 +54,9 @@ import (
 	rtypes "github.com/sigstore/rekor/pkg/types"
 	"github.com/sigstore/sigstore/pkg/signature"
 	"github.com/sigstore/sigstore/pkg/signature/options"
+	tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
+	"github.com/sigstore/timestamp-authority/pkg/server"
+	"github.com/spf13/viper"
 	"github.com/stretchr/testify/require"
 	"github.com/transparency-dev/merkle/rfc6962"
 )
@@ -420,6 +427,119 @@ func TestVerifyImageSignatureWithSigVerifierAndRekor(t *testing.T) {
 	}
 }
 
+func TestVerifyImageSignatureWithSigVerifierAndTSA(t *testing.T) {
+	// TODO: Replace with a full TSA mock client, related to https://github.com/sigstore/timestamp-authority/issues/146
+	viper.Set("timestamp-signer", "memory")
+	apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, 10*time.Second, 10*time.Second)
+	server := httptest.NewServer(apiServer.GetHandler())
+	t.Cleanup(server.Close)
+
+	client, err := tsaclient.GetTimestampClient(server.URL)
+	if err != nil {
+		t.Error(err)
+	}
+	sv, _, err := signature.NewDefaultECDSASignerVerifier()
+	if err != nil {
+		t.Fatalf("error generating verifier: %v", err)
+	}
+	payloadSigner := payload.NewSigner(sv)
+	testSigner := tsa.NewSigner(payloadSigner, client)
+
+	chain, err := client.Timestamp.GetTimestampCertChain(nil)
+	if err != nil {
+		t.Fatalf("unexpected error getting timestamp chain: %v", err)
+	}
+
+	file, err := os.CreateTemp(os.TempDir(), "tempfile")
+	if err != nil {
+		t.Fatalf("error creating temp file: %v", err)
+	}
+	defer os.Remove(file.Name())
+
+	_, err = file.WriteString(chain.Payload)
+	if err != nil {
+		t.Fatalf("error writing chain payload to temp file: %v", err)
+	}
+
+	payload := []byte{1, 2, 3, 4}
+	sig, _, err := testSigner.Sign(context.Background(), bytes.NewReader(payload))
+	if err != nil {
+		t.Fatalf("error signing the payload with the tsa client server: %v", err)
+	}
+	if bundleVerified, err := VerifyImageSignature(context.TODO(), sig, v1.Hash{}, &CheckOpts{
+		SigVerifier:      sv,
+		TSAClient:        client,
+		TSACertChainPath: file.Name(),
+		SkipTlogVerify:   true,
+	}); err != nil || !bundleVerified {
+		t.Fatalf("unexpected error while verifying signature, got %v", err)
+	}
+}
+
+func TestVerifyImageSignatureWithSigVerifierAndRekorTSA(t *testing.T) {
+	// TODO: Replace with a full TSA mock client, related to https://github.com/sigstore/timestamp-authority/issues/146
+	viper.Set("timestamp-signer", "memory")
+	apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, 10*time.Second, 10*time.Second)
+	server := httptest.NewServer(apiServer.GetHandler())
+	t.Cleanup(server.Close)
+
+	// Add a fake rekor client - this makes it look like there's a matching
+	// tlog entry for the signature during validation (even though it does not
+	// match the underlying data / key)
+	mClient := new(client.Rekor)
+	mClient.Entries = &mock.EntriesClient{
+		Entries: []*models.LogEntry{&data},
+	}
+
+	client, err := tsaclient.GetTimestampClient(server.URL)
+	if err != nil {
+		t.Error(err)
+	}
+	sv, _, err := signature.NewDefaultECDSASignerVerifier()
+	if err != nil {
+		t.Fatalf("error generating verifier: %v", err)
+	}
+	payloadSigner := payload.NewSigner(sv)
+	tsaSigner := tsa.NewSigner(payloadSigner, client)
+
+	chain, err := client.Timestamp.GetTimestampCertChain(nil)
+	if err != nil {
+		t.Fatalf("unexpected error getting timestamp chain: %v", err)
+	}
+
+	file, err := os.CreateTemp(os.TempDir(), "tempfile")
+	if err != nil {
+		t.Fatalf("error creating temp file: %v", err)
+	}
+	defer os.Remove(file.Name())
+
+	_, err = file.WriteString(chain.Payload)
+	if err != nil {
+		t.Fatalf("error writing chain payload to temp file: %v", err)
+	}
+
+	payload := []byte{1, 2, 3, 4}
+	sig, _, err := tsaSigner.Sign(context.Background(), bytes.NewReader(payload))
+	if err != nil {
+		t.Fatalf("error signing the payload with the rekor and tsa clients: %v", err)
+	}
+	if _, err := VerifyImageSignature(context.TODO(), sig, v1.Hash{}, &CheckOpts{
+		SigVerifier:      sv,
+		TSAClient:        client,
+		TSACertChainPath: file.Name(),
+		RekorClient:      mClient,
+	}); err == nil || !strings.Contains(err.Error(), "verifying inclusion proof") {
+		// TODO(wlynch): This is a weak test, since this is really failing because
+		// there is no inclusion proof for the Rekor entry rather than failing to
+		// validate the Rekor public key itself. At the very least this ensures
+		// that we're hitting tlog validation during signature checking,
+		// but we should look into improving this once there is an in-memory
+		// Rekor client that is capable of performing inclusion proof validation
+		// in unit tests.
+		t.Fatalf("expected error while verifying signature, got %s", err)
+	}
+}
+
 func TestValidateAndUnpackCertSuccess(t *testing.T) {
 	subject := "email@email"
 	oidcIssuer := "https://accounts.google.com"
diff --git a/pkg/oci/internal/signature/layer_test.go b/pkg/oci/internal/signature/layer_test.go
index 929dd975..8721b5c7 100644
--- a/pkg/oci/internal/signature/layer_test.go
+++ b/pkg/oci/internal/signature/layer_test.go
@@ -278,3 +278,178 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
 		})
 	}
 }
+
+func TestSignatureWithTSAAnnotation(t *testing.T) {
+	layer, err := random.Layer(300 /* byteSize */, types.DockerLayer)
+	if err != nil {
+		t.Fatalf("random.Layer() = %v", err)
+	}
+	digest, err := layer.Digest()
+	if err != nil {
+		t.Fatalf("Digest() = %v", err)
+	}
+
+	tests := []struct {
+		name           string
+		l              *sigLayer
+		wantPayloadErr error
+		wantSig        string
+		wantSigErr     error
+		wantCert       bool
+		wantCertErr    error
+		wantChain      int
+		wantChainErr   error
+		wantBundle     *bundle.TSABundle
+		wantBundleErr  error
+	}{{
+		name: "just payload and signature",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey: "blah",
+				},
+			},
+		},
+		wantSig: "blah",
+	}, {
+		name: "with empty other keys",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey:       "blah",
+					certkey:      "",
+					chainkey:     "",
+					TSABundleKey: "",
+				},
+			},
+		},
+		wantSig: "blah",
+	}, {
+		name: "missing signature",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+			},
+		},
+		wantSigErr: fmt.Errorf("signature layer %s is missing %q annotation", digest, sigkey),
+	}, {
+		name: "min plus bad bundle",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey:       "blah",
+					TSABundleKey: `}`,
+				},
+			},
+		},
+		wantSig:       "blah",
+		wantBundleErr: errors.New(`unmarshaling tsa bundle: invalid character '}' looking for beginning of value`),
+	}, {
+		name: "min plus bad cert",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey:  "blah",
+					certkey: `GARBAGE`,
+				},
+			},
+		},
+		wantSig:     "blah",
+		wantCertErr: errors.New(`error during PEM decoding`),
+	}, {
+		name: "min plus bad chain",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey:   "blah",
+					chainkey: `GARBAGE`,
+				},
+			},
+		},
+		wantSig:      "blah",
+		wantChainErr: errors.New(`error during PEM decoding`),
+	}, {
+		name: "min plus TSA bundle",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey: "TSA blah",
+					// This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16.
+					// The Body has been removed for brevity
+					TSABundleKey: `{"SignedRFC3161Timestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="}`,
+				},
+			},
+		},
+		wantSig: "TSA blah",
+		wantBundle: &bundle.TSABundle{
+			SignedRFC3161Timestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
+		},
+	}}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			b, err := test.l.Payload()
+			switch {
+			case (err != nil) != (test.wantPayloadErr != nil):
+				t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr)
+			case (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error():
+				t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr)
+			case err == nil:
+				if got, _, err := v1.SHA256(bytes.NewBuffer(b)); err != nil {
+					t.Errorf("v1.SHA256() = %v", err)
+				} else if want := digest; want != got {
+					t.Errorf("v1.SHA256() = %v, wanted %v", got, want)
+				}
+			}
+
+			switch got, err := test.l.Base64Signature(); {
+			case (err != nil) != (test.wantSigErr != nil):
+				t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr)
+			case (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error():
+				t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr)
+			case got != test.wantSig:
+				t.Errorf("Base64Signature() = %v, wanted %v", got, test.wantSig)
+			}
+
+			switch got, err := test.l.Cert(); {
+			case (err != nil) != (test.wantCertErr != nil):
+				t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr)
+			case (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error():
+				t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr)
+			case (got != nil) != test.wantCert:
+				t.Errorf("Cert() = %v, wanted cert? %v", got, test.wantCert)
+			}
+
+			switch got, err := test.l.Chain(); {
+			case (err != nil) != (test.wantChainErr != nil):
+				t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr)
+			case (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error():
+				t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr)
+			case len(got) != test.wantChain:
+				t.Errorf("Chain() = %v, wanted chain of length %d", got, test.wantChain)
+			}
+
+			switch got, err := test.l.TSABundle(); {
+			case (err != nil) != (test.wantBundleErr != nil):
+				t.Errorf("TSABundle() = %v, wanted %v", err, test.wantBundleErr)
+			case (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error():
+				t.Errorf("TSABundle() = %v, wanted %v", err, test.wantBundleErr)
+			case !cmp.Equal(got, test.wantBundle):
+				t.Errorf("TSABundle() %s", cmp.Diff(got, test.wantBundle))
+			}
+		})
+	}
+}
diff --git a/pkg/oci/mutate/signature_test.go b/pkg/oci/mutate/signature_test.go
index 1e17e9bc..6d7ab5f8 100644
--- a/pkg/oci/mutate/signature_test.go
+++ b/pkg/oci/mutate/signature_test.go
@@ -128,6 +128,21 @@ func assertSignaturesEqual(t *testing.T, wanted, got oci.Signature) {
 		}
 	})
 
+	t.Run("TSA Bundles match", func(t *testing.T) {
+		t.Helper()
+		wantedBundle, err := wanted.TSABundle()
+		if err != nil {
+			t.Fatalf("wanted.Bundle() returned error: %v", err)
+		}
+		gotBundle, err := got.TSABundle()
+		if err != nil {
+			t.Fatalf("got.TSABundle() returned error: %v", err)
+		}
+		if diff := cmp.Diff(wantedBundle, gotBundle); diff != "" {
+			t.Errorf("TSABundle() mismatch (-want +got):\n%s", diff)
+		}
+	})
+
 	t.Run("Certs match", func(t *testing.T) {
 		t.Helper()
 		wantedCert, err := wanted.Cert()
@@ -292,7 +307,7 @@ func TestSignatureWithBundle(t *testing.T) {
 	payload := "this is the TestSignatureWithBundle content!"
 	b64sig := "b64 content2="
 	b := &bundle.RekorBundle{
-		SignedEntryTimestamp: mustBase64Decode(t, "MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
+		SignedEntryTimestamp: mustBase64Decode(t, "MEUCIEDcarEwRYkrxE9ne+kzEVvUhnWaauYzxhUyXOLy1hwAAiEA4VdVCvNRs+D/5o33C2KBy+q2YX3lP4Y7nqRFU+K3hi0="),
 		Payload: bundle.RekorPayload{
 			Body:           "REMOVED",
 			IntegratedTime: 1631646761,
@@ -311,6 +326,23 @@ func TestSignatureWithBundle(t *testing.T) {
 	assertSignaturesEqual(t, expectedSig, newSig)
 }
 
+func TestSignatureWithTSABundle(t *testing.T) {
+	payload := "this is the TestSignatureWithBundle content!"
+	b64sig := "b64 content2="
+	b := &bundle.TSABundle{
+		SignedRFC3161Timestamp: mustBase64Decode(t, "MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
+	}
+	originalSig := mustCreateSignature(t, []byte(payload), b64sig)
+	expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithTSABundle(b))
+
+	newSig, err := Signature(originalSig, WithTSABundle(b))
+	if err != nil {
+		t.Fatalf("Signature(WithTSABundle()) returned error: %v", err)
+	}
+
+	assertSignaturesEqual(t, expectedSig, newSig)
+}
+
 func TestSignatureWithCertChain(t *testing.T) {
 	payload := "this is the TestSignatureWithCertChain content!"
 	b64sig := "b64 content3="
@@ -380,3 +412,36 @@ func TestSignatureWithEverything(t *testing.T) {
 
 	assertSignaturesEqual(t, expectedSig, newSig)
 }
+
+func TestSignatureWithEverythingTSA(t *testing.T) {
+	payload := "this is the TestSignatureWithEverything content!"
+	b64sig := "b64 content5="
+	annotations := map[string]string{
+		"foo":  "bar",
+		"test": "yes",
+	}
+	b := &bundle.TSABundle{
+		SignedRFC3161Timestamp: mustBase64Decode(t, "MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
+	}
+	mediaType := types.MediaType("test/media.type")
+
+	originalSig := mustCreateSignature(t, []byte(payload), b64sig)
+
+	expectedSig := mustCreateSignature(t, []byte(payload), b64sig,
+		static.WithAnnotations(annotations),
+		static.WithTSABundle(b),
+		static.WithCertChain(testCertBytes, testChainBytes),
+		static.WithLayerMediaType(mediaType))
+
+	newSig, err := Signature(originalSig,
+		WithAnnotations(annotations),
+		WithTSABundle(b),
+		WithCertChain(testCertBytes, testChainBytes),
+		WithMediaType(mediaType))
+
+	if err != nil {
+		t.Fatalf("Signature(With...) returned error: %v", err)
+	}
+
+	assertSignaturesEqual(t, expectedSig, newSig)
+}
diff --git a/pkg/oci/signature/layer_test.go b/pkg/oci/signature/layer_test.go
index 929dd975..1801b363 100644
--- a/pkg/oci/signature/layer_test.go
+++ b/pkg/oci/signature/layer_test.go
@@ -278,3 +278,178 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
 		})
 	}
 }
+
+func TestSignatureWithTSAAnnotation(t *testing.T) {
+	layer, err := random.Layer(300 /* byteSize */, types.DockerLayer)
+	if err != nil {
+		t.Fatalf("random.Layer() = %v", err)
+	}
+	digest, err := layer.Digest()
+	if err != nil {
+		t.Fatalf("Digest() = %v", err)
+	}
+
+	tests := []struct {
+		name           string
+		l              *sigLayer
+		wantPayloadErr error
+		wantSig        string
+		wantSigErr     error
+		wantCert       bool
+		wantCertErr    error
+		wantChain      int
+		wantChainErr   error
+		wantBundle     *bundle.TSABundle
+		wantBundleErr  error
+	}{{
+		name: "just payload and signature",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey: "blah",
+				},
+			},
+		},
+		wantSig: "blah",
+	}, {
+		name: "with empty other keys",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey:       "blah",
+					certkey:      "",
+					chainkey:     "",
+					TSABundleKey: "",
+				},
+			},
+		},
+		wantSig: "blah",
+	}, {
+		name: "missing signature",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+			},
+		},
+		wantSigErr: fmt.Errorf("signature layer %s is missing %q annotation", digest, sigkey),
+	}, {
+		name: "min plus bad TSA bundle",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey:       "blah",
+					TSABundleKey: `}`,
+				},
+			},
+		},
+		wantSig:       "blah",
+		wantBundleErr: errors.New(`unmarshaling tsa bundle: invalid character '}' looking for beginning of value`),
+	}, {
+		name: "min plus bad cert",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey:  "blah",
+					certkey: `GARBAGE`,
+				},
+			},
+		},
+		wantSig:     "blah",
+		wantCertErr: errors.New(`error during PEM decoding`),
+	}, {
+		name: "min plus bad chain",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey:   "blah",
+					chainkey: `GARBAGE`,
+				},
+			},
+		},
+		wantSig:      "blah",
+		wantChainErr: errors.New(`error during PEM decoding`),
+	}, {
+		name: "min plus TSA bundle",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey: "tsa blah",
+					// This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16.
+					// The Body has been removed for brevity
+					TSABundleKey: `{"SignedRFC3161Timestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE=","Payload":{"body":"REMOVED","integratedTime":1631646761,"logIndex":693591,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}`,
+				},
+			},
+		},
+		wantSig: "tsa blah",
+		wantBundle: &bundle.TSABundle{
+			SignedRFC3161Timestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
+		},
+	}}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			b, err := test.l.Payload()
+			switch {
+			case (err != nil) != (test.wantPayloadErr != nil):
+				t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr)
+			case (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error():
+				t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr)
+			case err == nil:
+				if got, _, err := v1.SHA256(bytes.NewBuffer(b)); err != nil {
+					t.Errorf("v1.SHA256() = %v", err)
+				} else if want := digest; want != got {
+					t.Errorf("v1.SHA256() = %v, wanted %v", got, want)
+				}
+			}
+
+			switch got, err := test.l.Base64Signature(); {
+			case (err != nil) != (test.wantSigErr != nil):
+				t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr)
+			case (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error():
+				t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr)
+			case got != test.wantSig:
+				t.Errorf("Base64Signature() = %v, wanted %v", got, test.wantSig)
+			}
+
+			switch got, err := test.l.Cert(); {
+			case (err != nil) != (test.wantCertErr != nil):
+				t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr)
+			case (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error():
+				t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr)
+			case (got != nil) != test.wantCert:
+				t.Errorf("Cert() = %v, wanted cert? %v", got, test.wantCert)
+			}
+
+			switch got, err := test.l.Chain(); {
+			case (err != nil) != (test.wantChainErr != nil):
+				t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr)
+			case (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error():
+				t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr)
+			case len(got) != test.wantChain:
+				t.Errorf("Chain() = %v, wanted chain of length %d", got, test.wantChain)
+			}
+
+			switch got, err := test.l.TSABundle(); {
+			case (err != nil) != (test.wantBundleErr != nil):
+				t.Errorf("TSABundle() = %v, wanted %v", err, test.wantBundleErr)
+			case (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error():
+				t.Errorf("TSABundle() = %v, wanted %v", err, test.wantBundleErr)
+			case !cmp.Equal(got, test.wantBundle):
+				t.Errorf("TSABundle() %s", cmp.Diff(got, test.wantBundle))
+			}
+		})
+	}
+}
diff --git a/pkg/oci/static/options_test.go b/pkg/oci/static/options_test.go
index 63600c79..d67a829e 100644
--- a/pkg/oci/static/options_test.go
+++ b/pkg/oci/static/options_test.go
@@ -21,12 +21,13 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-containerregistry/pkg/v1/types"
-	"github.com/sigstore/cosign/pkg/cosign/bundle"
+	cbundle "github.com/sigstore/cosign/pkg/cosign/bundle"
 	ctypes "github.com/sigstore/cosign/pkg/types"
 )
 
 func TestOptions(t *testing.T) {
-	bundle := &bundle.RekorBundle{}
+	bundle := &cbundle.RekorBundle{}
+	tsaBundle := &cbundle.TSABundle{}
 
 	tests := []struct {
 		name string
@@ -91,6 +92,30 @@ func TestOptions(t *testing.T) {
 			},
 			Bundle: bundle,
 		},
+	}, {
+		name: "with TSA bundle",
+		opts: []Option{WithTSABundle(tsaBundle)},
+		want: &options{
+			LayerMediaType:  ctypes.SimpleSigningMediaType,
+			ConfigMediaType: types.OCIConfigJSON,
+			Annotations: map[string]string{
+				TSABundleAnnotationKey: "{\"SignedRFC3161Timestamp\":null}",
+			},
+			TSABundle: tsaBundle,
+		},
+	}, {
+		name: "with TSA and Rekor bundle",
+		opts: []Option{WithTSABundle(tsaBundle), WithBundle(bundle)},
+		want: &options{
+			LayerMediaType:  ctypes.SimpleSigningMediaType,
+			ConfigMediaType: types.OCIConfigJSON,
+			Annotations: map[string]string{
+				TSABundleAnnotationKey: "{\"SignedRFC3161Timestamp\":null}",
+				BundleAnnotationKey:    "{\"SignedEntryTimestamp\":null,\"Payload\":{\"body\":null,\"integratedTime\":0,\"logIndex\":0,\"logID\":\"\"}}",
+			},
+			TSABundle: tsaBundle,
+			Bundle:    bundle,
+		},
 	}}
 
 	for _, test := range tests {
diff --git a/pkg/oci/static/signature_test.go b/pkg/oci/static/signature_test.go
index 1ef5becc..a507334f 100644
--- a/pkg/oci/static/signature_test.go
+++ b/pkg/oci/static/signature_test.go
@@ -447,6 +447,185 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
 	})
 }
 
+func TestNewSignatureCertChainAndRekorTSABundle(t *testing.T) {
+	payload := "this is the other content!"
+	b64sig := "b64 content="
+
+	// This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16
+	var (
+		cert = []byte(`
+-----BEGIN CERTIFICATE-----
+MIICjzCCAhSgAwIBAgITV2heiswW9YldtVEAu98QxDO8TTAKBggqhkjOPQQDAzAq
+MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx
+MDkxNDE5MTI0MFoXDTIxMDkxNDE5MzIzOVowADBZMBMGByqGSM49AgEGCCqGSM49
+AwEHA0IABMF1AWZcfvubslc4ABNnvGbRjm6GWVHxrJ1RRthTHMCE4FpFmiHQBfGt
+6n80DqszGj77Whb35O33+Dal4Y2po+CjggFBMIIBPTAOBgNVHQ8BAf8EBAMCB4Aw
+EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU340G
+3G1ozVNmFC5TBFV0yNuouvowHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG
+0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl
+Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu
+c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j
+cnQwOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj
+ZWFjY291bnQuY29tMAoGCCqGSM49BAMDA2kAMGYCMQDcH9cdkxW6ugsbPHqX9qrM
+wlMaprcwnlktS3+5xuABr5icuqwrB/Fj5doFtS7AnM0CMQD9MjSaUmHFFF7zoLMx
+uThR1Z6JuA21HwxtL3GyJ8UQZcEPOlTBV593HrSAwBhiCoY=
+-----END CERTIFICATE-----
+`)
+		chain = []byte(`
+-----BEGIN CERTIFICATE-----
+MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq
+MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx
+MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu
+ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy
+A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas
+taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm
+MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE
+FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u
+Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx
+Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup
+Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
+-----END CERTIFICATE-----
+`)
+		b = &bundle.TSABundle{
+			SignedRFC3161Timestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
+		}
+		rekorBundle = &bundle.RekorBundle{
+			SignedEntryTimestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
+			Payload: bundle.RekorPayload{
+				Body:           "REMOVED",
+				IntegratedTime: 1631646761,
+				LogIndex:       693591,
+				LogID:          "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d",
+			},
+		}
+	)
+
+	l, err := NewSignature([]byte(payload), b64sig,
+		WithCertChain(cert, chain), WithTSABundle(b))
+	if err != nil {
+		t.Fatalf("NewSignature() = %v", err)
+	}
+
+	t.Run("check tsa signature accessors", func(t *testing.T) {
+		gotPayload, err := l.Payload()
+		if err != nil {
+			t.Fatalf("Payload() = %v", err)
+		}
+		if got, want := string(gotPayload), payload; got != want {
+			t.Errorf("Payload() = %s, wanted %s", got, want)
+		}
+
+		gotSig, err := l.Base64Signature()
+		if err != nil {
+			t.Fatalf("Base64Signature() = %v", err)
+		}
+		if got, want := gotSig, b64sig; got != want {
+			t.Errorf("Base64Signature() = %s, wanted %s", got, want)
+		}
+
+		if got, err := l.TSABundle(); err != nil {
+			t.Fatalf("TSABundle() = %v", err)
+		} else if got != b {
+			t.Errorf("TSABundle() = %#v, wanted %#v", got, b)
+		}
+
+		if got, err := l.Cert(); err != nil {
+			t.Fatalf("Cert() = %v", err)
+		} else if got == nil {
+			t.Error("Cert() = nil, wanted non-nil")
+		}
+
+		if got, err := l.Chain(); err != nil {
+			t.Fatalf("Chain() = %v", err)
+		} else if len(got) != 1 {
+			t.Errorf("len(Chain()) = %d, wanted 1", len(got))
+		}
+	})
+
+	t.Run("check tsa annotations", func(t *testing.T) {
+		want := map[string]string{
+			SignatureAnnotationKey:   b64sig,
+			CertificateAnnotationKey: string(cert),
+			ChainAnnotationKey:       string(chain),
+
+			TSABundleAnnotationKey: `{"SignedRFC3161Timestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="}`,
+		}
+		got, err := l.Annotations()
+		if err != nil {
+			t.Fatalf("Annotations() = %v", err)
+		}
+		if !cmp.Equal(got, want) {
+			t.Errorf("Annotations = %s", cmp.Diff(got, want))
+		}
+	})
+
+	newSig, err := NewSignature([]byte(payload), b64sig,
+		WithCertChain(cert, chain), WithTSABundle(b), WithBundle(rekorBundle))
+	if err != nil {
+		t.Fatalf("NewSignature() = %v", err)
+	}
+
+	t.Run("check signature accessors", func(t *testing.T) {
+		gotPayload, err := newSig.Payload()
+		if err != nil {
+			t.Fatalf("Payload() = %v", err)
+		}
+		if got, want := string(gotPayload), payload; got != want {
+			t.Errorf("Payload() = %s, wanted %s", got, want)
+		}
+
+		gotSig, err := newSig.Base64Signature()
+		if err != nil {
+			t.Fatalf("Base64Signature() = %v", err)
+		}
+		if got, want := gotSig, b64sig; got != want {
+			t.Errorf("Base64Signature() = %s, wanted %s", got, want)
+		}
+
+		if got, err := newSig.Bundle(); err != nil {
+			t.Fatalf("Bundle() = %v", err)
+		} else if got != rekorBundle {
+			t.Errorf("Bundle() = %#v, wanted %#v", got, b)
+		}
+
+		if got, err := newSig.TSABundle(); err != nil {
+			t.Fatalf("TSABundle() = %v", err)
+		} else if got != b {
+			t.Errorf("TSABundle() = %#v, wanted %#v", got, b)
+		}
+
+		if got, err := newSig.Cert(); err != nil {
+			t.Fatalf("Cert() = %v", err)
+		} else if got == nil {
+			t.Error("Cert() = nil, wanted non-nil")
+		}
+
+		if got, err := newSig.Chain(); err != nil {
+			t.Fatalf("Chain() = %v", err)
+		} else if len(got) != 1 {
+			t.Errorf("len(Chain()) = %d, wanted 1", len(got))
+		}
+	})
+
+	t.Run("check rekor and tsa annotations", func(t *testing.T) {
+		want := map[string]string{
+			SignatureAnnotationKey:   b64sig,
+			CertificateAnnotationKey: string(cert),
+			ChainAnnotationKey:       string(chain),
+
+			TSABundleAnnotationKey: `{"SignedRFC3161Timestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="}`,
+			BundleAnnotationKey:    `{"SignedEntryTimestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE=","Payload":{"body":"REMOVED","integratedTime":1631646761,"logIndex":693591,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}`,
+		}
+		got, err := newSig.Annotations()
+		if err != nil {
+			t.Fatalf("Annotations() = %v", err)
+		}
+		if !cmp.Equal(got, want) {
+			t.Errorf("Annotations = %s", cmp.Diff(got, want))
+		}
+	})
+}
+
 func TestNewSignatureBadCertChain(t *testing.T) {
 	payload := "this is the other content!"
 	b64sig := "b64 content="
diff --git a/pkg/policy/attestation_test.go b/pkg/policy/attestation_test.go
index bd2139c4..d7c69b94 100644
--- a/pkg/policy/attestation_test.go
+++ b/pkg/policy/attestation_test.go
@@ -55,6 +55,9 @@ func (fa *failingAttestation) Chain() ([]*x509.Certificate, error) {
 func (fa *failingAttestation) Bundle() (*bundle.RekorBundle, error) {
 	return nil, fmt.Errorf("unimplemented")
 }
+func (fa *failingAttestation) TSABundle() (*bundle.TSABundle, error) {
+	return nil, fmt.Errorf("unimplemented")
+}
 func (fa *failingAttestation) Digest() (v1.Hash, error) {
 	return v1.Hash{}, fmt.Errorf("unimplemented")
 }
diff --git a/test/e2e_test.go b/test/e2e_test.go
index 2b67b037..30987b55 100644
--- a/test/e2e_test.go
+++ b/test/e2e_test.go
@@ -58,6 +58,7 @@ import (
 	"github.com/sigstore/cosign/cmd/cosign/cli/upload"
 	cliverify "github.com/sigstore/cosign/cmd/cosign/cli/verify"
 	"github.com/sigstore/cosign/pkg/cosign"
+	"github.com/sigstore/cosign/pkg/cosign/bundle"
 	"github.com/sigstore/cosign/pkg/cosign/env"
 	"github.com/sigstore/cosign/pkg/cosign/kubernetes"
 	cremote "github.com/sigstore/cosign/pkg/cosign/remote"
@@ -66,6 +67,9 @@ import (
 	"github.com/sigstore/cosign/pkg/sget"
 	sigs "github.com/sigstore/cosign/pkg/signature"
 	"github.com/sigstore/sigstore/pkg/signature/payload"
+	tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
+	"github.com/sigstore/timestamp-authority/pkg/server"
+	"github.com/spf13/viper"
 )
 
 const (
@@ -94,6 +98,23 @@ var verify = func(keyRef, imageRef string, checkClaims bool, annotations map[str
 	return cmd.Exec(context.Background(), args)
 }
 
+var verifyTSA = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment, tsaServerURL, tsaCertChain string, skipTlogVerify bool) error {
+	cmd := cliverify.VerifyCommand{
+		KeyRef:           keyRef,
+		CheckClaims:      checkClaims,
+		Annotations:      sigs.AnnotationsMap{Annotations: annotations},
+		Attachment:       attachment,
+		HashAlgorithm:    crypto.SHA256,
+		TSAServerURL:     tsaServerURL,
+		TSACertChainPath: tsaCertChain,
+		SkipTlogVerify:   skipTlogVerify,
+	}
+
+	args := []string{imageRef}
+
+	return cmd.Exec(context.Background(), args)
+}
+
 // Used to verify local images stored on disk
 var verifyLocal = func(keyRef, path string, checkClaims bool, annotations map[string]interface{}, attachment string) error {
 	cmd := cliverify.VerifyCommand{
@@ -507,6 +528,119 @@ func TestRekorBundle(t *testing.T) {
 	must(verify(pubKeyPath, imgName, true, nil, ""), t)
 }
 
+func TestTSABundle(t *testing.T) {
+	// turn on the tlog
+	defer setenv(t, env.VariableExperimental.String(), "1")()
+	// TODO: Replace with a full TSA mock client, related to https://github.com/sigstore/timestamp-authority/issues/146
+	viper.Set("timestamp-signer", "memory")
+	apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, 10*time.Second, 10*time.Second)
+	server := httptest.NewServer(apiServer.GetHandler())
+	t.Cleanup(server.Close)
+
+	client, err := tsaclient.GetTimestampClient(server.URL)
+	if err != nil {
+		t.Error(err)
+	}
+
+	chain, err := client.Timestamp.GetTimestampCertChain(nil)
+	if err != nil {
+		t.Fatalf("unexpected error getting timestamp chain: %v", err)
+	}
+
+	file, err := os.CreateTemp(os.TempDir(), "tempfile")
+	if err != nil {
+		t.Fatalf("error creating temp file: %v", err)
+	}
+	defer os.Remove(file.Name())
+	_, err = file.WriteString(chain.Payload)
+	if err != nil {
+		t.Fatalf("error writing chain payload to temp file: %v", err)
+	}
+
+	repo, stop := reg(t)
+	defer stop()
+	td := t.TempDir()
+
+	imgName := path.Join(repo, "cosign-e2e")
+
+	_, _, cleanup := mkimage(t, imgName)
+	defer cleanup()
+
+	_, privKeyPath, pubKeyPath := keypair(t, td)
+
+	ko := options.KeyOpts{
+		KeyRef:       privKeyPath,
+		PassFunc:     passFunc,
+		TSAServerURL: server.URL,
+	}
+	so := options.SignOptions{
+		Upload:     true,
+		TlogUpload: false,
+	}
+
+	// Sign the image
+	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
+	// Make sure verify works against the TSA server
+	must(verifyTSA(pubKeyPath, imgName, true, nil, "", server.URL, file.Name(), true), t)
+}
+
+func TestRekorBundleAndTSABundle(t *testing.T) {
+	// turn on the tlog
+	defer setenv(t, env.VariableExperimental.String(), "1")()
+	// TODO: Replace with a full TSA mock client, related to https://github.com/sigstore/timestamp-authority/issues/146
+	viper.Set("timestamp-signer", "memory")
+	apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, 10*time.Second, 10*time.Second)
+	server := httptest.NewServer(apiServer.GetHandler())
+	t.Cleanup(server.Close)
+
+	client, err := tsaclient.GetTimestampClient(server.URL)
+	if err != nil {
+		t.Error(err)
+	}
+
+	chain, err := client.Timestamp.GetTimestampCertChain(nil)
+	if err != nil {
+		t.Fatalf("unexpected error getting timestamp chain: %v", err)
+	}
+
+	file, err := os.CreateTemp(os.TempDir(), "tempfile")
+	if err != nil {
+		t.Fatalf("error creating temp file: %v", err)
+	}
+	defer os.Remove(file.Name())
+	_, err = file.WriteString(chain.Payload)
+	if err != nil {
+		t.Fatalf("error writing chain payload to temp file: %v", err)
+	}
+
+	repo, stop := reg(t)
+	defer stop()
+	td := t.TempDir()
+
+	imgName := path.Join(repo, "cosign-e2e")
+
+	_, _, cleanup := mkimage(t, imgName)
+	defer cleanup()
+
+	_, privKeyPath, pubKeyPath := keypair(t, td)
+
+	ko := options.KeyOpts{
+		KeyRef:       privKeyPath,
+		PassFunc:     passFunc,
+		TSAServerURL: server.URL,
+		RekorURL:     rekorURL,
+	}
+	so := options.SignOptions{
+		Upload:     true,
+		TlogUpload: true,
+	}
+
+	// Sign the image
+	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
+	// Make sure verify works against the Rekor and TSA clients
+	must(verifyTSA(pubKeyPath, imgName, true, nil, "", server.URL, file.Name(), false), t)
+}
+
 func TestDuplicateSign(t *testing.T) {
 	repo, stop := reg(t)
 	defer stop()
@@ -692,7 +826,7 @@ func TestSignBlob(t *testing.T) {
 		KeyRef:   privKeyPath1,
 		PassFunc: passFunc,
 	}
-	sig, err := sign.SignBlobCmd(ro, ko, options.RegistryOptions{}, bp, true, "", "")
+	sig, err := sign.SignBlobCmd(ro, ko, options.RegistryOptions{}, bp, true, "", "", false)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -737,7 +871,7 @@ func TestSignBlobBundle(t *testing.T) {
 		BundlePath: bundlePath,
 		RekorURL:   rekorURL,
 	}
-	if _, err := sign.SignBlobCmd(ro, ko, options.RegistryOptions{}, bp, true, "", ""); err != nil {
+	if _, err := sign.SignBlobCmd(ro, ko, options.RegistryOptions{}, bp, true, "", "", false); err != nil {
 		t.Fatal(err)
 	}
 	// Now verify should work
@@ -745,7 +879,7 @@ func TestSignBlobBundle(t *testing.T) {
 
 	// Now we turn on the tlog and sign again
 	defer setenv(t, env.VariableExperimental.String(), "1")()
-	if _, err := sign.SignBlobCmd(ro, ko, options.RegistryOptions{}, bp, true, "", ""); err != nil {
+	if _, err := sign.SignBlobCmd(ro, ko, options.RegistryOptions{}, bp, true, "", "", false); err != nil {
 		t.Fatal(err)
 	}
 
@@ -1566,3 +1700,70 @@ func TestAttestBlobSignVerify(t *testing.T) {
 	blobVerifyAttestationCmd.PredicateType = predicateType
 	mustErr(blobVerifyAttestationCmd.Exec(ctx, anotherBlob), t)
 }
+
+func TestOffline(t *testing.T) {
+	regName, stop := reg(t)
+	defer stop()
+	td := t.TempDir()
+
+	img1 := path.Join(regName, "cosign-e2e")
+
+	imgRef, _, cleanup := mkimage(t, img1)
+	defer cleanup()
+
+	_, privKeyPath, pubKeyPath := keypair(t, td)
+
+	ctx := context.Background()
+
+	// Sign image1 and store the entry in rekor
+	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL}
+	so := options.SignOptions{
+		Upload:           true,
+		TlogUpload:       true,
+		SkipConfirmation: true,
+	}
+	must(sign.SignCmd(ro, ko, so, []string{img1}), t)
+	// verify image1 online and offline
+	must(verify(pubKeyPath, img1, true, nil, ""), t)
+	verifyCmd := &cliverify.VerifyCommand{
+		KeyRef:      pubKeyPath,
+		Offline:     true,
+		CheckClaims: true,
+	}
+	must(verifyCmd.Exec(ctx, []string{img1}), t)
+
+	// Get signatures
+	si, err := ociremote.SignedEntity(imgRef)
+	must(err, t)
+	sigs, err := si.Signatures()
+	must(err, t)
+	gottenSigs, err := sigs.Get()
+	must(err, t)
+
+	fakeBundle := &bundle.RekorBundle{
+		SignedEntryTimestamp: []byte(""),
+		Payload: bundle.RekorPayload{
+			Body: "",
+		},
+	}
+	newSig, err := mutate.Signature(gottenSigs[0], mutate.WithBundle(fakeBundle))
+	must(err, t)
+
+	sigsTag, err := ociremote.SignatureTag(imgRef)
+	if err := remote.Delete(sigsTag); err != nil {
+		t.Fatal(err)
+	}
+
+	si, err = ociremote.SignedEntity(imgRef)
+	must(err, t)
+	newImage, err := mutate.AttachSignatureToEntity(si, newSig)
+	must(err, t)
+
+	mustErr(verify(pubKeyPath, img1, true, nil, ""), t)
+	if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil {
+		t.Fatal(err)
+	}
+
+	// Confirm offline verification fails
+	mustErr(verifyCmd.Exec(ctx, []string{img1}), t)
+}
diff --git a/test/sign_blob_test.sh b/test/sign_blob_test.sh
index 621ad72d..f7389488 100755
--- a/test/sign_blob_test.sh
+++ b/test/sign_blob_test.sh
@@ -81,7 +81,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" "$BLOB"
+$COSIGN_CLI verify-blob --signature "$SIG_FILE" --cert "$CERT_FILE" --certificate-chain "$CERT_FILE" --insecure-ignore-sct "$BLOB"
 
 echo "Verifying using cosign ENV variables ..."
-COSIGN_SIGNATURE="$SIG_FILE" COSIGN_CERTIFICATE_CHAIN="$CERT_FILE" COSIGN_CERTIFICATE="$CERT_FILE" $COSIGN_CLI verify-blob "$BLOB"
+COSIGN_SIGNATURE="$SIG_FILE" COSIGN_CERTIFICATE_CHAIN="$CERT_FILE" COSIGN_CERTIFICATE="$CERT_FILE" $COSIGN_CLI verify-blob --insecure-ignore-sct "$BLOB"