From e44079717028750d2ea3733df6882f39d22396d5 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] 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 f2bcb05d3f43..98348a412192 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 28d6d2e0e988..c3d19186042e 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -115,6 +115,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 73de8b4aba76..fe9ac258b6af 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -70,6 +70,7 @@ type VerifyCommand struct { Attachment string Annotations sigs.AnnotationsMap SignatureRef string + PayloadRef string HashAlgorithm crypto.Hash LocalImage bool NameOptions []name.Option @@ -119,6 +120,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 6ad78a9480cf..a5a1d6c8fd73 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 5ac17bbf0c24..fc9565b3d8cf 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 13e77b0616c3..1ea9d603f19e 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 c26d5f7f2c3f..2d12f74830c9 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 9d2973c447db..66e4c30edd7e 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 55c81e55b2bf..27a1aab58c9e 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?