From b7f31db6111390a2622889ddf01ce612b5680a77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 10 Mar 2023 23:24:34 +0100 Subject: [PATCH 1/5] Pass all of options.SignOptions to signDigest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... to replace SEVEN individual parameters. This will make it easier to add more parameters in the future. Should not change behavior. Signed-off-by: Miloslav Trmač --- cmd/cosign/cli/sign/sign.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index ab7e90535ec..fc36441f67d 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -158,7 +158,6 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO ErrDone = mutate.ErrSkipChildren } regOpts := signOpts.Registry - regExpOpts := signOpts.RegistryExperimental opts, err := regOpts.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) @@ -183,7 +182,7 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO if err != nil { return fmt.Errorf("accessing image: %w", err) } - err = signDigest(ctx, digest, staticPayload, ko, regOpts, regExpOpts, annotations, signOpts.Upload, signOpts.OutputSignature, signOpts.OutputCertificate, signOpts.Recursive, signOpts.TlogUpload, dd, sv, se) + err = signDigest(ctx, digest, staticPayload, ko, signOpts, annotations, dd, sv, se) if err != nil { return fmt.Errorf("signing digest: %w", err) } @@ -202,7 +201,7 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO return fmt.Errorf("computing digest: %w", err) } digest := ref.Context().Digest(d.String()) - err = signDigest(ctx, digest, staticPayload, ko, regOpts, regExpOpts, annotations, signOpts.Upload, signOpts.OutputSignature, signOpts.OutputCertificate, signOpts.Recursive, signOpts.TlogUpload, dd, sv, se) + err = signDigest(ctx, digest, staticPayload, ko, signOpts, annotations, dd, sv, se) if err != nil { return fmt.Errorf("signing digest: %w", err) } @@ -215,8 +214,8 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO return nil } -func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko options.KeyOpts, - regOpts options.RegistryOptions, regExpOpts options.RegistryExperimentalOptions, annotations map[string]interface{}, upload bool, outputSignature, outputCertificate string, recursive bool, tlogUpload bool, +func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko options.KeyOpts, signOpts options.SignOptions, + annotations map[string]interface{}, dd mutate.DupeDetector, sv *SignerVerifier, se oci.SignedEntity) error { var err error // The payload can be passed to skip generation. @@ -239,7 +238,7 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti if ko.TSAServerURL != "" { s = tsa.NewSigner(s, client.NewTSAClient(ko.TSAServerURL)) } - shouldUpload, err := ShouldUploadToTlog(ctx, ko, digest, tlogUpload) + shouldUpload, err := ShouldUploadToTlog(ctx, ko, digest, signOpts.TlogUpload) if err != nil { return fmt.Errorf("should upload to tlog: %w", err) } @@ -261,9 +260,10 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti return err } + outputSignature := signOpts.OutputSignature if outputSignature != "" { // Add digest to suffix to differentiate each image during recursive signing - if recursive { + if signOpts.Recursive { outputSignature = fmt.Sprintf("%s-%s", outputSignature, strings.Replace(digest.DigestStr(), ":", "-", 1)) } if err := os.WriteFile(outputSignature, []byte(b64sig), 0600); err != nil { @@ -271,20 +271,20 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti } } - if outputCertificate != "" { + if signOpts.OutputCertificate != "" { rekorBytes, err := sv.Bytes(ctx) if err != nil { return fmt.Errorf("create certificate file: %w", err) } - if err := os.WriteFile(outputCertificate, rekorBytes, 0600); err != nil { + if err := os.WriteFile(signOpts.OutputCertificate, rekorBytes, 0600); err != nil { return fmt.Errorf("create certificate file: %w", err) } // TODO: maybe accept a --b64 flag as well? - ui.Infof(ctx, "Certificate wrote in the file %s", outputCertificate) + ui.Infof(ctx, "Certificate wrote in the file %s", signOpts.OutputCertificate) } - if !upload { + if !signOpts.Upload { return nil } @@ -295,7 +295,7 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti } // Publish the signatures associated with this entity - walkOpts, err := regOpts.ClientOpts(ctx) + walkOpts, err := signOpts.Registry.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) } @@ -309,7 +309,7 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti } // Publish the signatures associated with this entity (using OCI 1.1+ behavior) - if regExpOpts.RegistryReferrersMode == options.RegistryReferrersModeOCI11 { + if signOpts.RegistryExperimental.RegistryReferrersMode == options.RegistryReferrersModeOCI11 { return ociremote.WriteSignaturesExperimentalOCI(digest, newSE, walkOpts...) } From 7f099b4747e44159b1aa18a928b143a3616bce8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 10 Mar 2023 23:26:00 +0100 Subject: [PATCH 2/5] Add --output-payload to (cosign sign) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --output-signature makes no sense without --output-payload; otherwise users would need to somehow deterministically regenerate the payload to be able to verify the signature. Signed-off-by: Miloslav Trmač --- cmd/cosign/cli/options/sign.go | 4 ++++ cmd/cosign/cli/sign/sign.go | 10 ++++++++++ doc/cosign_sign.md | 1 + 3 files changed, 15 insertions(+) diff --git a/cmd/cosign/cli/options/sign.go b/cmd/cosign/cli/options/sign.go index 81016c7587c..d2cda047c51 100644 --- a/cmd/cosign/cli/options/sign.go +++ b/cmd/cosign/cli/options/sign.go @@ -27,6 +27,7 @@ type SignOptions struct { Upload bool Output string // deprecated: TODO remove when the output flag is fully deprecated OutputSignature string // TODO: this should be the root output file arg. + OutputPayload string OutputCertificate string PayloadPath string Recursive bool @@ -78,6 +79,9 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "", "write the signature to FILE") _ = cmd.Flags().SetAnnotation("output-signature", cobra.BashCompFilenameExt, []string{}) + cmd.Flags().StringVar(&o.OutputPayload, "output-payload", "", + "write the signed payload to FILE") + _ = cmd.Flags().SetAnnotation("output-payload", cobra.BashCompFilenameExt, []string{}) cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "", "write the certificate to FILE") diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index fc36441f67d..5f742acda5f 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -270,6 +270,16 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti return fmt.Errorf("create signature file: %w", err) } } + outputPayload := signOpts.OutputPayload + if outputPayload != "" { + // Add digest to suffix to differentiate each image during recursive signing + if signOpts.Recursive { + outputPayload = fmt.Sprintf("%s-%s", outputPayload, strings.Replace(digest.DigestStr(), ":", "-", 1)) + } + if err := os.WriteFile(outputPayload, payload, 0600); err != nil { + return fmt.Errorf("create payload file: %w", err) + } + } if signOpts.OutputCertificate != "" { rekorBytes, err := sv.Bytes(ctx) diff --git a/doc/cosign_sign.md b/doc/cosign_sign.md index f1a17d7cab4..b8aa40710e5 100644 --- a/doc/cosign_sign.md +++ b/doc/cosign_sign.md @@ -87,6 +87,7 @@ cosign sign [flags] --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-certificate string write the certificate to FILE + --output-payload string write the signed payload to FILE --output-signature string write the signature to FILE --payload string path to a payload file to use rather than generating one -r, --recursive if a multi-arch image is specified, additionally sign each discrete image From 8f243cc60ac9e95438d0a96f9d5ebfd6629b1241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Mon, 13 Mar 2023 20:36:14 +0100 Subject: [PATCH 3/5] Add pkg/cosign.ObsoletePayload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- pkg/cosign/obsolete.go | 37 +++++++++++++++++++++++++++++++++ pkg/cosign/obsolete_test.go | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 pkg/cosign/obsolete.go create mode 100644 pkg/cosign/obsolete_test.go diff --git a/pkg/cosign/obsolete.go b/pkg/cosign/obsolete.go new file mode 100644 index 00000000000..817f05bead0 --- /dev/null +++ b/pkg/cosign/obsolete.go @@ -0,0 +1,37 @@ +// +// 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 cosign + +import ( + "context" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/sigstore/cosign/v2/internal/ui" + "github.com/sigstore/sigstore/pkg/signature/payload" +) + +// ObsoletePayload returns the implied payload that some commands expect to match +// the signature if no payload is provided by the user. +// DO NOT ADD ANY NEW CALLERS OF THIS. +func ObsoletePayload(ctx context.Context, digestedImage name.Digest) ([]byte, error) { + blob, err := (&payload.Cosign{Image: digestedImage}).MarshalJSON() + if err != nil { + return nil, err + } + ui.Warnf(ctx, "using obsolete implied signature payload data (with digested reference %s); specify it explicitly with --payload instead", + digestedImage.Name()) + return blob, nil +} diff --git a/pkg/cosign/obsolete_test.go b/pkg/cosign/obsolete_test.go new file mode 100644 index 00000000000..25d3cffa1ad --- /dev/null +++ b/pkg/cosign/obsolete_test.go @@ -0,0 +1,41 @@ +// +// 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 cosign + +import ( + "context" + "testing" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/sigstore/cosign/v2/internal/ui" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestObsoletePayload(t *testing.T) { + // This looks like a smoke test, but the property of generating _exactly_ the same string as previous versions is + // essential. + digestedImg, err := name.NewDigest("docker.io/namespace/image@sha256:4aa3054270f7a70b4528f2064ee90961788e1e1518703592ae4463de3b889dec") + require.NoError(t, err) + var res []byte + stderr := ui.RunWithTestCtx(func(ctx context.Context, write ui.WriteFunc) { + r, err := ObsoletePayload(ctx, digestedImg) + require.NoError(t, err) + res = r + }) + assert.Contains(t, stderr, "obsolete implied signature payload") + assert.Equal(t, []byte(`{"critical":{"identity":{"docker-reference":"index.docker.io/namespace/image"},"image":{"docker-manifest-digest":"sha256:4aa3054270f7a70b4528f2064ee90961788e1e1518703592ae4463de3b889dec"},"type":"cosign container image signature"},"optional":null}`), res) +} From cc5c8e2d72944f7706c01aba5fb72fe24433942a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Tue, 28 Feb 2023 18:39:58 +0100 Subject: [PATCH 4/5] Warn if the payload is not provided for (cosign signature attach) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signature signs the payload; it makes no sense for the user to provide the signature but not the payload - it would effectively force cosign to generate a byte-for-byte identical (and, currently, undesirable) payload forever. Still, for compatibility, continue to accept such invocations, but trigger a warning. Signed-off-by: Miloslav Trmač --- EXAMPLES.md | 4 ++-- USAGE.md | 6 +++--- cmd/cosign/cli/attach/sig.go | 4 ++-- cmd/cosign/cli/options/attach.go | 2 +- doc/cosign_attach_signature.md | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 1c5ad9b5daa..ea2a1c1f349 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -8,7 +8,7 @@ Use `cosign` to generate the payload, sign it with `gcloud kms`, then use `cosig $ cosign generate us-central1-docker.pkg.dev/dlorenc-vmtest2/test/taskrun > payload.json $ gcloud kms asymmetric-sign --digest-algorithm=sha256 --input-file=payload.json --signature-file=gcpkms.sig --key=foo --keyring=foo --version=1 --location=us-central # We have to base64 encode the signature -$ cat gcpkms.sig | base64 | cosign attach signature --signature - us-central1-docker.pkg.dev/dlorenc-vmtest2/test/taskrun +$ cat gcpkms.sig | base64 | cosign attach signature --payload payload.json --signature - us-central1-docker.pkg.dev/dlorenc-vmtest2/test/taskrun ``` Now (on another machine) download the public key, payload, signatures and verify it! @@ -71,7 +71,7 @@ $ aws kms sign --key-id $AWS_CMK_ID \ --output text \ --query Signature > payload.sig -$ cosign attach signature docker.io/davivcgarcia/hello-world:latest --signature $(< payload.sig) +$ cosign attach signature docker.io/davivcgarcia/hello-world:latest --signature $(< payload.sig) --payload payload.json ``` Now (on another machine) use the `cosign` to download signature bundle, extract payload and signature value, and verify it with `aws kms`! diff --git a/USAGE.md b/USAGE.md index e58d1295161..7a593849cef 100644 --- a/USAGE.md +++ b/USAGE.md @@ -130,18 +130,18 @@ $ cosign generate $IMAGE_DIGEST | openssl... ## Upload a generated signature -The signature is passed via the `--signature` flag. +The signature is passed via the `--signature` and `--payload` flags. It can be a file: ```shell -$ cosign attach signature --signature file.sig $IMAGE_DIGEST +$ cosign attach signature --signature file.sig --payload payload.json $IMAGE_DIGEST Pushing signature to: dlorenc/demo:sha256-87ef60f558bad79beea6425a3b28989f01dd417164150ab3baab98dcbf04def8.sig ``` or, `-` for stdin for chaining from other commands: ```shell -$ cosign generate $IMAGE_DIGEST | openssl... | cosign attach signature --signature - $IMAGE_DIGEST +$ … | openssl... | cosign attach signature --signature - --payload ... $IMAGE_DIGEST Pushing signature to: dlorenc/demo:sha256-87ef60f558bad79beea6425a3b28989f01dd417164150ab3baab98dcbf04def.sig ``` diff --git a/cmd/cosign/cli/attach/sig.go b/cmd/cosign/cli/attach/sig.go index aa0d2f5acbf..f1ad36cdd4f 100644 --- a/cmd/cosign/cli/attach/sig.go +++ b/cmd/cosign/cli/attach/sig.go @@ -25,10 +25,10 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/oci/mutate" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci/static" - sigPayload "github.com/sigstore/sigstore/pkg/signature/payload" ) func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef, payloadRef, certRef, certChainRef, imageRef string) error { @@ -58,7 +58,7 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef, var payload []byte if payloadRef == "" { - payload, err = (&sigPayload.Cosign{Image: digest}).MarshalJSON() + payload, err = cosign.ObsoletePayload(ctx, digest) } else { payload, err = os.ReadFile(filepath.Clean(payloadRef)) } diff --git a/cmd/cosign/cli/options/attach.go b/cmd/cosign/cli/options/attach.go index 8eaa4cf281f..5c02663516c 100644 --- a/cmd/cosign/cli/options/attach.go +++ b/cmd/cosign/cli/options/attach.go @@ -44,7 +44,7 @@ func (o *AttachSignatureOptions) AddFlags(cmd *cobra.Command) { "path to the signature, or {-} for stdin") cmd.Flags().StringVar(&o.Payload, "payload", "", - "path to the payload covered by the signature (if using another format)") + "path to the payload covered by the signature") cmd.Flags().StringVar(&o.Cert, "certificate", "", "path to the X.509 certificate in PEM format to include in the OCI Signature") diff --git a/doc/cosign_attach_signature.md b/doc/cosign_attach_signature.md index bbe86366672..861a142e578 100644 --- a/doc/cosign_attach_signature.md +++ b/doc/cosign_attach_signature.md @@ -22,7 +22,7 @@ cosign attach signature [flags] --certificate-chain string path to a list of CA X.509 certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Included in the OCI Signature -h, --help help for signature --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). - --payload string path to the payload covered by the signature (if using another format) + --payload string path to the payload covered by the signature --signature string path to the signature, or {-} for stdin ``` From 76171396b4316d8eb40855b9ab51e8b8dcdb938c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Tue, 28 Feb 2023 20:01:18 +0100 Subject: [PATCH 5/5] Allow the payload to be provided with a provided signature in verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... and warn if that's not the case. The signature signs the payload; it makes no sense for the user to provide the signature but not the payload - it would effectively force cosign to generate a byte-for-byte identical (and, currently, undesirable) payload forever. Still, for compatibility, continue to accept such invocations, but trigger a warning. Signed-off-by: Miloslav Trmač --- cmd/cosign/cli/options/verify.go | 4 ++++ cmd/cosign/cli/verify.go | 1 + cmd/cosign/cli/verify/verify.go | 2 ++ doc/cosign_dockerfile_verify.md | 1 + doc/cosign_manifest_verify.md | 1 + doc/cosign_verify.md | 1 + pkg/cosign/verify.go | 38 ++++++++++++++++++++------------ test/e2e_test.ps1 | 4 ++-- test/e2e_test_secrets_kms.sh | 4 ++-- 9 files changed, 38 insertions(+), 18 deletions(-) diff --git a/cmd/cosign/cli/options/verify.go b/cmd/cosign/cli/options/verify.go index f2bcb05d3f4..98348a41219 100644 --- a/cmd/cosign/cli/options/verify.go +++ b/cmd/cosign/cli/options/verify.go @@ -45,6 +45,7 @@ type VerifyOptions struct { Attachment string Output string SignatureRef string + PayloadRef string LocalImage bool CommonVerifyOptions CommonVerifyOptions @@ -85,6 +86,9 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.SignatureRef, "signature", "", "signature content or path or remote URL") + cmd.Flags().StringVar(&o.PayloadRef, "payload", "", + "payload path or remote URL") + cmd.Flags().BoolVar(&o.LocalImage, "local-image", false, "whether the specified image is a path to an image saved locally via 'cosign save'") } diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index e8a8c793892..df9820df6a8 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -117,6 +117,7 @@ against the transparency log.`, Annotations: annotations, HashAlgorithm: hashAlgorithm, SignatureRef: o.SignatureRef, + PayloadRef: o.PayloadRef, LocalImage: o.LocalImage, Offline: o.CommonVerifyOptions.Offline, TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath, diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index b2b981a13cc..edfa6906a87 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -71,6 +71,7 @@ type VerifyCommand struct { Attachment string Annotations sigs.AnnotationsMap SignatureRef string + PayloadRef string HashAlgorithm crypto.Hash LocalImage bool NameOptions []name.Option @@ -120,6 +121,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { CertGithubWorkflowRef: c.CertGithubWorkflowRef, IgnoreSCT: c.IgnoreSCT, SignatureRef: c.SignatureRef, + PayloadRef: c.PayloadRef, Identities: identities, Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, diff --git a/doc/cosign_dockerfile_verify.md b/doc/cosign_dockerfile_verify.md index 6ad78a9480c..a5a1d6c8fd7 100644 --- a/doc/cosign_dockerfile_verify.md +++ b/doc/cosign_dockerfile_verify.md @@ -75,6 +75,7 @@ cosign dockerfile verify [flags] --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") + --payload string payload path or remote URL --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 diff --git a/doc/cosign_manifest_verify.md b/doc/cosign_manifest_verify.md index 5ac17bbf0c2..fc9565b3d8c 100644 --- a/doc/cosign_manifest_verify.md +++ b/doc/cosign_manifest_verify.md @@ -69,6 +69,7 @@ cosign manifest verify [flags] --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") + --payload string payload path or remote URL --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 diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md index 13e77b0616c..1ea9d603f19 100644 --- a/doc/cosign_verify.md +++ b/doc/cosign_verify.md @@ -88,6 +88,7 @@ cosign verify [flags] --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") + --payload string payload path or remote URL --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 diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index c26d5f7f2c3..2d12f74830c 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -57,7 +57,6 @@ import ( "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" "github.com/sigstore/sigstore/pkg/signature/options" - sigPayload "github.com/sigstore/sigstore/pkg/signature/payload" tsaverification "github.com/sigstore/timestamp-authority/pkg/verification" ) @@ -121,8 +120,10 @@ type CheckOpts struct { // It is a map from log id to LogIDMetadata. It is a map from LogID to crypto.PublicKey. LogID is derived from the PublicKey (see RFC 6962 S3.2). CTLogPubKeys *TrustedTransparencyLogPubKeys - // SignatureRef is the reference to the signature file + // SignatureRef is the reference to the signature file. PayloadRef should always be specified as well (though it’s possible for a _some_ signatures to be verified without it, with a warning). SignatureRef string + // PayloadRef is a reference to the payload file. Applicable only if SignatureRef is set. + PayloadRef string // Identities is an array of Identity (Subject, Issuer) matchers that have // to be met for the signature to ve valid. @@ -509,7 +510,7 @@ func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co return nil, false, err } } else { - sigs, err = loadSignatureFromFile(sigRef, signedImgRef, co) + sigs, err = loadSignatureFromFile(ctx, sigRef, signedImgRef, co) if err != nil { return nil, false, err } @@ -782,7 +783,7 @@ func VerifyImageSignature(ctx context.Context, sig oci.Signature, h v1.Hash, co return verifyInternal(ctx, sig, h, verifyOCISignature, co) } -func loadSignatureFromFile(sigRef string, signedImgRef name.Reference, co *CheckOpts) (oci.Signatures, error) { +func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name.Reference, co *CheckOpts) (oci.Signatures, error) { var b64sig string targetSig, err := blob.LoadFileOrURL(sigRef) if err != nil { @@ -800,15 +801,21 @@ func loadSignatureFromFile(sigRef string, signedImgRef name.Reference, co *Check b64sig = base64.StdEncoding.EncodeToString(targetSig) } - digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...) - if err != nil { - return nil, err - } - - payload, err := (&sigPayload.Cosign{Image: digest}).MarshalJSON() - - if err != nil { - return nil, err + var payload []byte + if co.PayloadRef != "" { + payload, err = blob.LoadFileOrURL(co.PayloadRef) + if err != nil { + return nil, err + } + } else { + digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...) + if err != nil { + return nil, err + } + payload, err = ObsoletePayload(ctx, digest) + if err != nil { + return nil, err + } } sig, err := static.NewSignature(payload, b64sig) @@ -1366,7 +1373,10 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name return nil, false, err } } else { - sigs, err = loadSignatureFromFile(sigRef, signedImgRef, co) + if co.PayloadRef == "" { + return nil, false, errors.New("payload is required with a manually-provided signature") + } + sigs, err = loadSignatureFromFile(ctx, sigRef, signedImgRef, co) if err != nil { return nil, false, err } diff --git a/test/e2e_test.ps1 b/test/e2e_test.ps1 index 9d2973c447d..66e4c30edd7 100644 --- a/test/e2e_test.ps1 +++ b/test/e2e_test.ps1 @@ -35,8 +35,8 @@ $signing_key = "cosign.key" $verification_key = "cosign.pub" $test_img = "ghcr.io/distroless/static" -Write-Output $pass | .\cosign.exe sign --key $signing_key --output-signature interactive.sig --tlog-upload=false $test_img -.\cosign.exe verify --key $verification_key --signature interactive.sig --insecure-ignore-tlog=true $test_img +Write-Output $pass | .\cosign.exe sign --key $signing_key --output-signature interactive.sig --output-payload interactive.payload --tlog-upload=false $test_img +.\cosign.exe verify --key $verification_key --signature interactive.sig --payload interactive.payload --insecure-ignore-tlog=true $test_img Pop-Location diff --git a/test/e2e_test_secrets_kms.sh b/test/e2e_test_secrets_kms.sh index 55c81e55b2b..27a1aab58c9 100755 --- a/test/e2e_test_secrets_kms.sh +++ b/test/e2e_test_secrets_kms.sh @@ -59,8 +59,8 @@ unset COSIGN_REPOSITORY stdin_password=${COSIGN_PASSWORD} unset COSIGN_PASSWORD (crane delete $(./cosign triangulate $img)) || true -echo $stdin_password | ./cosign sign --key ${signing_key} --output-signature interactive.sig $img -COSIGN_KEY=${verification_key} COSIGN_SIGNATURE=interactive.sig ./cosign verify $img +echo $stdin_password | ./cosign sign --key ${signing_key} --output-signature interactive.sig --output-payload interactive.payload $img +COSIGN_KEY=${verification_key} COSIGN_SIGNATURE=interactive.sig ./cosign verify --payload interactive.payload $img export COSIGN_PASSWORD=${stdin_password} # What else needs auth?