From 25bcbadbd885c8eeb5698c4213729e94691d9799 Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Wed, 30 Nov 2022 21:43:12 +0000 Subject: [PATCH 1/8] Updates to Timestamp signing and verification * Switch to using the raw signature rather than base64 signature for OCI and blob signing * For blob signing, write only the timestamp to disk, not the LocalSignedPayload (since that's already written with the bundle) * For blob verification, expect only a timestamp in the file. If you don't pass a bundle, you'll need to also pass the signature by flag * Some nits from the previous PR Signed-off-by: Hayden Blauzvern --- cmd/cosign/cli/options/signblob.go | 6 +- cmd/cosign/cli/options/verify.go | 4 +- cmd/cosign/cli/policy_init.go | 3 +- cmd/cosign/cli/sign/sign_blob.go | 18 ++--- cmd/cosign/cli/verify/verify_blob.go | 39 +++------ cmd/cosign/cli/verify/verify_blob_test.go | 98 +++++++---------------- doc/cosign_sign-blob.md | 46 +++++------ doc/cosign_verify-blob.md | 2 +- internal/pkg/cosign/tsa/signer.go | 16 +++- pkg/cosign/bundle/tsa.go | 7 +- pkg/cosign/fetch.go | 7 +- pkg/cosign/verify.go | 13 ++- specs/SIGNATURE_SPEC.md | 5 ++ 13 files changed, 109 insertions(+), 155 deletions(-) diff --git a/cmd/cosign/cli/options/signblob.go b/cmd/cosign/cli/options/signblob.go index 59f96607457..8cc23e23a35 100644 --- a/cmd/cosign/cli/options/signblob.go +++ b/cmd/cosign/cli/options/signblob.go @@ -79,7 +79,7 @@ func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", "url to the Timestamp RFC3161 server, default none") - cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp-bundle", "", - "write everything required to verify the blob to a FILE") - _ = cmd.Flags().SetAnnotation("rfc3161-timestamp-bundle", cobra.BashCompFilenameExt, []string{}) + cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", + "write the RFC3161 timestamp to a file") + _ = cmd.Flags().SetAnnotation("rfc3161-timestamp", cobra.BashCompFilenameExt, []string{}) } diff --git a/cmd/cosign/cli/options/verify.go b/cmd/cosign/cli/options/verify.go index 005bac9afb1..2b1413b43b7 100644 --- a/cmd/cosign/cli/options/verify.go +++ b/cmd/cosign/cli/options/verify.go @@ -164,8 +164,8 @@ func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.BundlePath, "bundle", "", "path to bundle FILE") - cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp-bundle", "", - "path to timestamp bundle FILE") + cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", + "path to rfc3161 timestamp FILE") } // VerifyDockerfileOptions is the top level wrapper for the `dockerfile verify` command. diff --git a/cmd/cosign/cli/policy_init.go b/cmd/cosign/cli/policy_init.go index 15fb5bdde9e..cf3ea4ee754 100644 --- a/cmd/cosign/cli/policy_init.go +++ b/cmd/cosign/cli/policy_init.go @@ -273,8 +273,7 @@ func signPolicy() *cobra.Command { return fmt.Errorf("failed to create TSA client: %w", err) } // Here we get the response from the timestamped authority server - _, err = tsa.GetTimestampedSignature(signed.Signed, clientTSA) - if err != nil { + if _, err := tsa.GetTimestampedSignature(signed.Signed, clientTSA); err != nil { return err } } diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index ebe80351334..e5e6460cc4e 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -72,19 +72,20 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string signedPayload := cosign.LocalSignedPayload{} + var rfc3161Timestamp *cbundle.RFC3161Timestamp if ko.TSAServerURL != "" { clientTSA, err := tsaclient.GetTimestampClient(ko.TSAServerURL) if err != nil { return nil, fmt.Errorf("failed to create TSA client: %w", err) } - b64Sig := []byte(base64.StdEncoding.EncodeToString(sig)) - respBytes, err := tsa.GetTimestampedSignature(b64Sig, clientTSA) + respBytes, err := tsa.GetTimestampedSignature(sig, clientTSA) if err != nil { return nil, err } - signedPayload.RFC3161Timestamp = cbundle.TimestampToRFC3161Timestamp(respBytes) + rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(respBytes) + // TODO: Consider uploading RFC3161 TS to Rekor } if ShouldUploadToTlog(ctx, ko, nil, tlogUpload) { rekorBytes, err = sv.Bytes(ctx) @@ -103,18 +104,15 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string signedPayload.Bundle = cbundle.EntryToBundle(entry) } - // if bundle is specified, just do that and ignore the rest - if ko.RFC3161TimestampPath != "" { - signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(sig) - - contents, err := json.Marshal(signedPayload) + if ko.RFC3161TimestampPath != "" && rfc3161Timestamp != nil { + ts, err := json.Marshal(rfc3161Timestamp) if err != nil { return nil, err } - if err := os.WriteFile(ko.RFC3161TimestampPath, contents, 0600); err != nil { + if err := os.WriteFile(ko.RFC3161TimestampPath, ts, 0600); err != nil { return nil, fmt.Errorf("create rfc3161 timestamp file: %w", err) } - fmt.Printf("RF3161 timestamp bundle wrote in the file %s\n", ko.RFC3161TimestampPath) + fmt.Printf("RFC3161 timestamp bundle written to file %s\n", ko.RFC3161TimestampPath) } // if bundle is specified, just do that and ignore the rest diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index d108681a6d6..d97417602c9 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -33,6 +33,7 @@ import ( "github.com/sigstore/cosign/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/pkg/blob" "github.com/sigstore/cosign/pkg/cosign" + "github.com/sigstore/cosign/pkg/cosign/bundle" "github.com/sigstore/cosign/pkg/cosign/pivkey" "github.com/sigstore/cosign/pkg/cosign/pkcs11key" "github.com/sigstore/cosign/pkg/oci/static" @@ -73,8 +74,8 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { opts := make([]static.Option, 0) // Require a certificate/key OR a local bundle file that has the cert. - if options.NOf(c.KeyRef, c.CertRef, c.Sk, c.BundlePath, c.RFC3161TimestampPath) == 0 { - return fmt.Errorf("please provide a cert to verify against via --certificate or a bundle via --bundle or --rfc3161-timestamp-bundle") + if options.NOf(c.KeyRef, c.CertRef, c.Sk, c.BundlePath) == 0 { + return fmt.Errorf("please provide a cert to verify against via --certificate or a bundle via --bundle") } // Key, sk, and cert are mutually exclusive. @@ -82,7 +83,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { return &options.KeyParseError{} } - sig, err := base64signature(c.SigRef, c.BundlePath, c.RFC3161TimestampPath) + sig, err := base64signature(c.SigRef, c.BundlePath) if err != nil { return err } @@ -208,29 +209,15 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { opts = append(opts, static.WithBundle(b.Bundle)) } if c.RFC3161TimestampPath != "" { - b, err := cosign.FetchLocalSignedPayloadFromPath(c.RFC3161TimestampPath) + var rfc3161Timestamp bundle.RFC3161Timestamp + ts, err := blob.LoadFileOrURL(c.RFC3161TimestampPath) if err != nil { return err } - // Note: RFC3161 timestamp does not set the certificate. - // We have to condition on this because sign-blob may not output the signing - // key to the bundle when there is no tlog upload. - if b.Cert != "" { - // b.Cert can either be a certificate or public key - certBytes := []byte(b.Cert) - if isb64(certBytes) { - certBytes, _ = base64.StdEncoding.DecodeString(b.Cert) - } - cert, err = loadCertFromPEM(certBytes) - if err != nil { - // check if cert is actually a public key - co.SigVerifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256) - if err != nil { - return fmt.Errorf("loading verifier from rfc3161 timestamp bundle: %w", err) - } - } + if err := json.Unmarshal(ts, &rfc3161Timestamp); err != nil { + return err } - opts = append(opts, static.WithRFC3161Timestamp(b.RFC3161Timestamp)) + opts = append(opts, static.WithRFC3161Timestamp(&rfc3161Timestamp)) } // Set an SCT if provided via the CLI. if c.SCTRef != "" { @@ -306,7 +293,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { } // base64signature returns the base64 encoded signature -func base64signature(sigRef string, bundlePath, rfc3161TimestampPath string) (string, error) { +func base64signature(sigRef, bundlePath string) (string, error) { var targetSig []byte var err error switch { @@ -325,12 +312,6 @@ func base64signature(sigRef string, bundlePath, rfc3161TimestampPath string) (st return "", err } targetSig = []byte(b.Base64Signature) - case rfc3161TimestampPath != "": - b, err := cosign.FetchLocalSignedPayloadFromPath(rfc3161TimestampPath) - if err != nil { - return "", err - } - targetSig = []byte(b.Base64Signature) default: return "", fmt.Errorf("missing flag '--signature'") } diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go index aec30046c7c..c4c3c77aa56 100644 --- a/cmd/cosign/cli/verify/verify_blob_test.go +++ b/cmd/cosign/cli/verify/verify_blob_test.go @@ -87,7 +87,7 @@ func TestSignaturesRef(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { - gotSig, err := base64signature(test.sigRef, "", "") + gotSig, err := base64signature(test.sigRef, "") if test.shouldErr && err != nil { return } @@ -119,34 +119,7 @@ func TestSignaturesBundle(t *testing.T) { t.Fatal(err) } - gotSig, err := base64signature("", fp, "") - if err != nil { - t.Fatal(err) - } - if gotSig != b64sig { - t.Fatalf("unexpected signature, expected: %s got: %s", b64sig, gotSig) - } -} - -func TestSignaturesRFC3161TimestampBundle(t *testing.T) { - td := t.TempDir() - fp := filepath.Join(td, "file") - - b64sig := "YT09" - - // save as a LocalSignedPayload to the file - lsp := cosign.LocalSignedPayload{ - Base64Signature: b64sig, - } - contents, err := json.Marshal(lsp) - if err != nil { - t.Fatal(err) - } - if err := os.WriteFile(fp, contents, 0644); err != nil { - t.Fatal(err) - } - - gotSig, err := base64signature("", "", fp) + gotSig, err := base64signature("", fp) if err != nil { t.Fatal(err) } @@ -1015,12 +988,6 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { // Create blob blob := "someblob" - // Sign blob with private key - sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) - if err != nil { - t.Fatal(err) - } - // 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) @@ -1032,8 +999,7 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { t.Error(err) } - payloadSigner := payload.NewSigner(keyless.rekorSigner) - + payloadSigner := payload.NewSigner(signer) tsaSigner := tsa.NewSigner(payloadSigner, client) var sigTSA oci.Signature sigTSA, _, err = tsaSigner.Sign(context.Background(), bytes.NewReader([]byte(blob))) @@ -1045,6 +1011,7 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { if err != nil { t.Fatalf("unexpected error getting rfc3161 timestamp bundle: %v", err) } + tsPath := writeTimestampFile(t, keyless.td, rfc3161Timestamp, "rfc3161TS.json") chain, err := client.Timestamp.GetTimestampCertChain(nil) if err != nil { @@ -1058,8 +1025,16 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { defer os.Remove(tsaCertChainPath) // Create bundle + b64Sig, err := sigTSA.Base64Signature() + if err != nil { + t.Fatal(err) + } + sig, err := base64.StdEncoding.DecodeString(b64Sig) + if err != nil { + t.Fatal(err) + } entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) - b := createRFC3161TimestampAndOrRekorBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry, rfc3161Timestamp.SignedRFC3161Timestamp) + b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") @@ -1070,12 +1045,12 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { CertEmail: identity, CertChain: os.Getenv("SIGSTORE_ROOT_FILE"), SigRef: "", // Sig is fetched from bundle - KeyOpts: options.KeyOpts{BundlePath: bundlePath, TSACertChainPath: tsaCertChainPath}, + KeyOpts: options.KeyOpts{BundlePath: bundlePath, TSACertChainPath: tsaCertChainPath, RFC3161TimestampPath: tsPath}, IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err != nil { - t.Fatalf("expected success specifying the intermediates, got %v", err) + t.Fatalf("expected success verifying with timestamp, got %v", err) } }) t.Run("Explicit Fulcio chain with bundle in non-experimental mode", func(t *testing.T) { @@ -1384,37 +1359,6 @@ func createBundle(_ *testing.T, sig []byte, certPem []byte, logID string, integr return b } -func createRFC3161TimestampAndOrRekorBundle(_ *testing.T, sig []byte, certPem []byte, logID string, integratedTime int64, rekorEntry string, rfc3161timestamp []byte) *cosign.LocalSignedPayload { - // Create bundle with: - // * Blob signature - // * Signing certificate - b := &cosign.LocalSignedPayload{ - Base64Signature: base64.StdEncoding.EncodeToString(sig), - Cert: string(certPem), - } - - if rekorEntry != "" { - // * Bundle with a payload and signature over the payload - b.Bundle = &bundle.RekorBundle{ - SignedEntryTimestamp: []byte{}, - Payload: bundle.RekorPayload{ - LogID: logID, - IntegratedTime: integratedTime, - LogIndex: 1, - Body: rekorEntry, - }, - } - } - - if rfc3161timestamp != nil { - b.RFC3161Timestamp = &bundle.RFC3161Timestamp{ - SignedRFC3161Timestamp: rfc3161timestamp, - } - } - - return b -} - func createEntry(ctx context.Context, kind, apiVersion string, blobBytes, certBytes, sigBytes []byte) (types.EntryImpl, error) { props := types.ArtifactProperties{ PublicKeyBytes: [][]byte{certBytes}, @@ -1475,3 +1419,15 @@ func writeBlobFile(t *testing.T, td string, blob string, name string) string { } return blobPath } + +func writeTimestampFile(t *testing.T, td string, ts *bundle.RFC3161Timestamp, name string) string { + jsonBundle, err := json.Marshal(ts) + if err != nil { + t.Fatal(err) + } + path := filepath.Join(td, name) + if err := os.WriteFile(path, jsonBundle, 0644); err != nil { + t.Fatal(err) + } + return path +} diff --git a/doc/cosign_sign-blob.md b/doc/cosign_sign-blob.md index 45b07447069..4c67fb1853c 100644 --- a/doc/cosign_sign-blob.md +++ b/doc/cosign_sign-blob.md @@ -33,29 +33,29 @@ cosign sign-blob [flags] ### Options ``` - --b64 whether to base64 encode the output (default true) - --bundle string write everything required to verify the blob to a FILE - --fulcio-url string [EXPERIMENTAL] address of sigstore PKI server (default "https://fulcio.sigstore.dev") - -h, --help help for sign-blob - --identity-token string [EXPERIMENTAL] identity token to use for certificate from fulcio - --insecure-skip-verify [EXPERIMENTAL] skip verifying fulcio published to the SCT (this should only be used for testing). - --key string path to the private key file, KMS URI or Kubernetes Secret - --oidc-client-id string [EXPERIMENTAL] OIDC client ID for application (default "sigstore") - --oidc-client-secret-file string [EXPERIMENTAL] Path to file containing OIDC client secret for application - --oidc-disable-ambient-providers [EXPERIMENTAL] Disable ambient OIDC providers. When true, ambient credentials will not be read - --oidc-issuer string [EXPERIMENTAL] OIDC provider to be used to issue ID token (default "https://oauth2.sigstore.dev/auth") - --oidc-provider string [EXPERIMENTAL] Specify the provider to get the OIDC token from (Optional). If unset, all options will be tried. Options include: [spiffe, google, github, filesystem] - --oidc-redirect-url string [EXPERIMENTAL] OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'. - --output string write the signature to FILE - --output-certificate string write the certificate to FILE - --output-signature string write the signature to FILE - --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") - --rfc3161-timestamp-bundle string write everything required to verify the blob to a FILE - --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 (default true) - -y, --yes skip confirmation prompts for non-destructive operations + --b64 whether to base64 encode the output (default true) + --bundle string write everything required to verify the blob to a FILE + --fulcio-url string [EXPERIMENTAL] address of sigstore PKI server (default "https://fulcio.sigstore.dev") + -h, --help help for sign-blob + --identity-token string [EXPERIMENTAL] identity token to use for certificate from fulcio + --insecure-skip-verify [EXPERIMENTAL] skip verifying fulcio published to the SCT (this should only be used for testing). + --key string path to the private key file, KMS URI or Kubernetes Secret + --oidc-client-id string [EXPERIMENTAL] OIDC client ID for application (default "sigstore") + --oidc-client-secret-file string [EXPERIMENTAL] Path to file containing OIDC client secret for application + --oidc-disable-ambient-providers [EXPERIMENTAL] Disable ambient OIDC providers. When true, ambient credentials will not be read + --oidc-issuer string [EXPERIMENTAL] OIDC provider to be used to issue ID token (default "https://oauth2.sigstore.dev/auth") + --oidc-provider string [EXPERIMENTAL] Specify the provider to get the OIDC token from (Optional). If unset, all options will be tried. Options include: [spiffe, google, github, filesystem] + --oidc-redirect-url string [EXPERIMENTAL] OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'. + --output string write the signature to FILE + --output-certificate string write the certificate to FILE + --output-signature string write the signature to FILE + --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --rfc3161-timestamp string write the RFC3161 timestamp to a file + --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 (default true) + -y, --yes skip confirmation prompts for non-destructive operations ``` ### Options inherited from parent commands diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md index 43771a0110b..74dd8d24207 100644 --- a/doc/cosign_verify-blob.md +++ b/doc/cosign_verify-blob.md @@ -79,7 +79,7 @@ cosign verify-blob [flags] --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") - --rfc3161-timestamp-bundle string path to timestamp bundle FILE + --rfc3161-timestamp string path to rfc3161 timestamp FILE --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 diff --git a/internal/pkg/cosign/tsa/signer.go b/internal/pkg/cosign/tsa/signer.go index c4afc6a7194..74dda4f2756 100644 --- a/internal/pkg/cosign/tsa/signer.go +++ b/internal/pkg/cosign/tsa/signer.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "crypto" + "encoding/base64" "fmt" "io" "os" @@ -36,12 +37,13 @@ import ( ts "github.com/sigstore/timestamp-authority/pkg/generated/client/timestamp" ) +// GetTimestampedSignature queries a timestamp authority to fetch an RFC3161 timestamp. sigBytes is an +// opaque blob, but is typically a signature over an artifact. 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)) @@ -58,7 +60,7 @@ func GetTimestampedSignature(sigBytes []byte, tsaClient *tsaclient.TimestampAuth return nil, err } - fmt.Fprintln(os.Stderr, "Timestamp fetched with time:", ts.Time) + fmt.Fprintln(os.Stderr, "Timestamp fetched with time: ", ts.Time) return respBytes.Bytes(), nil } @@ -84,8 +86,14 @@ func (rs *signerWrapper) Sign(ctx context.Context, payload io.Reader) (oci.Signa return nil, nil, err } - // Here we get the response from the timestamped authority server - responseBytes, err := GetTimestampedSignature([]byte(b64Sig), rs.tsaClient) + // create timestamp over raw bytes of signature + rawSig, err := base64.StdEncoding.DecodeString(b64Sig) + if err != nil { + return nil, nil, err + } + + // fetch rfc3161 timestamp from timestamp authority + responseBytes, err := GetTimestampedSignature(rawSig, rs.tsaClient) if err != nil { return nil, nil, err } diff --git a/pkg/cosign/bundle/tsa.go b/pkg/cosign/bundle/tsa.go index e11864b7a75..bbb846759b2 100644 --- a/pkg/cosign/bundle/tsa.go +++ b/pkg/cosign/bundle/tsa.go @@ -16,9 +16,10 @@ package bundle // RFC3161Timestamp holds metadata about timestamp RFC3161 verification data. type RFC3161Timestamp 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 contains a DER encoded TimeStampResponse. + // See https://www.rfc-editor.org/rfc/rfc3161.html#section-2.4.2 + // Clients MUST verify the hashed message in the message imprint, + // typically using the artifact signature. SignedRFC3161Timestamp []byte } diff --git a/pkg/cosign/fetch.go b/pkg/cosign/fetch.go index b42a949e146..70ffb2f3a67 100644 --- a/pkg/cosign/fetch.go +++ b/pkg/cosign/fetch.go @@ -39,10 +39,9 @@ type SignedPayload struct { } type LocalSignedPayload struct { - Base64Signature string `json:"base64Signature"` - Cert string `json:"cert,omitempty"` - Bundle *bundle.RekorBundle `json:"rekorBundle,omitempty"` - RFC3161Timestamp *bundle.RFC3161Timestamp `json:"rfc3161Timestamp,omitempty"` + Base64Signature string `json:"base64Signature"` + Cert string `json:"cert,omitempty"` + Bundle *bundle.RekorBundle `json:"rekorBundle,omitempty"` } type Signatures struct { diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 99962afd9da..56cc1332a53 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -1004,17 +1004,24 @@ func VerifyRFC3161Timestamp(ctx context.Context, sig oci.Signature, tsaCerts *x5 return false, err } - verifiedBytes := []byte(b64Sig) + var tsBytes []byte if len(b64Sig) == 0 { // For attestations, the Base64Signature is not set, therefore we rely on the signed payload signedPayload, err := sig.Payload() if err != nil { return false, fmt.Errorf("reading the payload: %w", err) } - verifiedBytes = signedPayload + tsBytes = signedPayload + } else { + // create timestamp over raw bytes of signature + rawSig, err := base64.StdEncoding.DecodeString(b64Sig) + if err != nil { + return false, err + } + tsBytes = rawSig } - err = tsaverification.VerifyTimestampResponse(bundle.SignedRFC3161Timestamp, bytes.NewReader(verifiedBytes), tsaCerts) + err = tsaverification.VerifyTimestampResponse(bundle.SignedRFC3161Timestamp, bytes.NewReader(tsBytes), tsaCerts) if err != nil { return false, fmt.Errorf("unable to verify TimestampResponse: %w", err) } diff --git a/specs/SIGNATURE_SPEC.md b/specs/SIGNATURE_SPEC.md index e2ad2aba3e6..c0590674f49 100644 --- a/specs/SIGNATURE_SPEC.md +++ b/specs/SIGNATURE_SPEC.md @@ -129,6 +129,11 @@ Gyp4apdU7AXEwysEQIb034aPrTlpmxh90SnTZFs2DHOvCjCPPAmoWfuQUwPhSPRb For instructions on using the `bundle` for verification, see [USAGE.md](../USAGE.md#verify-a-signature-was-added-to-the-transparency-log). +* `rfc3161timestamp` string + + This OPTIONAL property contains a JSON formatted `RFC3161Timestamp` bundle containing the timestamp response from a + timestamp authority. + ## Storage `cosign` image signatures are stored in an OCI registry and are designed to make use of the existing specifications. From cb9c9cbd5817394d88451d780d9384342be44868 Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Wed, 30 Nov 2022 22:54:48 +0000 Subject: [PATCH 2/8] Add testing in verify, update e2e test Signed-off-by: Hayden Blauzvern --- cmd/cosign/cli/verify/verify_blob_test.go | 4 + pkg/cosign/verify.go | 32 +++---- pkg/cosign/verify_test.go | 111 ++++++++++++++++++++++ test/e2e_test.go | 4 +- 4 files changed, 134 insertions(+), 17 deletions(-) diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go index c4c3c77aa56..f623eb88b38 100644 --- a/cmd/cosign/cli/verify/verify_blob_test.go +++ b/cmd/cosign/cli/verify/verify_blob_test.go @@ -523,6 +523,10 @@ func TestVerifyBlob(t *testing.T) { expiredLeafPem, true)}, shouldErr: true, }, + // TODO: Add tests for TSA: + // * With or without bundle + // * Mismatched signature + // * Unexpired and expired certificate } for _, tt := range tts { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 56cc1332a53..024dcbce0f5 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -666,7 +666,7 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, } } if co.TSACerts != nil { - bundleVerified, err = VerifyRFC3161Timestamp(ctx, sig, co.TSACerts) + err = VerifyRFC3161Timestamp(sig, co.TSACerts) if err != nil { return false, fmt.Errorf("unable to verify RFC3161 timestamp bundle: %w", err) } @@ -986,22 +986,22 @@ func VerifyBundle(ctx context.Context, sig oci.Signature, co *CheckOpts, rekorCl return true, nil } -func VerifyRFC3161Timestamp(ctx context.Context, sig oci.Signature, tsaCerts *x509.CertPool) (bool, error) { - bundle, err := sig.RFC3161Timestamp() +func VerifyRFC3161Timestamp(sig oci.Signature, tsaCerts *x509.CertPool) error { + ts, err := sig.RFC3161Timestamp() if err != nil { - return false, err - } else if bundle == nil { - return false, nil + return err + } else if ts == nil { + return nil } b64Sig, err := sig.Base64Signature() if err != nil { - return false, fmt.Errorf("reading base64signature: %w", err) + return fmt.Errorf("reading base64signature: %w", err) } cert, err := sig.Cert() if err != nil { - return false, err + return err } var tsBytes []byte @@ -1009,36 +1009,36 @@ func VerifyRFC3161Timestamp(ctx context.Context, sig oci.Signature, tsaCerts *x5 // For attestations, the Base64Signature is not set, therefore we rely on the signed payload signedPayload, err := sig.Payload() if err != nil { - return false, fmt.Errorf("reading the payload: %w", err) + return fmt.Errorf("reading the payload: %w", err) } tsBytes = signedPayload } else { // create timestamp over raw bytes of signature rawSig, err := base64.StdEncoding.DecodeString(b64Sig) if err != nil { - return false, err + return err } tsBytes = rawSig } - err = tsaverification.VerifyTimestampResponse(bundle.SignedRFC3161Timestamp, bytes.NewReader(tsBytes), tsaCerts) + err = tsaverification.VerifyTimestampResponse(ts.SignedRFC3161Timestamp, bytes.NewReader(tsBytes), tsaCerts) if err != nil { - return false, fmt.Errorf("unable to verify TimestampResponse: %w", err) + return fmt.Errorf("unable to verify TimestampResponse: %w", err) } if cert != nil { - ts, err := timestamp.ParseResponse(bundle.SignedRFC3161Timestamp) + ts, err := timestamp.ParseResponse(ts.SignedRFC3161Timestamp) if err != nil { - return false, fmt.Errorf("error parsing response into timestamp: %w", err) + return 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 fmt.Errorf("checking expiry on certificate: %w", err) } } - return true, nil + return nil } // compare bundle signature to the signature we are verifying diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index f61ca64cbf3..78e2252badf 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "crypto" + "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" @@ -1287,3 +1288,113 @@ func Test_getSubjectAltnernativeNames(t *testing.T) { t.Fatalf("unexpected URL SAN value") } } + +func TestVerifyRFC3161Timestamp(t *testing.T) { + // generate signed artifact + rootCert, rootKey, _ := test.GenerateRootCa() + leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) + pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) + pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) + payload := []byte{1, 2, 3, 4} + h := sha256.Sum256(payload) + signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) + + // TODO: Replace with a TSA mock client, blocked by 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.Fatal(err) + } + + tsBytes, err := tsa.GetTimestampedSignature(signature, client) + if err != nil { + t.Fatalf("unexpected error creating timestamp: %v", err) + } + rfc3161TS := bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} + chain, err := client.Timestamp.GetTimestampCertChain(nil) + if err != nil { + t.Fatalf("unexpected error getting timestamp chain: %v", err) + } + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM([]byte(chain.Payload)) { + t.Fatalf("error creating trust root pool") + } + + ociSig, _ := static.NewSignature(payload, + base64.StdEncoding.EncodeToString(signature), + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + + // success, signing over signature + err = VerifyRFC3161Timestamp(ociSig, pool) + if err != nil { + t.Fatalf("unexpected error verifying timestamp with signature: %v", err) + } + + // success, signing over payload + tsBytes, err = tsa.GetTimestampedSignature(payload, client) + if err != nil { + t.Fatalf("unexpected error creating timestamp: %v", err) + } + rfc3161TS = bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} + ociSig, _ = static.NewSignature(payload, + "", /*signature*/ + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + err = VerifyRFC3161Timestamp(ociSig, pool) + if err != nil { + t.Fatalf("unexpected error verifying timestamp with payload: %v", err) + } + + // failure with non-base64 encoded signature + ociSig, _ = static.NewSignature(payload, + string(signature), + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + err = VerifyRFC3161Timestamp(ociSig, pool) + if err == nil || !strings.Contains(err.Error(), "base64 data") { + t.Fatalf("expected error verifying timestamp with raw signature, got: %v", err) + } + + // failure with mismatched signature + tsBytes, err = tsa.GetTimestampedSignature(signature, client) + if err != nil { + t.Fatalf("unexpected error creating timestamp: %v", err) + } + rfc3161TS = bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} + // regenerate signature + signature, _ = privKey.Sign(rand.Reader, h[:], crypto.SHA256) + ociSig, _ = static.NewSignature(payload, + base64.StdEncoding.EncodeToString(signature), + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + err = VerifyRFC3161Timestamp(ociSig, pool) + if err == nil || !strings.Contains(err.Error(), "hashed messages don't match") { + t.Fatalf("expected error verifying mismatched signatures, got: %v", err) + } + + // failure with old certificate + leafPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + // generate old certificate + leafCert, _ = test.GenerateLeafCertWithExpiration("subject", "oidc-issuer", time.Now().AddDate(-1, 0, 0), leafPriv, rootCert, rootKey) + pemLeaf = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) + tsBytes, err = tsa.GetTimestampedSignature(signature, client) + if err != nil { + t.Fatalf("unexpected error creating timestamp: %v", err) + } + rfc3161TS = bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} + ociSig, _ = static.NewSignature(payload, + base64.StdEncoding.EncodeToString(signature), + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + err = VerifyRFC3161Timestamp(ociSig, pool) + if err == nil || !strings.Contains(err.Error(), "checking expiry on certificate") { + t.Fatalf("expected error verifying old certificate, got: %v", err) + } +} diff --git a/test/e2e_test.go b/test/e2e_test.go index a5b99b5a5d6..2af82be6641 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -997,8 +997,8 @@ func TestSignBlobRFC3161TimestampBundle(t *testing.T) { os.RemoveAll(td1) }) bp := filepath.Join(td1, blob) - tsPath := filepath.Join(td1, "rfc3161TimestampBundle.sig") bundlePath := filepath.Join(td1, "bundle.sig") + tsPath := filepath.Join(td1, "rfc3161Timestamp.json") if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { t.Fatal(err) @@ -1030,6 +1030,7 @@ func TestSignBlobRFC3161TimestampBundle(t *testing.T) { ko1 := options.KeyOpts{ KeyRef: pubKeyPath1, + BundlePath: bundlePath, RFC3161TimestampPath: tsPath, TSACertChainPath: file.Name(), BundlePath: bundlePath, @@ -1045,6 +1046,7 @@ func TestSignBlobRFC3161TimestampBundle(t *testing.T) { ko := options.KeyOpts{ KeyRef: privKeyPath1, PassFunc: passFunc, + BundlePath: bundlePath, RFC3161TimestampPath: tsPath, TSAServerURL: server.URL, RekorURL: rekorURL, From 56fb38c887143d6124b6b40aeed6a3f0060ae7ec Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Wed, 30 Nov 2022 23:40:57 +0000 Subject: [PATCH 3/8] Fix test Signed-off-by: Hayden Blauzvern --- pkg/cosign/verify_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index 78e2252badf..95e28f75d22 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -466,7 +466,7 @@ func TestVerifyImageSignatureWithSigVerifierAndTSA(t *testing.T) { SigVerifier: sv, TSACerts: tsaCertPool, SkipTlogVerify: true, - }); err != nil || !bundleVerified { + }); err != nil || bundleVerified { // bundle is not verified since there's no Rekor bundle t.Fatalf("unexpected error while verifying signature, got %v", err) } } From 5f8e8bda5255d8702a512dc2785d784156e96a95 Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Fri, 2 Dec 2022 20:06:09 +0000 Subject: [PATCH 4/8] Add missing setting of SkipTlogVerify Signed-off-by: Hayden Blauzvern --- cmd/cosign/cli/verify.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index c5eb7a287a5..235cf5d9478 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -301,6 +301,7 @@ The blob may be specified as a path to a file or - for stdin.`, IgnoreSCT: o.CertVerify.IgnoreSCT, SCTRef: o.CertVerify.SCT, Offline: o.CommonVerifyOptions.Offline, + SkipTlogVerify: o.CommonVerifyOptions.SkipTlogVerify, } if err := verifyBlobCmd.Exec(cmd.Context(), args[0]); err != nil { return fmt.Errorf("verifying blob %s: %w", args, err) From 5acd7f613ffc7d2c4ac773b3e04d1cfdf43fd26b Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Tue, 6 Dec 2022 21:50:46 +0000 Subject: [PATCH 5/8] Address comments Signed-off-by: Hayden Blauzvern --- cmd/cosign/cli/options/verify.go | 2 +- cmd/cosign/cli/sign/sign_blob.go | 27 ++++++++++++++++----------- specs/SIGNATURE_SPEC.md | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/cmd/cosign/cli/options/verify.go b/cmd/cosign/cli/options/verify.go index 2b1413b43b7..5e4ebb11a71 100644 --- a/cmd/cosign/cli/options/verify.go +++ b/cmd/cosign/cli/options/verify.go @@ -165,7 +165,7 @@ func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) { "path to bundle FILE") cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", - "path to rfc3161 timestamp FILE") + "path to RFC3161 timestamp FILE") } // VerifyDockerfileOptions is the top level wrapper for the `dockerfile verify` command. diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index e5e6460cc4e..5eb618a53ac 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -74,6 +74,10 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string var rfc3161Timestamp *cbundle.RFC3161Timestamp if ko.TSAServerURL != "" { + if ko.RFC3161TimestampPath == "" { + return nil, fmt.Errorf("timestamp output path must be set") + } + clientTSA, err := tsaclient.GetTimestampClient(ko.TSAServerURL) if err != nil { return nil, fmt.Errorf("failed to create TSA client: %w", err) @@ -86,6 +90,18 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(respBytes) // TODO: Consider uploading RFC3161 TS to Rekor + + if rfc3161Timestamp == nil { + return nil, fmt.Errorf("rfc3161 timestamp is nil") + } + ts, err := json.Marshal(rfc3161Timestamp) + if err != nil { + return nil, err + } + if err := os.WriteFile(ko.RFC3161TimestampPath, ts, 0600); err != nil { + return nil, fmt.Errorf("create rfc3161 timestamp file: %w", err) + } + fmt.Printf("RFC3161 timestamp bundle written to file %s\n", ko.RFC3161TimestampPath) } if ShouldUploadToTlog(ctx, ko, nil, tlogUpload) { rekorBytes, err = sv.Bytes(ctx) @@ -104,17 +120,6 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string signedPayload.Bundle = cbundle.EntryToBundle(entry) } - if ko.RFC3161TimestampPath != "" && rfc3161Timestamp != nil { - ts, err := json.Marshal(rfc3161Timestamp) - if err != nil { - return nil, err - } - if err := os.WriteFile(ko.RFC3161TimestampPath, ts, 0600); err != nil { - return nil, fmt.Errorf("create rfc3161 timestamp file: %w", err) - } - fmt.Printf("RFC3161 timestamp bundle written to file %s\n", ko.RFC3161TimestampPath) - } - // if bundle is specified, just do that and ignore the rest if ko.BundlePath != "" { signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(sig) diff --git a/specs/SIGNATURE_SPEC.md b/specs/SIGNATURE_SPEC.md index c0590674f49..20cf5460555 100644 --- a/specs/SIGNATURE_SPEC.md +++ b/specs/SIGNATURE_SPEC.md @@ -131,7 +131,7 @@ For instructions on using the `bundle` for verification, see [USAGE.md](../USAGE * `rfc3161timestamp` string - This OPTIONAL property contains a JSON formatted `RFC3161Timestamp` bundle containing the timestamp response from a + This OPTIONAL property contains a JSON formatted `RFC3161Timestamp` containing the timestamp response from a timestamp authority. ## Storage From 6fb95c906e15c5cbf8f22c9498bc2adf605360a6 Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Tue, 6 Dec 2022 21:55:34 +0000 Subject: [PATCH 6/8] Fix docs Signed-off-by: Hayden Blauzvern --- doc/cosign_verify-blob.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md index 74dd8d24207..29124ca48d4 100644 --- a/doc/cosign_verify-blob.md +++ b/doc/cosign_verify-blob.md @@ -79,7 +79,7 @@ cosign verify-blob [flags] --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") - --rfc3161-timestamp string path to rfc3161 timestamp FILE + --rfc3161-timestamp string path to RFC3161 timestamp FILE --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 From ee0f511aa5c7adad7e47679b7d4105e664458e14 Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Tue, 6 Dec 2022 23:04:31 +0000 Subject: [PATCH 7/8] Address nits Signed-off-by: Hayden Blauzvern --- cmd/cosign/cli/sign/sign_blob.go | 4 ++-- cmd/cosign/cli/verify/verify_blob.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index 5eb618a53ac..8f77c6b0487 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -99,9 +99,9 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string return nil, err } if err := os.WriteFile(ko.RFC3161TimestampPath, ts, 0600); err != nil { - return nil, fmt.Errorf("create rfc3161 timestamp file: %w", err) + return nil, fmt.Errorf("create RFC3161 timestamp file: %w", err) } - fmt.Printf("RFC3161 timestamp bundle written to file %s\n", ko.RFC3161TimestampPath) + fmt.Fprintf(os.Stderr, "RFC3161 timestamp written to file %s\n", ko.RFC3161TimestampPath) } if ShouldUploadToTlog(ctx, ko, nil, tlogUpload) { rekorBytes, err = sv.Bytes(ctx) diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index d97417602c9..08e3789d282 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -75,7 +75,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { // Require a certificate/key OR a local bundle file that has the cert. if options.NOf(c.KeyRef, c.CertRef, c.Sk, c.BundlePath) == 0 { - return fmt.Errorf("please provide a cert to verify against via --certificate or a bundle via --bundle") + return fmt.Errorf("provide a key with --key or --sk, a certificate to verify against with --certificate, or a bundle with --bundle") } // Key, sk, and cert are mutually exclusive. From ae1470856a7d764f3d5f881bcd61c94612ff1870 Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Tue, 6 Dec 2022 23:20:27 +0000 Subject: [PATCH 8/8] Fix e2e test Signed-off-by: Hayden Blauzvern --- test/e2e_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/e2e_test.go b/test/e2e_test.go index 2af82be6641..cc7c54ffb26 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -1033,7 +1033,6 @@ func TestSignBlobRFC3161TimestampBundle(t *testing.T) { BundlePath: bundlePath, RFC3161TimestampPath: tsPath, TSACertChainPath: file.Name(), - BundlePath: bundlePath, } // Verify should fail on a bad input verifyBlobCmd := cliverify.VerifyBlobCmd{ @@ -1050,7 +1049,6 @@ func TestSignBlobRFC3161TimestampBundle(t *testing.T) { RFC3161TimestampPath: tsPath, TSAServerURL: server.URL, RekorURL: rekorURL, - BundlePath: bundlePath, } if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false); err != nil { t.Fatal(err)