Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require a payload to be provided with a signature #2785

Merged
merged 5 commits into from
Apr 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down Expand Up @@ -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`!
Expand Down
6 changes: 3 additions & 3 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
4 changes: 2 additions & 2 deletions cmd/cosign/cli/attach/sig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/options/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/options/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type VerifyOptions struct {
Attachment string
Output string
SignatureRef string
PayloadRef string
LocalImage bool

CommonVerifyOptions CommonVerifyOptions
Expand Down Expand Up @@ -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'")
}
Expand Down
36 changes: 23 additions & 13 deletions cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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.
Expand All @@ -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)
}
Expand All @@ -261,30 +260,41 @@ 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 {
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 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
}

Expand All @@ -295,7 +305,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)
}
Expand All @@ -309,7 +319,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...)
}

Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type VerifyCommand struct {
Attachment string
Annotations sigs.AnnotationsMap
SignatureRef string
PayloadRef string
HashAlgorithm crypto.Hash
LocalImage bool
NameOptions []name.Option
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion doc/cosign_attach_signature.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_dockerfile_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_manifest_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_sign.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions pkg/cosign/obsolete.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading