Skip to content

Commit

Permalink
Add support for new bundle specification for container images
Browse files Browse the repository at this point in the history
Signed-off-by: Cody Soyland <[email protected]>
  • Loading branch information
codysoyland committed Jan 24, 2025
1 parent accc80a commit 1602cc1
Show file tree
Hide file tree
Showing 24 changed files with 868 additions and 102 deletions.
1 change: 1 addition & 0 deletions cmd/cosign/cli/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func Attest() *cobra.Command {
OIDCProvider: o.OIDC.Provider,
SkipConfirmation: o.SkipConfirmation,
TSAServerURL: o.TSAServerURL,
NewBundleFormat: o.NewBundleFormat,
}
attestCommand := attest.AttestCommand{
KeyOpts: ko,
Expand Down
47 changes: 34 additions & 13 deletions cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import (

type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error)

func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*cbundle.RekorBundle, error) {
func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*models.LogEntryAnon, error) {
rekorBytes, err := sv.Bytes(ctx)
if err != nil {
return nil, err
Expand All @@ -64,7 +64,7 @@ func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string,
return nil, err
}
fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex)
return cbundle.EntryToBundle(entry), nil
return entry, nil
}

// nolint
Expand Down Expand Up @@ -174,20 +174,28 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
if sv.Cert != nil {
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
}
var timestampBytes []byte
var tsaPayload []byte
if c.KeyOpts.TSAServerURL != "" {
// TODO - change this when we implement protobuf / new bundle support
// We need to decide what signature to send to the timestamp authority.
//
// Historically, cosign sent the entire JSON DSSE Envelope to the
// timestamp authority. However, when sigstore clients are verifying a
// bundle they will use the DSSE Sig field, so we choose what signature
// to send to the timestamp authority based on our output format.
//
// See cmd/cosign/cli/attest/attest_blob.go
responseBytes, err := tsa.GetTimestampedSignature(signedPayload, tsaclient.NewTSAClient(c.KeyOpts.TSAServerURL))
// Historically, cosign sent `signedPayload`, which is the entire JSON DSSE
// Envelope. However, when sigstore clients are verifying a bundle they
// will use the DSSE Sig field, so we choose what signature to send to
// the timestamp authority based on our output format.
if c.KeyOpts.NewBundleFormat {
tsaPayload, err = getEnvelopeSigBytes(signedPayload)
if err != nil {
return err
}
} else {
tsaPayload = signedPayload
}
timestampBytes, err = tsa.GetTimestampedSignature(tsaPayload, tsaclient.NewTSAClient(c.KeyOpts.TSAServerURL))
if err != nil {
return err
}
bundle := cbundle.TimestampToRFC3161Timestamp(responseBytes)
bundle := cbundle.TimestampToRFC3161Timestamp(timestampBytes)

opts = append(opts, static.WithRFC3161Timestamp(bundle))
}
Expand All @@ -208,8 +216,9 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
if err != nil {
return fmt.Errorf("should upload to tlog: %w", err)
}
var rekorEntry *models.LogEntryAnon
if shouldUpload {
bundle, err := uploadToTlog(ctx, sv, c.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) {
rekorEntry, err = uploadToTlog(ctx, sv, c.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) {
if c.RekorEntryType == "intoto" {
return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b)
} else {
Expand All @@ -220,14 +229,26 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
if err != nil {
return err
}
opts = append(opts, static.WithBundle(bundle))
opts = append(opts, static.WithBundle(cbundle.EntryToBundle(rekorEntry)))
}

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

if c.KeyOpts.NewBundleFormat {
signerBytes, err := sv.Bytes(ctx)
if err != nil {
return err
}
bundleBytes, err := makeNewBundle(sv, rekorEntry, payload, signedPayload, signerBytes, timestampBytes)
if err != nil {
return err
}
return ociremote.WriteAttestationNewBundleFormat(digest.Repository, bundleBytes, predicateType, ociremoteOpts...)
}

// 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...)
Expand Down
25 changes: 7 additions & 18 deletions cmd/cosign/cli/attest/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error

var rfc3161Timestamp *cbundle.RFC3161Timestamp
var timestampBytes []byte
var tsaPayload []byte
var rekorEntry *models.LogEntryAnon

if c.TSAServerURL != "" {
Expand All @@ -173,28 +174,16 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
// will use the DSSE Sig field, so we choose what signature to send to
// the timestamp authority based on our output format.
if c.NewBundleFormat {
var envelope dsse.Envelope
err = json.Unmarshal(sig, &envelope)
if err != nil {
return err
}
if len(envelope.Signatures) == 0 {
return fmt.Errorf("envelope has no signatures")
}
envelopeSigBytes, err := base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig)
if err != nil {
return err
}

timestampBytes, err = tsa.GetTimestampedSignature(envelopeSigBytes, client.NewTSAClient(c.TSAServerURL))
tsaPayload, err = getEnvelopeSigBytes(sig)
if err != nil {
return err
}
} else {
timestampBytes, err = tsa.GetTimestampedSignature(sig, client.NewTSAClient(c.TSAServerURL))
if err != nil {
return err
}
tsaPayload = sig
}
timestampBytes, err = tsa.GetTimestampedSignature(tsaPayload, client.NewTSAClient(c.TSAServerURL))
if err != nil {
return err
}
rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(timestampBytes)
// TODO: Consider uploading RFC3161 TS to Rekor
Expand Down
16 changes: 16 additions & 0 deletions cmd/cosign/cli/attest/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
package attest

import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"

"github.com/secure-systems-lab/go-securesystemslib/dsse"
)

func predicateReader(predicatePath string) (io.ReadCloser, error) {
Expand All @@ -33,3 +37,15 @@ func predicateReader(predicatePath string) (io.ReadCloser, error) {
}
return f, nil
}

func getEnvelopeSigBytes(envelopeBytes []byte) ([]byte, error) {
var envelope dsse.Envelope
err := json.Unmarshal(envelopeBytes, &envelope)
if err != nil {
return nil, err
}
if len(envelope.Signatures) == 0 {
return nil, fmt.Errorf("envelope has no signatures")
}
return base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig)
}
3 changes: 3 additions & 0 deletions cmd/cosign/cli/options/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type AttestOptions struct {
TSAServerURL string
RekorEntryType string
RecordCreationTimestamp bool
NewBundleFormat bool

Rekor RekorOptions
Fulcio FulcioOptions
Expand Down Expand Up @@ -90,4 +91,6 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command) {

cmd.Flags().BoolVar(&o.RecordCreationTimestamp, "record-creation-timestamp", false,
"set the createdAt timestamp in the attestation artifact to the time it was created; by default, cosign sets this to the zero value")

cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, "attach a Sigstore bundle using OCI referrers API")
}
4 changes: 4 additions & 0 deletions cmd/cosign/cli/options/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type CertVerifyOptions struct {
CertChain string
SCT string
IgnoreSCT bool
NewBundleFormat bool
TrustedRootPath string
}

