Skip to content
Open
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
14 changes: 12 additions & 2 deletions cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,16 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
return err
}

bundleOpts := signcommon.CommonBundleOpts{
Payload: payload,
Digest: digest,
PredicateType: types.CosignSignPredicateType,
Upload: !c.NoUpload,
OCIRemoteOpts: ociremoteOpts,
}

if c.SigningConfig != nil {
return signcommon.WriteNewBundleWithSigningConfig(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, payload, digest, types.CosignSignPredicateType, "", c.SigningConfig, c.TrustedMaterial, ociremoteOpts...)
return signcommon.WriteNewBundleWithSigningConfig(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, bundleOpts, c.SigningConfig, c.TrustedMaterial)
}

bundleComponents, closeSV, err := signcommon.GetBundleComponents(ctx, c.CertPath, c.CertChainPath, c.KeyOpts, c.NoUpload, c.TlogUpload, payload, digest, c.RekorEntryType)
Expand Down Expand Up @@ -148,6 +156,8 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
return err
}

bundleOpts.PredicateType = predicateType

predicateTypeAnnotation := map[string]string{
"predicateType": predicateType,
}
Expand All @@ -159,7 +169,7 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
}

if c.KeyOpts.NewBundleFormat {
return signcommon.WriteBundle(sv, bundleComponents.RekorEntry, payload, bundleComponents.SignedPayload, bundleComponents.SignerBytes, bundleComponents.TimestampBytes, digest, predicateType, ociremoteOpts...)
return signcommon.WriteBundle(ctx, sv, bundleComponents.RekorEntry, bundleOpts, bundleComponents.SignedPayload, bundleComponents.SignerBytes, bundleComponents.TimestampBytes)
}

// We don't actually need to access the remote entity to attach things to it
Expand Down
8 changes: 6 additions & 2 deletions cmd/cosign/cli/attest/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"strings"
"time"

"github.com/google/go-containerregistry/pkg/name"
intotov1 "github.com/in-toto/attestation/go/v1"
"github.com/sigstore/cosign/v3/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon"
Expand Down Expand Up @@ -142,8 +141,13 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
}
}

bundleOpts := signcommon.CommonBundleOpts{
Payload: payload,
BundlePath: c.BundlePath,
}

if c.SigningConfig != nil {
return signcommon.WriteNewBundleWithSigningConfig(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, payload, name.Digest{}, "", c.BundlePath, c.SigningConfig, c.TrustedMaterial, nil)
return signcommon.WriteNewBundleWithSigningConfig(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, bundleOpts, c.SigningConfig, c.TrustedMaterial)
}

bundleComponents, closeSV, err := signcommon.GetBundleComponents(ctx, c.CertPath, c.CertChainPath, c.KeyOpts, false, c.TlogUpload, payload, nil, c.RekorEntryType)
Expand Down
5 changes: 5 additions & 0 deletions cmd/cosign/cli/options/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type SignOptions struct {
OutputSignature string // TODO: this should be the root output file arg.
OutputPayload string
OutputCertificate string
Bundle string
PayloadPath string
Recursive bool
Attachment string
Expand Down Expand Up @@ -97,6 +98,10 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) {
"write the certificate to FILE")
_ = cmd.MarkFlagFilename("output-certificate", certificateExts...)

cmd.Flags().StringVar(&o.Bundle, "bundle", "",
"write everything required to verify the image to FILE")
_ = cmd.MarkFlagFilename("bundle", bundleExts...)

cmd.Flags().StringVar(&o.PayloadPath, "payload", "",
"path to a payload file to use rather than generating one")
// _ = cmd.MarkFlagFilename("payload") // no typical extensions
Expand Down
13 changes: 11 additions & 2 deletions cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,17 @@ func signDigestBundle(ctx context.Context, digest name.Digest, ko options.KeyOpt
return fmt.Errorf("constructing client options: %w", err)
}

bundleOpts := signcommon.CommonBundleOpts{
Payload: payload,
Digest: digest,
PredicateType: types.CosignSignPredicateType,
BundlePath: signOpts.Bundle,
Upload: signOpts.Upload,
OCIRemoteOpts: ociremoteOpts,
}

