Skip to content
Draft
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
6 changes: 6 additions & 0 deletions cmd/cosign/cli/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ func Attest() *cobra.Command {

Args: cobra.MinimumNArgs(1),
PersistentPreRun: options.BindViper,
PreRunE: func(_ *cobra.Command, _ []string) error {
if o.NewBundleFormat && o.NoUpload && o.BundlePath == "" {
return fmt.Errorf("must enable upload to the OCI registry or specify a local --bundle path with --new-bundle-format")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
oidcClientSecret, err := o.OIDC.ClientSecret()
if err != nil {
Expand Down
106 changes: 74 additions & 32 deletions cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,25 @@ import (
"context"
_ "crypto/sha256" // for `crypto.SHA256`
"fmt"
"os"
"time"

"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"google.golang.org/protobuf/encoding/protojson"

"github.com/sigstore/cosign/v3/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon"
"github.com/sigstore/cosign/v3/internal/ui"
"github.com/sigstore/cosign/v3/pkg/cosign/attestation"
cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle"
cremote "github.com/sigstore/cosign/v3/pkg/cosign/remote"
"github.com/sigstore/cosign/v3/pkg/oci/mutate"
ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
"github.com/sigstore/cosign/v3/pkg/oci/static"
"github.com/sigstore/cosign/v3/pkg/types"
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
"github.com/sigstore/sigstore/pkg/signature"
)

// nolint
Expand Down Expand Up @@ -123,64 +128,106 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
bundleOpts := signcommon.CommonBundleOpts{
Payload: payload,
Digest: digest,
PredicateType: types.CosignSignPredicateType,
PredicateType: predicateURI,
BundlePath: c.BundlePath,
Upload: !c.NoUpload,
OCIRemoteOpts: ociremoteOpts,
}

if c.SigningConfig != nil {
return signcommon.WriteNewBundleWithSigningConfig(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, bundleOpts, c.SigningConfig, c.TrustedMaterial)
if c.SigningConfig == nil {
c.SigningConfig, err = signcommon.NewSigningConfigFromKeyOpts(c.KeyOpts, c.TlogUpload)
if err != nil {
return fmt.Errorf("creating signing config: %w", err)
}
}

bundleComponents, closeSV, err := signcommon.GetBundleComponents(ctx, c.CertPath, c.CertChainPath, c.KeyOpts, c.NoUpload, c.TlogUpload, payload, digest, c.RekorEntryType)
bundleBytes, pubKey, pubKeyPem, hashAlgProto, err := signcommon.NewAttestationBundle(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, bundleOpts, c.SigningConfig, c.TrustedMaterial)
if err != nil {
return fmt.Errorf("getting bundle components: %w", err)
return fmt.Errorf("creating bundle: %w", err)
}

if c.NewBundleFormat {
if c.BundlePath != "" {
if err := os.WriteFile(c.BundlePath, bundleBytes, 0600); err != nil {
return fmt.Errorf("create bundle file: %w", err)
}
ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath)
}

if !c.NoUpload {
if err := ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, bundleOpts.PredicateType, ociremoteOpts...); err != nil {
return fmt.Errorf("writing bundle: %w", err)
}
}
return nil
}
defer closeSV()

sv := bundleComponents.SV
var pb protobundle.Bundle
if err := protojson.Unmarshal(bundleBytes, &pb); err != nil {
return fmt.Errorf("unmarshalling bundle: %w", err)
}

if c.NoUpload && c.BundlePath == "" {
fmt.Println(string(bundleComponents.SignedPayload))
return nil
bundleComponents, err := signcommon.ExtractComponentsFromProtoBundle(&pb)
if err != nil {
return fmt.Errorf("extracting components from bundle: %w", err)
}

opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)}
if sv.Cert != nil {
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
legacyBundleBytes, err := signcommon.NewLegacyBundleFromProtoBundleComponents(bundleComponents, pubKeyPem)
if err != nil {
return fmt.Errorf("creating legacy bundle: %w", err)
}

if bundleComponents.RFC3161Timestamp != nil {
opts = append(opts, static.WithRFC3161Timestamp(bundleComponents.RFC3161Timestamp))
if c.BundlePath != "" {
if err := os.WriteFile(c.BundlePath, legacyBundleBytes, 0600); err != nil {
return fmt.Errorf("create bundle file: %w", err)
}
ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath)
}

predicateType, err := options.ParsePredicateType(c.PredicateType)
if err != nil {
return err
if c.NoUpload {
return nil
}

bundleOpts.PredicateType = predicateType
certPem, chainPem := signcommon.EncodeCertificatesToPEM(bundleComponents.Certificates)

opts := []static.Option{
static.WithLayerMediaType(types.DssePayloadType),
static.WithAnnotations(map[string]string{
"predicateType": predicateURI,
}),
}
if certPem != nil {
opts = append(opts, static.WithCertChain(certPem, chainPem))
}

if len(bundleComponents.RFC3161Timestamps) > 0 {
opts = append(opts, static.WithRFC3161Timestamp(cbundle.TimestampToRFC3161Timestamp(bundleComponents.RFC3161Timestamps[0].GetSignedTimestamp())))
}

predicateTypeAnnotation := map[string]string{
"predicateType": predicateType,
"predicateType": predicateURI,
}
// Add predicateType as manifest annotation
opts = append(opts, static.WithAnnotations(predicateTypeAnnotation))

if bundleComponents.RekorEntry != nil {
opts = append(opts, static.WithBundle(cbundle.EntryToBundle(bundleComponents.RekorEntry)))
if len(bundleComponents.RekorEntries) > 0 {
opts = append(opts, static.WithBundle(signcommon.RekorBundleFromProtoTlogEntry(bundleComponents.RekorEntries[0])))
}

if c.KeyOpts.NewBundleFormat {
return signcommon.WriteBundle(ctx, sv, bundleComponents.RekorEntry, bundleOpts, bundleComponents.SignedPayload, bundleComponents.SignerBytes, bundleComponents.TimestampBytes)
ociSig, err := static.NewAttestation(bundleComponents.Signature, opts...)
if err != nil {
return fmt.Errorf("creating attestation: %w", err)
}

// We don't actually need to access the remote entity to attach things to it
// so we use a placeholder here.
se := ociremote.SignedUnknown(digest, ociremoteOpts...)

dd := cremote.NewDupeDetector(sv)
ddVerifier, err := signature.LoadVerifier(pubKey, signcommon.ProtoHashAlgoToHash(hashAlgProto))
if err != nil {
return fmt.Errorf("loading verifier: %w", err)
}
dd := cremote.NewDupeDetector(ddVerifier)
signOpts := []mutate.SignOption{
mutate.WithDupeDetector(dd),
mutate.WithRecordCreationTimestamp(c.RecordCreationTimestamp),
Expand All @@ -191,15 +238,10 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
signOpts = append(signOpts, mutate.WithReplaceOp(ro))
}

sig, err := static.NewAttestation(bundleComponents.SignedPayload, opts...)
if err != nil {
return err
}

// Attach the attestation to the entity.
newSE, err := mutate.AttachAttestationToEntity(se, sig, signOpts...)
newSE, err := mutate.AttachAttestationToEntity(se, ociSig, signOpts...)
if err != nil {
return err
return fmt.Errorf("attaching attestation: %w", err)
}

// Publish the attestations associated with this entity
Expand Down
98 changes: 51 additions & 47 deletions cmd/cosign/cli/attest/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"bytes"
"context"
"crypto"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
Expand All @@ -32,11 +31,12 @@ import (
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"
"github.com/sigstore/cosign/v3/pkg/cosign"
"github.com/sigstore/cosign/v3/internal/ui"
"github.com/sigstore/cosign/v3/pkg/cosign/attestation"
cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle"
"github.com/sigstore/sigstore/pkg/cryptoutils"
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
"github.com/sigstore/sigstore/pkg/signature"
"google.golang.org/protobuf/encoding/protojson"
)

// nolint
Expand Down Expand Up @@ -146,87 +146,91 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
BundlePath: c.BundlePath,
}

if c.SigningConfig != nil {
return signcommon.WriteNewBundleWithSigningConfig(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, bundleOpts, c.SigningConfig, c.TrustedMaterial)
if c.SigningConfig == nil {
var err error
c.SigningConfig, err = signcommon.NewSigningConfigFromKeyOpts(c.KeyOpts, c.TlogUpload)
if err != nil {
return fmt.Errorf("creating signing config: %w", err)
}
}

bundleComponents, closeSV, err := signcommon.GetBundleComponents(ctx, c.CertPath, c.CertChainPath, c.KeyOpts, false, c.TlogUpload, payload, nil, c.RekorEntryType)
bundleBytes, _, pubKeyPem, _, err := signcommon.NewAttestationBundle(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, bundleOpts, c.SigningConfig, c.TrustedMaterial)
if err != nil {
return fmt.Errorf("getting bundle components: %w", err)
return fmt.Errorf("creating bundle: %w", err)
}
defer closeSV()

sv := bundleComponents.SV
if c.NewBundleFormat {
if err := os.WriteFile(c.BundlePath, bundleBytes, 0600); err != nil {
return fmt.Errorf("create bundle file: %w", err)
}
ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath)
return nil
}

signedPayload := cosign.LocalSignedPayload{}
var pb protobundle.Bundle
if err := protojson.Unmarshal(bundleBytes, &pb); err != nil {
return fmt.Errorf("unmarshalling bundle: %w", err)
}

if bundleComponents.RekorEntry != nil {
signedPayload.Bundle = cbundle.EntryToBundle(bundleComponents.RekorEntry)
bundleComponents, err := signcommon.ExtractComponentsFromProtoBundle(&pb)
if err != nil {
return err
}

if c.BundlePath != "" {
var contents []byte
if c.NewBundleFormat {
pubKey, err := sv.PublicKey()
if err != nil {
return err
}

contents, err = cbundle.MakeNewBundle(pubKey, bundleComponents.RekorEntry, payload, bundleComponents.SignedPayload, bundleComponents.SignerBytes, bundleComponents.TimestampBytes)
if err != nil {
return err
}
} else {
signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(bundleComponents.SignedPayload)
signedPayload.Cert = base64.StdEncoding.EncodeToString(bundleComponents.SignerBytes)

contents, err = json.Marshal(signedPayload)
if err != nil {
return err
}
contents, err := signcommon.NewLegacyBundleFromProtoBundleComponents(bundleComponents, pubKeyPem)
if err != nil {
return fmt.Errorf("creating legacy bundle: %w", err)
}

if err := os.WriteFile(c.BundlePath, contents, 0600); err != nil {
return fmt.Errorf("create bundle file: %w", err)
}
fmt.Fprintln(os.Stderr, "Bundle wrote in the file ", c.BundlePath)
ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath)
}

if c.OutputSignature != "" {
if err := os.WriteFile(c.OutputSignature, bundleComponents.SignedPayload, 0600); err != nil {
if err := os.WriteFile(c.OutputSignature, bundleComponents.Signature, 0600); err != nil {
return fmt.Errorf("create signature file: %w", err)
}
fmt.Fprintf(os.Stderr, "Signature written in %s\n", c.OutputSignature)
} else {
fmt.Fprintln(os.Stdout, string(bundleComponents.SignedPayload))
fmt.Fprintln(os.Stdout, string(bundleComponents.Signature))
}

if c.OutputAttestation != "" {
if err := os.WriteFile(c.OutputAttestation, payload, 0600); err != nil {
return fmt.Errorf("create signature file: %w", err)
return fmt.Errorf("create attestation file: %w", err)
}
fmt.Fprintf(os.Stderr, "Attestation written in %s\n", c.OutputAttestation)
}

if c.OutputCertificate != "" {
cert, err := cryptoutils.UnmarshalCertificatesFromPEM(bundleComponents.SignerBytes)
// signer is a certificate
if err != nil {
fmt.Fprintln(os.Stderr, "Could not output signer certificate. Was a certificate used? ", err)
return nil

if len(bundleComponents.Certificates) == 0 {
return fmt.Errorf("no certificate found in bundle")
}
if len(cert) != 1 {
fmt.Fprintln(os.Stderr, "Could not output signer certificate. Expected a single certificate")
return nil
}
bts := bundleComponents.SignerBytes
if err := os.WriteFile(c.OutputCertificate, bts, 0600); err != nil {
certPem, _ := signcommon.EncodeCertificatesToPEM(bundleComponents.Certificates)
if err := os.WriteFile(c.OutputCertificate, certPem, 0600); err != nil {
return fmt.Errorf("create certificate file: %w", err)
}
fmt.Fprintln(os.Stderr, "Certificate written to file ", c.OutputCertificate)
}

if c.RFC3161TimestampPath != "" {
if len(bundleComponents.RFC3161Timestamps) == 0 {
return fmt.Errorf("no RFC3161 timestamp found in bundle")
}
legacyTimestamp := cbundle.TimestampToRFC3161Timestamp(bundleComponents.RFC3161Timestamps[0].GetSignedTimestamp())
ts, err := json.Marshal(legacyTimestamp)
if err != nil {
return fmt.Errorf("marshalling timestamp: %w", err)
}
if err := os.WriteFile(c.RFC3161TimestampPath, ts, 0600); err != nil {
return fmt.Errorf("create timestamp file: %w", err)
}
fmt.Fprintln(os.Stderr, "Timestamp wrote in the file ", c.RFC3161TimestampPath)
}

return nil
}

Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/attest/attest_blob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ func TestAttestBlobCmdLocalKeyAndCert(t *testing.T) {
keyOpts := options.KeyOpts{KeyRef: tc.keyref}
if tc.newBundle {
keyOpts.NewBundleFormat = true
keyOpts.BundlePath = filepath.Join(td, "output.bundle")
}
at := AttestBlobCommand{
KeyOpts: keyOpts,
Expand Down
8 changes: 8 additions & 0 deletions cmd/cosign/cli/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package cli

import (
"fmt"

"github.com/sigstore/cosign/v3/cmd/cosign/cli/attest"
"github.com/sigstore/cosign/v3/cmd/cosign/cli/generate"
"github.com/sigstore/cosign/v3/cmd/cosign/cli/options"
Expand Down Expand Up @@ -49,6 +51,12 @@ func AttestBlob() *cobra.Command {
echo <PAYLOAD> | cosign attest-blob --predicate - --yes`,

PersistentPreRun: options.BindViper,
PreRunE: func(_ *cobra.Command, _ []string) error {
if o.NewBundleFormat && o.BundlePath == "" {
return fmt.Errorf("must specify --bundle with --new-bundle-format")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if o.Predicate.Statement == "" && len(args) != 1 {
return cobra.ExactArgs(1)(cmd, args)
Expand Down
3 changes: 2 additions & 1 deletion cmd/cosign/cli/options/fulcio.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type FulcioOptions struct {
URL string
AuthFlow string
IdentityToken string
InsecureSkipFulcioVerify bool
InsecureSkipFulcioVerify bool // Deprecated: SCT verification is no longer performed during signing/attestation.
}

var _ Interface = (*FulcioOptions)(nil)
Expand All @@ -46,4 +46,5 @@ func (o *FulcioOptions) AddFlags(cmd *cobra.Command) {

cmd.Flags().BoolVar(&o.InsecureSkipFulcioVerify, "insecure-skip-verify", false,
"skip verifying fulcio published to the SCT (this should only be used for testing).")
_ = cmd.Flags().MarkDeprecated("insecure-skip-verify", "SCT verification is no longer performed during signing/attestation.")
}
Loading
Loading