var _ Interface = (*RekorOptions)(nil)
Expand Down Expand Up @@ -103,6 +105,8 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&o.IgnoreSCT, "insecure-ignore-sct", false,
"when set, verification will not check that a certificate contains an embedded SCT, a proof of "+
"inclusion in a certificate transparency log")
cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "", "Path to a Sigstore TrustedRoot JSON file.")
cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, "expect the signature/attestation to be packaged in a Sigstore bundle")
}

func (o *CertVerifyOptions) Identities() ([]cosign.Identity, error) {
Expand Down
32 changes: 7 additions & 25 deletions cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) {
"Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp")

cmd.Flags().BoolVar(&o.UseSignedTimestamps, "use-signed-timestamps", false,
"use signed timestamps if available")
"verify rfc3161 timestamps")

cmd.Flags().BoolVar(&o.IgnoreTlog, "insecure-ignore-tlog", false,
"ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts "+
Expand Down Expand Up @@ -158,11 +158,9 @@ func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) {

// VerifyBlobOptions is the top level wrapper for the `verify blob` command.
type VerifyBlobOptions struct {
Key string
Signature string
BundlePath string
NewBundleFormat bool
TrustedRootPath string
Key string
Signature string
BundlePath string

SecurityKey SecurityKeyOptions
CertVerify CertVerifyOptions
Expand Down Expand Up @@ -190,13 +188,6 @@ func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"path to bundle FILE")

// TODO: have this default to true as a breaking change
cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false,
"output bundle in new format that contains all verification material")

cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
"path to trusted root FILE")

cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "",
"path to RFC3161 timestamp FILE")
}
Expand All @@ -219,11 +210,9 @@ func (o *VerifyDockerfileOptions) AddFlags(cmd *cobra.Command) {

// VerifyBlobAttestationOptions is the top level wrapper for the `verify-blob-attestation` command.
type VerifyBlobAttestationOptions struct {
Key string
SignaturePath string
BundlePath string
NewBundleFormat bool
TrustedRootPath string
Key string
SignaturePath string
BundlePath string

PredicateOptions
CheckClaims bool
Expand Down Expand Up @@ -255,13 +244,6 @@ func (o *VerifyBlobAttestationOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"path to bundle FILE")

// TODO: have this default to true as a breaking change
cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false,
"output bundle in new format that contains all verification material")

cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
"path to trusted root FILE")

cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true,
"if true, verifies the provided blob's sha256 digest exists as an in-toto subject within the attestation. If false, only the DSSE envelope is verified.")

Expand Down
8 changes: 4 additions & 4 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ The blob may be specified as a path to a file or - for stdin.`,
Slot: o.SecurityKey.Slot,
RekorURL: o.Rekor.URL,
BundlePath: o.BundlePath,
NewBundleFormat: o.NewBundleFormat,
NewBundleFormat: o.CertVerify.NewBundleFormat,
RFC3161TimestampPath: o.RFC3161TimestampPath,
TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath,
}
Expand All @@ -347,7 +347,7 @@ The blob may be specified as a path to a file or - for stdin.`,
CARoots: o.CertVerify.CARoots,
CAIntermediates: o.CertVerify.CAIntermediates,
SigRef: o.Signature,
TrustedRootPath: o.TrustedRootPath,
TrustedRootPath: o.CertVerify.TrustedRootPath,
CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger,
CertGithubWorkflowSHA: o.CertVerify.CertGithubWorkflowSha,
CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName,
Expand Down Expand Up @@ -406,7 +406,7 @@ The blob may be specified as a path to a file.`,
Slot: o.SecurityKey.Slot,
RekorURL: o.Rekor.URL,
BundlePath: o.BundlePath,
NewBundleFormat: o.NewBundleFormat,
NewBundleFormat: o.CertVerify.NewBundleFormat,
RFC3161TimestampPath: o.RFC3161TimestampPath,
TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath,
}
Expand All @@ -416,7 +416,7 @@ The blob may be specified as a path to a file.`,
CheckClaims: o.CheckClaims,
SignaturePath: o.SignaturePath,
CertVerifyOptions: o.CertVerify,
TrustedRootPath: o.TrustedRootPath,
TrustedRootPath: o.CertVerify.TrustedRootPath,
CertRef: o.CertVerify.Cert,
CertChain: o.CertVerify.CertChain,
CARoots: o.CertVerify.CARoots,
Expand Down
10 changes: 10 additions & 0 deletions cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key"
"github.com/sigstore/cosign/v2/pkg/oci"
sigs "github.com/sigstore/cosign/v2/pkg/signature"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/payload"
Expand Down Expand Up @@ -134,7 +135,16 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
MaxWorkers: c.MaxWorkers,
ExperimentalOCI11: c.ExperimentalOCI11,
UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps,
NewBundleFormat: c.NewBundleFormat,
}

if c.TrustedRootPath != "" {
co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath)
if err != nil {
return fmt.Errorf("loading trusted root: %w", err)
}
}

if c.CheckClaims {
co.ClaimVerifier = cosign.SimpleClaimVerifier
}
Expand Down
Loading

0 comments on commit 1602cc1

Please sign in to comment.