if ko.SigningConfig != nil {
return signcommon.WriteNewBundleWithSigningConfig(ctx, ko, signOpts.Cert, signOpts.CertChain, payload, digest, types.CosignSignPredicateType, "", ko.SigningConfig, ko.TrustedMaterial, ociremoteOpts...)
return signcommon.WriteNewBundleWithSigningConfig(ctx, ko, signOpts.Cert, signOpts.CertChain, bundleOpts, ko.SigningConfig, ko.TrustedMaterial)
}

bundleComponents, closeSV, err := signcommon.GetBundleComponents(ctx, signOpts.Cert, signOpts.CertChain, ko, false, signOpts.TlogUpload, payload, digest, "dsse")
Expand All @@ -190,7 +199,7 @@ func signDigestBundle(ctx context.Context, digest name.Digest, ko options.KeyOpt
}
defer closeSV()

return signcommon.WriteBundle(bundleComponents.SV, bundleComponents.RekorEntry, payload, bundleComponents.SignedPayload, bundleComponents.SignerBytes, bundleComponents.TimestampBytes, digest, types.CosignSignPredicateType, ociremoteOpts...)
return signcommon.WriteBundle(ctx, bundleComponents.SV, bundleComponents.RekorEntry, bundleOpts, bundleComponents.SignedPayload, bundleComponents.SignerBytes, bundleComponents.TimestampBytes)
}

func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko options.KeyOpts, signOpts options.SignOptions,
Expand Down
39 changes: 30 additions & 9 deletions cmd/cosign/cli/signcommon/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,43 +466,64 @@ func UploadToTlog(ctx context.Context, ko options.KeyOpts, ref name.Reference, t
return entry, nil
}

type CommonBundleOpts struct {
Payload []byte
Digest name.Digest
PredicateType string
BundlePath string
Upload bool
OCIRemoteOpts []ociremote.Option
}

// WriteBundle compiles a protobuf bundle from components and writes the bundle to the OCI remote layer.
func WriteBundle(sv *SignerVerifier, rekorEntry *models.LogEntryAnon, payload, signedPayload, signerBytes, timestampBytes []byte, digest name.Digest, predicateType string, ociremoteOpts ...ociremote.Option) error {
func WriteBundle(ctx context.Context, sv *SignerVerifier, rekorEntry *models.LogEntryAnon, bundleOpts CommonBundleOpts, signedPayload, signerBytes, timestampBytes []byte) error {
pubKey, err := sv.PublicKey()
if err != nil {
return err
}
bundleBytes, err := cbundle.MakeNewBundle(pubKey, rekorEntry, payload, signedPayload, signerBytes, timestampBytes)
bundleBytes, err := cbundle.MakeNewBundle(pubKey, rekorEntry, bundleOpts.Payload, signedPayload, signerBytes, timestampBytes)
if err != nil {
return err
}
return ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, predicateType, ociremoteOpts...)
if bundleOpts.BundlePath != "" {
if err := os.WriteFile(bundleOpts.BundlePath, bundleBytes, 0600); err != nil {
return fmt.Errorf("creating bundle file: %w", err)
}
ui.Infof(ctx, "Wrote bundle to file %s", bundleOpts.BundlePath)
}
if !bundleOpts.Upload {
return nil
}
return ociremote.WriteAttestationNewBundleFormat(bundleOpts.Digest, bundleBytes, bundleOpts.PredicateType, bundleOpts.OCIRemoteOpts...)
}

