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

Add support for new bundle specification for attesting/verifying OCI image attestations #3889

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3dc6a85
Add support for new bundle specification in `cosign attest`
codysoyland Sep 25, 2024
f6b6f81
Add support for TSA for new bundle format in attest cli
codysoyland Oct 29, 2024
e46acdb
Add support for new bundle specification in `cosign verify-attestation`
codysoyland Sep 25, 2024
e509ec5
Refactor and use CheckOpts to build sigstore-go verification options
codysoyland Nov 1, 2024
558e873
Merge branch 'cosign-attest-bundle-spec' into verify-attestation-bund…
codysoyland Nov 4, 2024
8b3edc9
Remove accidentally-committed commented code
codysoyland Nov 4, 2024
b17dfd8
Add comment about missing output data
codysoyland Nov 5, 2024
5ae6109
Add tests and fix a few verifier options
codysoyland Nov 5, 2024
96410a7
Remove incomplete code
codysoyland Nov 5, 2024
c540689
Clean up linter warnings
codysoyland Nov 6, 2024
8796901
Update docs
codysoyland Nov 6, 2024
67d421a
Use ParsePredicateType helper here
codysoyland Nov 6, 2024
ba5c7cb
Add missing annotations
codysoyland Nov 6, 2024
7a4c4df
Filter by bundle artifact type
codysoyland Nov 6, 2024
693b391
Fix up MediaType logic
codysoyland Nov 7, 2024
a380b89
Revert "Use ParsePredicateType helper here"
codysoyland Nov 19, 2024
189e0d5
Rename flag to NewBundleFormat for consistency
codysoyland Nov 19, 2024
ac29355
Add unit tests for cosign.VerifyNewBundle
codysoyland Nov 21, 2024
2f3b321
Remove redundant new-bundle-format flags
codysoyland Nov 21, 2024
524f558
Update docs
codysoyland Nov 21, 2024
5c1fd5a
This TODO is not needed: go-containerregistry supports tag fallback s…
codysoyland Nov 22, 2024
4d41f5f
Update flag name
codysoyland Nov 22, 2024
77be5a8
Add missing brace
codysoyland Nov 22, 2024
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
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 @@ -162,6 +162,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 @@ -172,28 +173,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
25 changes: 4 additions & 21 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 @@ -222,7 +213,6 @@ type VerifyBlobAttestationOptions struct {
Key string
SignaturePath string
BundlePath string
NewBundleFormat bool
TrustedRootPath string

PredicateOptions
Expand Down Expand Up @@ -255,13 +245,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: 5 additions & 3 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ against the transparency log.`,
Offline: o.CommonVerifyOptions.Offline,
TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath,
IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog,
UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps,
MaxWorkers: o.CommonVerifyOptions.MaxWorkers,
ExperimentalOCI11: o.CommonVerifyOptions.ExperimentalOCI11,
}
Expand Down Expand Up @@ -244,6 +245,7 @@ against the transparency log.`,
Offline: o.CommonVerifyOptions.Offline,
TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath,
IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog,
UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps,
MaxWorkers: o.CommonVerifyOptions.MaxWorkers,
}

Expand Down Expand Up @@ -333,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 @@ -345,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 @@ -404,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 Down
11 changes: 11 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 @@ -142,9 +143,19 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
Identities: identities,
Offline: c.Offline,
IgnoreTlog: c.IgnoreTlog,
UseSignedTimestamps: c.UseSignedTimestamps,
MaxWorkers: c.MaxWorkers,
ExperimentalOCI11: c.ExperimentalOCI11,
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
Loading