// WriteNewBundleWithSigningConfig uses signing config and trusted root to fetch responses from services for the bundle and writes the bundle to the OCI remote layer.
func WriteNewBundleWithSigningConfig(ctx context.Context, ko options.KeyOpts, cert, certChain string, payload []byte, digest name.Digest, predicateType, bundlePath string, signingConfig *root.SigningConfig, trustedMaterial root.TrustedMaterial, ociremoteOpts ...ociremote.Option) error {
func WriteNewBundleWithSigningConfig(ctx context.Context, ko options.KeyOpts, cert, certChain string, bundleOpts CommonBundleOpts, signingConfig *root.SigningConfig, trustedMaterial root.TrustedMaterial) error {
keypair, idToken, err := GetKeypairAndToken(ctx, ko, cert, certChain)
if err != nil {
return fmt.Errorf("getting keypair and token: %w", err)
}

content := &sign.DSSEData{
Data: payload,
Data: bundleOpts.Payload,
PayloadType: "application/vnd.in-toto+json",
}
bundle, err := cbundle.SignData(ctx, content, keypair, idToken, signingConfig, trustedMaterial)
if err != nil {
return fmt.Errorf("signing bundle: %w", err)
}

if bundlePath != "" {
if err := os.WriteFile(bundlePath, bundle, 0600); err != nil {
if bundleOpts.BundlePath != "" {
if err := os.WriteFile(bundleOpts.BundlePath, bundle, 0600); err != nil {
return fmt.Errorf("creating bundle file: %w", err)
}
ui.Infof(ctx, "Wrote bundle to file %s", bundlePath)
ui.Infof(ctx, "Wrote bundle to file %s", bundleOpts.BundlePath)
return nil
}
if !bundleOpts.Upload {
return nil
}
return ociremote.WriteAttestationNewBundleFormat(digest, bundle, predicateType, ociremoteOpts...)
return ociremote.WriteAttestationNewBundleFormat(bundleOpts.Digest, bundle, bundleOpts.PredicateType, bundleOpts.OCIRemoteOpts...)
}

type bundleComponents struct {
Expand Down
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.

89 changes: 89 additions & 0 deletions test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
Expand All @@ -36,6 +37,7 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -4042,6 +4044,7 @@ func TestTree(t *testing.T) {
ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
so := options.SignOptions{
NewBundleFormat: true,
Upload: true,
}

must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
Expand All @@ -4051,3 +4054,89 @@ func TestTree(t *testing.T) {
must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t)
assert.True(t, strings.Contains(out.String(), "https://sigstore.dev/cosign/sign/v1"))
}

func TestSignVerifyUploadFalse(t *testing.T) {
td := t.TempDir()
ctx := context.Background()

repo, stop := reg(t)
defer stop()

imgName := path.Join(repo, "cosign-e2e-no-upload")
name, desc, cleanup := mkimage(t, imgName)
defer cleanup()

_, privKeyPath, _ := keypair(t, td)

regOpts := options.RegistryOptions{}
regExpOpts := options.RegistryExperimentalOptions{}
out := bytes.Buffer{}

// There should be no signatures yet
must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t)
assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image")

// Now sign the image with Upload: false
ko := options.KeyOpts{
KeyRef: privKeyPath,
PassFunc: passFunc,
SkipConfirmation: true,
}
so := options.SignOptions{
Upload: false,
}
must(sign.SignCmd(ro, ko, so, []string{imgName}), t)

// There should still be no signatures
out.Reset()
must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t)
assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image")

// Now with Upload: true
so.Upload = true
must(sign.SignCmd(ro, ko, so, []string{imgName}), t)

// Now there should be signatures
out.Reset()
must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t)
assert.Contains(t, out.String(), fmt.Sprintf("Signatures for an image tag: %s:%s-%s.sig", name, desc.Digest.Algorithm, desc.Digest.Hex))

// Try on a new image with new bundle format
imgName = path.Join(repo, "cosign-e2e-no-upload-bundle")
name2, _, cleanup2 := mkimage(t, imgName)
defer cleanup2()

// There should be no signatures yet
out.Reset()
must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t)
assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image")

// Now sign the image with Upload: false
so.Upload = false
so.NewBundleFormat = true
so.Bundle = path.Join(td, "output.bundle")
must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
assert.FileExists(t, so.Bundle)

// There should still be no signatures
out.Reset()
must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t)
assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image")

// Now with Upload: true
so.Upload = true
must(sign.SignCmd(ro, ko, so, []string{imgName}), t)

// Now there should be signatures
out.Reset()
must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t)
assert.Regexp(t, regexp.MustCompile(fmt.Sprintf("https://sigstore.dev/cosign/sign/v1 artifacts via OCI referrer: %s@sha256:[a-z0-9]*\n", name2)), out.String())
assert.FileExists(t, so.Bundle)
f, err := os.Open(so.Bundle)
must(err, t)
defer f.Close()
h := sha256.New()
_, err = io.Copy(h, f)
must(err, t)
assert.Contains(t, out.String(), fmt.Sprintf("sha256:%s", hex.EncodeToString(h.Sum(nil))))
}
Loading