From 3dc6a85a05401d189e02bda1b4271c650c4e7fe6 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Wed, 25 Sep 2024 11:49:21 -0400 Subject: [PATCH 01/22] Add support for new bundle specification in `cosign attest` Signed-off-by: Cody Soyland --- cmd/cosign/cli/attest.go | 1 + cmd/cosign/cli/attest/attest.go | 22 +++++- cmd/cosign/cli/options/attest.go | 3 + pkg/oci/remote/write.go | 123 +++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 4 deletions(-) diff --git a/cmd/cosign/cli/attest.go b/cmd/cosign/cli/attest.go index 66334471780..0a1e6120f3e 100644 --- a/cmd/cosign/cli/attest.go +++ b/cmd/cosign/cli/attest.go @@ -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, diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index b23d587fc33..8ecc781bc42 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -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 @@ -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 @@ -208,8 +208,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 { @@ -220,7 +221,7 @@ 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...) @@ -228,6 +229,19 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { return err } + if c.KeyOpts.NewBundleFormat { + signerBytes, err := sv.Bytes(ctx) + if err != nil { + return err + } + // TODO: Add TSA timestamp + bundleBytes, err := makeNewBundle(sv, rekorEntry, payload, signedPayload, signerBytes, nil) + if err != nil { + return err + } + return ociremote.WriteAttestationNewBundleFormat(digest.Repository, bundleBytes, 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...) diff --git a/cmd/cosign/cli/options/attest.go b/cmd/cosign/cli/options/attest.go index 8139cddaefa..06bab74ce98 100644 --- a/cmd/cosign/cli/options/attest.go +++ b/cmd/cosign/cli/options/attest.go @@ -32,6 +32,7 @@ type AttestOptions struct { TSAServerURL string RekorEntryType string RecordCreationTimestamp bool + NewBundleFormat bool Rekor RekorOptions Fulcio FulcioOptions @@ -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") } diff --git a/pkg/oci/remote/write.go b/pkg/oci/remote/write.go index d0614768e89..efa8f5f9b71 100644 --- a/pkg/oci/remote/write.go +++ b/pkg/oci/remote/write.go @@ -29,6 +29,7 @@ import ( ociexperimental "github.com/sigstore/cosign/v2/internal/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci" ctypes "github.com/sigstore/cosign/v2/pkg/types" + sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" ) // WriteSignedImageIndexImages writes the images within the image index @@ -221,3 +222,125 @@ func (taggable taggableManifest) RawManifest() ([]byte, error) { func (taggable taggableManifest) MediaType() (types.MediaType, error) { return taggable.mediaType, nil } + +func WriteAttestationNewBundleFormat(d name.Repository, bundleBytes []byte, opts ...Option) error { + o := makeOptions(d, opts...) + + signTarget := d.String() + ref, err := name.ParseReference(signTarget, o.NameOpts...) + if err != nil { + return err + } + desc, err := remote.Head(ref, o.ROpt...) + if err != nil { + return err + } + + // Write the empty config layer + configLayer := static.NewLayer([]byte("{}"), "application/vnd.oci.image.config.v1+json") + configDigest, err := configLayer.Digest() + if err != nil { + return fmt.Errorf("failed to calculate digest: %w", err) + } + configSize, err := configLayer.Size() + if err != nil { + return fmt.Errorf("failed to calculate size: %w", err) + } + err = remote.WriteLayer(d, configLayer, o.ROpt...) + if err != nil { + return fmt.Errorf("failed to upload layer: %w", err) + } + + // generate bundle media type string + bundleMediaType, err := sgbundle.MediaTypeString("0.3") + if err != nil { + return fmt.Errorf("failed to generate bundle media type string: %w", err) + } + + // Write the bundle layer + layer := static.NewLayer(bundleBytes, types.MediaType(bundleMediaType)) + blobDigest, err := layer.Digest() + if err != nil { + return fmt.Errorf("failed to calculate digest: %w", err) + } + + blobSize, err := layer.Size() + if err != nil { + return fmt.Errorf("failed to calculate size: %w", err) + } + + err = remote.WriteLayer(d, layer, o.ROpt...) + if err != nil { + return fmt.Errorf("failed to upload layer: %w", err) + } + + // Create a manifest that includes the blob as a layer + manifest := referrerManifest{v1.Manifest{ + SchemaVersion: 2, + MediaType: types.OCIManifestSchema1, + Config: v1.Descriptor{ + MediaType: types.MediaType("application/vnd.oci.empty.v1+json"), + ArtifactType: bundleMediaType, + Digest: configDigest, + Size: configSize, + }, + Layers: []v1.Descriptor{ + { + MediaType: types.MediaType(bundleMediaType), + Digest: blobDigest, + Size: blobSize, + }, + }, + Subject: &v1.Descriptor{ + MediaType: types.OCIManifestSchema1, + Digest: desc.Digest, + Size: desc.Size, + }, + // TODO: Add annotations org.opencontainers.image.created, dev.sigstore.bundle.content, and dev.sigstore.bundle.predicateType + // See https://github.com/sigstore/cosign/blob/main/specs/BUNDLE_SPEC.md + }, bundleMediaType} + + targetRef, err := manifest.targetRef(d) + if err != nil { + return fmt.Errorf("failed to create target reference: %w", err) + } + + if err := remote.Put(targetRef, manifest, o.ROpt...); err != nil { + return fmt.Errorf("failed to upload manifest: %w", err) + } + + // TODO: add support for tag fallback scheme for non-compliant registries + + return nil +} + +// referrerManifest implements Taggable for use in remote.Put. +// This type also augments the built-in v1.Manifest with an ArtifactType field +// which is part of the OCI 1.1 Image Manifest spec but is unsupported by +// go-containerregistry at this time. +// See https://github.com/opencontainers/image-spec/blob/v1.1.0/manifest.md#image-manifest-property-descriptions +// and https://github.com/google/go-containerregistry/pull/1931 +type referrerManifest struct { + v1.Manifest + ArtifactType string `json:"artifactType,omitempty"` +} + +func (r referrerManifest) RawManifest() ([]byte, error) { + return json.Marshal(r) +} + +func (r referrerManifest) targetRef(repo name.Repository) (name.Reference, error) { + manifestBytes, err := r.RawManifest() + if err != nil { + return nil, err + } + digest, _, err := v1.SHA256(bytes.NewReader(manifestBytes)) + if err != nil { + return nil, err + } + return name.ParseReference(fmt.Sprintf("%s/%s@%s", repo.RegistryStr(), repo.RepositoryStr(), digest.String())) +} + +func (r referrerManifest) MediaType() (types.MediaType, error) { + return types.OCIManifestSchema1, nil +} From f6b6f81704e2c5599327b5a414bce72d2ad3972b Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Tue, 29 Oct 2024 16:16:02 -0400 Subject: [PATCH 02/22] Add support for TSA for new bundle format in attest cli Signed-off-by: Cody Soyland --- cmd/cosign/cli/attest/attest.go | 29 +++++++++++++++++----------- cmd/cosign/cli/attest/attest_blob.go | 25 +++++++----------------- cmd/cosign/cli/attest/common.go | 16 +++++++++++++++ 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index 8ecc781bc42..528481a0fbb 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -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)) } @@ -234,8 +242,7 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { if err != nil { return err } - // TODO: Add TSA timestamp - bundleBytes, err := makeNewBundle(sv, rekorEntry, payload, signedPayload, signerBytes, nil) + bundleBytes, err := makeNewBundle(sv, rekorEntry, payload, signedPayload, signerBytes, timestampBytes) if err != nil { return err } diff --git a/cmd/cosign/cli/attest/attest_blob.go b/cmd/cosign/cli/attest/attest_blob.go index beb8ecf0bf9..02f0db16940 100644 --- a/cmd/cosign/cli/attest/attest_blob.go +++ b/cmd/cosign/cli/attest/attest_blob.go @@ -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 != "" { @@ -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 diff --git a/cmd/cosign/cli/attest/common.go b/cmd/cosign/cli/attest/common.go index b9bb6dcebcc..e5f4589a343 100644 --- a/cmd/cosign/cli/attest/common.go +++ b/cmd/cosign/cli/attest/common.go @@ -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) { @@ -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) +} From e46acdb2e45ae13ef690337d94c3ec76549b8faa Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Wed, 25 Sep 2024 15:20:01 -0400 Subject: [PATCH 03/22] Add support for new bundle specification in `cosign verify-attestation` Signed-off-by: Cody Soyland --- cmd/cosign/cli/options/certificate.go | 4 + cmd/cosign/cli/options/verify.go | 7 - cmd/cosign/cli/verify.go | 2 +- cmd/cosign/cli/verify/verify.go | 10 ++ cmd/cosign/cli/verify/verify_attestation.go | 10 ++ pkg/cosign/verify.go | 154 +++++++++++++++++++- pkg/oci/remote/referrers.go | 4 +- pkg/oci/remote/signatures.go | 24 +++ 8 files changed, 204 insertions(+), 11 deletions(-) diff --git a/cmd/cosign/cli/options/certificate.go b/cmd/cosign/cli/options/certificate.go index 3df7b4b962e..d894380a212 100644 --- a/cmd/cosign/cli/options/certificate.go +++ b/cmd/cosign/cli/options/certificate.go @@ -38,6 +38,8 @@ type CertVerifyOptions struct { CertChain string SCT string IgnoreSCT bool + ExpectSigstoreBundle bool + TrustedRootPath string } var _ Interface = (*RekorOptions)(nil) @@ -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.ExpectSigstoreBundle, "expect-sigstore-bundle", false, "expect the signature/attestation to be packaged in a Sigstore bundle") } func (o *CertVerifyOptions) Identities() ([]cosign.Identity, error) { diff --git a/cmd/cosign/cli/options/verify.go b/cmd/cosign/cli/options/verify.go index 3cdbb0e8a62..b14f06626fb 100644 --- a/cmd/cosign/cli/options/verify.go +++ b/cmd/cosign/cli/options/verify.go @@ -162,7 +162,6 @@ type VerifyBlobOptions struct { Signature string BundlePath string NewBundleFormat bool - TrustedRootPath string SecurityKey SecurityKeyOptions CertVerify CertVerifyOptions @@ -194,9 +193,6 @@ func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) { 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") } @@ -259,9 +255,6 @@ func (o *VerifyBlobAttestationOptions) AddFlags(cmd *cobra.Command) { 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.") diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index 703898523b7..3d360794d46 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -345,7 +345,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, diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 17fd63e8330..434f2501003 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -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" @@ -144,7 +145,16 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { IgnoreTlog: c.IgnoreTlog, MaxWorkers: c.MaxWorkers, ExperimentalOCI11: c.ExperimentalOCI11, + ExpectSigstoreBundle: c.ExpectSigstoreBundle, } + + 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 } diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index 93c27690455..c9e8164f216 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -37,6 +37,7 @@ import ( "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/policy" sigs "github.com/sigstore/cosign/v2/pkg/signature" + "github.com/sigstore/sigstore-go/pkg/root" ) // VerifyAttestationCommand verifies a signature on a supplied container image @@ -119,6 +120,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, MaxWorkers: c.MaxWorkers, + ExpectSigstoreBundle: c.ExpectSigstoreBundle, } if c.CheckClaims { co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier @@ -223,6 +225,14 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e } co.SCT = sct } + case c.TrustedRootPath != "": + if !c.ExpectSigstoreBundle { + return fmt.Errorf("unsupported: trusted root path currently only supported with --expect-sigstore-bundle") + } + co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath) + if err != nil { + return fmt.Errorf("loading trusted root: %w", err) + } case c.CARoots != "": // CA roots + possible intermediates are already loaded into co.RootCerts with the call to // loadCertsKeylessVerification above. diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 3ab5d76026a..8b29c515d37 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -43,8 +43,14 @@ import ( "github.com/sigstore/cosign/v2/internal/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/blob" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" + + ocibundle "github.com/sigstore/cosign/v2/pkg/oci/bundle" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/pkg/types" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" + "github.com/sigstore/sigstore-go/pkg/root" + sgverify "github.com/sigstore/sigstore-go/pkg/verify" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/google/go-containerregistry/pkg/name" @@ -83,6 +89,14 @@ type Identity struct { SubjectRegExp string } +// type CertPool struct { +// certs []*x509.Certificate +// } + +// func (c *CertPool) AddCert(cert *x509.Certificate) { +// c.certs = append(c.certs, cert) +// } + // CheckOpts are the options for checking signatures. type CheckOpts struct { // RegistryClientOpts are the options for interacting with the container registry. @@ -163,6 +177,16 @@ type CheckOpts struct { // Should the experimental OCI 1.1 behaviour be enabled or not. // Defaults to false. ExperimentalOCI11 bool + + ExpectSigstoreBundle bool + + // TrustedMaterial is the trusted material to use for verification. + // Currently, this is only applicable when ExpectSigstoreBundle is true. + TrustedMaterial root.TrustedMaterial + + // TODO: Add the following, deprecate overlapping fields + //CertificateIdentities verify.CertificateIdentities + //VerifierOptions []verify.VerifierOption } // This is a substitutable signature verification function that can be used for verifying @@ -495,6 +519,10 @@ func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co } } + if co.ExpectSigstoreBundle { + return nil, false, errors.New("bundle support for image signatures is not yet implemented") + } + // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") @@ -842,6 +870,24 @@ func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name targetSig = []byte(sigRef) } + if co.ExpectSigstoreBundle { + var bundle *sgbundle.Bundle + bundle.Bundle = new(protobundle.Bundle) + + err = bundle.UnmarshalJSON(targetSig) + if err != nil { + return nil, err + } + signature, err := ocibundle.NewSignature(bundle, &ocibundle.Options{}) + if err != nil { + return nil, err + } + + return &fakeOCISignatures{ + signatures: []oci.Signature{signature}, + }, nil + } + _, err = base64.StdEncoding.DecodeString(string(targetSig)) if err == nil { @@ -880,8 +926,11 @@ func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name // If there were no valid attestations, we return an error. func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. - if co.RootCerts == nil && co.SigVerifier == nil { - return nil, false, errors.New("one of verifier or root certs is required") + if co.RootCerts == nil && co.SigVerifier == nil && co.TrustedMaterial == nil { + return nil, false, errors.New("one of verifier, root certs, or TrustedMaterial is required") + } + if co.ExpectSigstoreBundle { + return verifyImageAttestationsSigstoreBundle(ctx, signedImgRef, co) } // This is a carefully optimized sequence for fetching the attestations of @@ -1413,3 +1462,104 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name return verifySignatures(ctx, sigs, h, co) } + +func getBundles(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) ([]*sgbundle.Bundle, *v1.Hash, error) { + // Enforce this up front. + if co.TrustedMaterial == nil { + return nil, nil, errors.New("Sigstore bundle verification requires TrustedMaterial") + } + + // This is a carefully optimized sequence for fetching the signatures of the + // entity that minimizes registry requests when supplied with a digest input + digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...) + if err != nil { + if terr := (&transport.Error{}); errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound { + return nil, nil, &ErrImageTagNotFound{ + fmt.Errorf("image tag not found: %w", err), + } + } + return nil, nil, err + } + h, err := v1.NewHash(digest.Identifier()) + if err != nil { + return nil, nil, err + } + + index, err := ociremote.Referrers(digest, "", co.RegistryClientOpts...) + if err != nil { + return nil, nil, err + } + var bundles = make([]*sgbundle.Bundle, 0, len(index.Manifests)) + for _, result := range index.Manifests { + st, err := name.ParseReference(fmt.Sprintf("%s@%s", digest.Repository, result.Digest.String())) + if err != nil { + return nil, nil, err + } + bundle, err := ociremote.Bundle(st, co.RegistryClientOpts...) + if err != nil { + return nil, nil, err + } + bundles = append(bundles, bundle) + } + + return bundles, &h, nil +} + +// verifyImageAttestationsSigstoreBundle verifies attestations from attached sigstore bundles +func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { + bundles, hash, err := getBundles(ctx, signedImgRef, co) + if err != nil { + return nil, false, err + } + + digestBytes, err := hex.DecodeString(hash.Hex) + if err != nil { + return nil, false, err + } + + // TODO: build verifierConfig from CheckOpts + verifierConfig := []sgverify.VerifierOption{ + sgverify.WithSignedCertificateTimestamps(1), + sgverify.WithTransparencyLog(1), + sgverify.WithIntegratedTimestamps(1), + } + + sev, err := sgverify.NewSignedEntityVerifier(co.TrustedMaterial, verifierConfig...) + if err != nil { + return nil, false, err + } + + policyOptions := make([]sgverify.PolicyOption, 0, len(co.Identities)) + for _, i := range co.Identities { + id, err := sgverify.NewShortCertificateIdentity(i.Issuer, i.IssuerRegExp, i.Subject, i.SubjectRegExp) + if err != nil { + return nil, false, err + } + policyOptions = append(policyOptions, sgverify.WithCertificateIdentity(id)) + } + policy := sgverify.NewPolicy(sgverify.WithArtifactDigest(hash.Algorithm, digestBytes), policyOptions...) + + checkedSignatures = make([]oci.Signature, 0, len(bundles)) + for _, bundle := range bundles { + _, err := sev.Verify(bundle, policy) + if err != nil { + continue + } + dsse, ok := bundle.Content.(*protobundle.Bundle_DsseEnvelope) + if !ok { + continue + } + payload, err := json.Marshal(dsse.DsseEnvelope) + if err != nil { + continue + } + // TODO: Add additional data to oci.Signature (Cert, Rekor Bundle, Timestamp, etc) + sig, err := static.NewAttestation(payload) + if err != nil { + continue + } + checkedSignatures = append(checkedSignatures, sig) + bundleVerified = true + } + return checkedSignatures, bundleVerified, nil +} diff --git a/pkg/oci/remote/referrers.go b/pkg/oci/remote/referrers.go index 24cb802eb40..1b67cdf392a 100644 --- a/pkg/oci/remote/referrers.go +++ b/pkg/oci/remote/referrers.go @@ -25,7 +25,9 @@ import ( func Referrers(d name.Digest, artifactType string, opts ...Option) (*v1.IndexManifest, error) { o := makeOptions(name.Repository{}, opts...) rOpt := o.ROpt - rOpt = append(rOpt, remote.WithFilter("artifactType", artifactType)) + if artifactType != "" { + rOpt = append(rOpt, remote.WithFilter("artifactType", artifactType)) + } idx, err := remote.Referrers(d, rOpt...) if err != nil { return nil, err diff --git a/pkg/oci/remote/signatures.go b/pkg/oci/remote/signatures.go index 825d80727f9..279d3c257d5 100644 --- a/pkg/oci/remote/signatures.go +++ b/pkg/oci/remote/signatures.go @@ -17,6 +17,7 @@ package remote import ( "errors" + "io" "net/http" "github.com/google/go-containerregistry/pkg/name" @@ -26,6 +27,7 @@ import ( "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/empty" "github.com/sigstore/cosign/v2/pkg/oci/internal/signature" + sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" ) const maxLayers = 1000 @@ -49,6 +51,28 @@ func Signatures(ref name.Reference, opts ...Option) (oci.Signatures, error) { }, nil } +func Bundle(ref name.Reference, opts ...Option) (*sgbundle.Bundle, error) { + signatures, err := Signatures(ref, opts...) + if err != nil { + return nil, err + } + layers, err := signatures.(*sigs).Image.Layers() + if err != nil { + return nil, err + } + layer0, err := layers[0].Uncompressed() + if err != nil { + return nil, err + } + bundleBytes, err := io.ReadAll(layer0) + if err != nil { + return nil, err + } + b := &sgbundle.Bundle{} + err = b.UnmarshalJSON(bundleBytes) + return b, err +} + type sigs struct { v1.Image } From e509ec5ae5e010d6ffd384555287e66257f4b14a Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Fri, 1 Nov 2024 16:41:06 -0400 Subject: [PATCH 04/22] Refactor and use CheckOpts to build sigstore-go verification options Signed-off-by: Cody Soyland --- cmd/cosign/cli/options/verify.go | 2 +- cmd/cosign/cli/verify.go | 2 + cmd/cosign/cli/verify/verify.go | 1 + cmd/cosign/cli/verify/verify_attestation.go | 45 ++++++- cmd/cosign/cli/verify/verify_blob.go | 1 + .../cli/verify/verify_blob_attestation.go | 1 + pkg/cosign/verify.go | 127 +++++++++++++----- pkg/cosign/verify_bundle.go | 36 +++++ 8 files changed, 177 insertions(+), 38 deletions(-) create mode 100644 pkg/cosign/verify_bundle.go diff --git a/cmd/cosign/cli/options/verify.go b/cmd/cosign/cli/options/verify.go index b14f06626fb..570303a40e4 100644 --- a/cmd/cosign/cli/options/verify.go +++ b/cmd/cosign/cli/options/verify.go @@ -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 "+ diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index 3d360794d46..6ed33467b44 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -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, } @@ -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, } diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 434f2501003..4fb4e45c82d 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -143,6 +143,7 @@ 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, ExpectSigstoreBundle: c.ExpectSigstoreBundle, diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index c9e8164f216..b65cd99a4ad 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -119,21 +119,29 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e Identities: identities, Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, + UseSignedTimestamps: c.UseSignedTimestamps, MaxWorkers: c.MaxWorkers, ExpectSigstoreBundle: c.ExpectSigstoreBundle, } if c.CheckClaims { co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier } + + if c.ExpectSigstoreBundle { + if err = checkSigstoreBundleUnsupportedOptions(c); err != nil { + return err + } + } + // Ignore Signed Certificate Timestamp if the flag is set or a key is provided - if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) { + if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) && !c.ExpectSigstoreBundle { co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) if err != nil { return fmt.Errorf("getting ctlog public keys: %w", err) } } - if c.TSACertChainPath != "" || c.UseSignedTimestamps { + if c.TSACertChainPath != "" || c.UseSignedTimestamps && !c.ExpectSigstoreBundle { tsaCertificates, err := c.loadTSACertificates(ctx) if err != nil { return fmt.Errorf("unable to load TSA certificates: %w", err) @@ -143,7 +151,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts } - if !c.IgnoreTlog { + if !c.IgnoreTlog && !co.ExpectSigstoreBundle { if c.RekorURL != "" { rekorClient, err := rekor.NewClient(c.RekorURL) if err != nil { @@ -189,6 +197,10 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return fmt.Errorf("initializing piv token verifier: %w", err) } case c.CertRef != "": + if c.ExpectSigstoreBundle { + // This shouldn't happen because we already checked for this above in checkSigstoreBundleUnsupportedOptions + return fmt.Errorf("unsupported: certificate reference currently not supported with --expect-sigstore-bundle") + } cert, err := loadCertFromFileOrURL(c.CertRef) if err != nil { return fmt.Errorf("loading certificate from reference: %w", err) @@ -229,9 +241,14 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e if !c.ExpectSigstoreBundle { return fmt.Errorf("unsupported: trusted root path currently only supported with --expect-sigstore-bundle") } - co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath) - if err != nil { - return fmt.Errorf("loading trusted root: %w", err) + + // If a trusted root path is provided, we will use it to verify the bundle. + // Otherwise, the verifier will default to the public good instance. + if c.TrustedRootPath == "" { + co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath) + if err != nil { + return fmt.Errorf("creating trusted root from path: %w", err) + } } case c.CARoots != "": // CA roots + possible intermediates are already loaded into co.RootCerts with the call to @@ -337,3 +354,19 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return nil } + +func checkSigstoreBundleUnsupportedOptions(c *VerifyAttestationCommand) error { + if c.Cert != "" || c.CertRef != "" { + return fmt.Errorf("unsupported: certificate may not be provided using --cert when using --expect-sigstore-bundle (cert must be in bundle)") + } + if c.CertChain != "" { + return fmt.Errorf("unsupported: certificate chain may not be provided using --cert-chain when using --expect-sigstore-bundle (cert must be in bundle)") + } + if c.CARoots != "" || c.CAIntermediates != "" { + return fmt.Errorf("unsupported: CA roots/intermediates must be provided using --trusted-root when using --expect-sigstore-bundle") + } + if c.TSACertChainPath != "" { + return fmt.Errorf("unsupported: TSA certificate chain path may only be provided using --trusted-root when using --expect-sigstore-bundle") + } + return nil +} diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index 79475c90d80..1b063c95ef8 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -137,6 +137,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { Identities: identities, Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, + UseSignedTimestamps: c.UseSignedTimestamps, } if c.RFC3161TimestampPath != "" && !(c.TSACertChainPath != "" || c.UseSignedTimestamps) { return fmt.Errorf("either TSA certificate chain path must be provided or use-signed-timestamps must be set when using RFC3161 timestamp path") diff --git a/cmd/cosign/cli/verify/verify_blob_attestation.go b/cmd/cosign/cli/verify/verify_blob_attestation.go index 3f2c33cc63b..6b458a37a5e 100644 --- a/cmd/cosign/cli/verify/verify_blob_attestation.go +++ b/cmd/cosign/cli/verify/verify_blob_attestation.go @@ -123,6 +123,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st IgnoreSCT: c.IgnoreSCT, Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, + UseSignedTimestamps: c.UseSignedTimestamps, } var h v1.Hash if c.CheckClaims { diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 8b29c515d37..50a09534806 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -44,12 +44,15 @@ import ( "github.com/sigstore/cosign/v2/pkg/blob" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" + "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" + ocibundle "github.com/sigstore/cosign/v2/pkg/oci/bundle" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/pkg/types" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/verify" sgverify "github.com/sigstore/sigstore-go/pkg/verify" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" @@ -170,6 +173,9 @@ type CheckOpts struct { // IgnoreTlog skip tlog verification IgnoreTlog bool + // UseSignedTimestamps use signed timestamps if available + UseSignedTimestamps bool + // The amount of maximum workers for parallel executions. // Defaults to 10. MaxWorkers int @@ -184,9 +190,93 @@ type CheckOpts struct { // Currently, this is only applicable when ExpectSigstoreBundle is true. TrustedMaterial root.TrustedMaterial - // TODO: Add the following, deprecate overlapping fields - //CertificateIdentities verify.CertificateIdentities - //VerifierOptions []verify.VerifierOption + // VerifierOptions are the options to be passed to the verifier. + VerifierOptions []verify.VerifierOption + + // PolicyOptions are the policy options to be passed to the verifier. + PolicyOptions []verify.PolicyOption +} + +type verifyTrustedMaterial struct { + root.TrustedMaterial + keyTrustedMaterial root.TrustedMaterial +} + +func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstrainedVerifier, error) { + return v.keyTrustedMaterial.PublicKeyVerifier(hint) +} + +// SigstoreGoOptions returns the verification options for verifying with sigstore-go. +func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, verifierOptions []verify.VerifierOption, policyOptions []verify.PolicyOption, err error) { + var sanMatcher verify.SubjectAlternativeNameMatcher + var issuerMatcher verify.IssuerMatcher + + if len(co.Identities) > 0 { + if len(co.Identities) > 1 { + return nil, nil, nil, fmt.Errorf("unsupported: multiple identities are not supported at this time") + } + sanMatcher, err = verify.NewSANMatcher(co.Identities[0].Subject, co.Identities[0].SubjectRegExp) + if err != nil { + return nil, nil, nil, err + } + + issuerMatcher, err = verify.NewIssuerMatcher(co.Identities[0].Issuer, co.Identities[0].IssuerRegExp) + if err != nil { + return nil, nil, nil, err + } + } + + extensions := certificate.Extensions{ + GithubWorkflowTrigger: co.CertGithubWorkflowTrigger, + GithubWorkflowSHA: co.CertGithubWorkflowSha, + GithubWorkflowName: co.CertGithubWorkflowName, + GithubWorkflowRepository: co.CertGithubWorkflowRepository, + GithubWorkflowRef: co.CertGithubWorkflowRef, + } + + certificateIdentities, err := verify.NewCertificateIdentity(sanMatcher, issuerMatcher, extensions) + if err != nil { + return nil, nil, nil, err + } + + policyOptions = append(policyOptions, co.PolicyOptions...) + policyOptions = append(policyOptions, verify.WithCertificateIdentity(certificateIdentities)) + + // Wrap TrustedMaterial + vTrustedMaterial := &verifyTrustedMaterial{TrustedMaterial: co.TrustedMaterial} + + // If TrustedMaterial is not set, fetch it from TUF + if vTrustedMaterial.TrustedMaterial == nil { + vTrustedMaterial.TrustedMaterial, err = root.FetchTrustedRoot() + if err != nil { + return nil, nil, nil, err + } + } + + if co.SigVerifier != nil { + policyOptions = append(policyOptions, verify.WithKey()) + newExpiringKey := root.NewExpiringKey(co.SigVerifier, time.Time{}, time.Time{}) + vTrustedMaterial.keyTrustedMaterial = root.NewTrustedPublicKeyMaterial(func(_ string) (root.TimeConstrainedVerifier, error) { + return newExpiringKey, nil + }) + } + + // Make some educated guesses about verification policy + verifierOptions = append(verifierOptions, co.VerifierOptions...) + if !co.IgnoreTlog { + verifierOptions = append(verifierOptions, verify.WithTransparencyLog(1), verify.WithIntegratedTimestamps(1)) + } + if co.UseSignedTimestamps { + verifierOptions = append(verifierOptions, verify.WithSignedTimestamps(1)) + } + if !co.IgnoreSCT { + verifierOptions = append(verifierOptions, verify.WithSignedCertificateTimestamps(1)) + } + if co.IgnoreSCT && !co.UseSignedTimestamps { + verifierOptions = append(verifierOptions, verify.WithoutAnyObserverTimestampsUnsafe()) + } + + return vTrustedMaterial, verifierOptions, policyOptions, nil } // This is a substitutable signature verification function that can be used for verifying @@ -1463,12 +1553,7 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name return verifySignatures(ctx, sigs, h, co) } -func getBundles(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) ([]*sgbundle.Bundle, *v1.Hash, error) { - // Enforce this up front. - if co.TrustedMaterial == nil { - return nil, nil, errors.New("Sigstore bundle verification requires TrustedMaterial") - } - +func getBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ([]*sgbundle.Bundle, *v1.Hash, error) { // This is a carefully optimized sequence for fetching the signatures of the // entity that minimizes registry requests when supplied with a digest input digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...) @@ -1517,31 +1602,11 @@ func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef nam return nil, false, err } - // TODO: build verifierConfig from CheckOpts - verifierConfig := []sgverify.VerifierOption{ - sgverify.WithSignedCertificateTimestamps(1), - sgverify.WithTransparencyLog(1), - sgverify.WithIntegratedTimestamps(1), - } - - sev, err := sgverify.NewSignedEntityVerifier(co.TrustedMaterial, verifierConfig...) - if err != nil { - return nil, false, err - } - - policyOptions := make([]sgverify.PolicyOption, 0, len(co.Identities)) - for _, i := range co.Identities { - id, err := sgverify.NewShortCertificateIdentity(i.Issuer, i.IssuerRegExp, i.Subject, i.SubjectRegExp) - if err != nil { - return nil, false, err - } - policyOptions = append(policyOptions, sgverify.WithCertificateIdentity(id)) - } - policy := sgverify.NewPolicy(sgverify.WithArtifactDigest(hash.Algorithm, digestBytes), policyOptions...) + artifactPolicyOption := sgverify.WithArtifactDigest(hash.Algorithm, digestBytes) checkedSignatures = make([]oci.Signature, 0, len(bundles)) for _, bundle := range bundles { - _, err := sev.Verify(bundle, policy) + _, err := VerifyNewBundle(ctx, co, artifactPolicyOption, bundle) if err != nil { continue } diff --git a/pkg/cosign/verify_bundle.go b/pkg/cosign/verify_bundle.go new file mode 100644 index 00000000000..d4da34addae --- /dev/null +++ b/pkg/cosign/verify_bundle.go @@ -0,0 +1,36 @@ +// +// Copyright 2024 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/sigstore/sigstore-go/pkg/verify" + // sigs "github.com/sigstore/cosign/v2/pkg/signature" +) + +// VerifyNewBundle verifies a SigstoreBundle with the given parameters +func VerifyNewBundle(_ context.Context, co *CheckOpts, artifactPolicyOption verify.ArtifactPolicyOption, bundle verify.SignedEntity) (*verify.VerificationResult, error) { + trustedMaterial, verifierOptions, policyOptions, err := co.SigstoreGoOptions() + if err != nil { + return nil, err + } + verifier, err := verify.NewSignedEntityVerifier(trustedMaterial, verifierOptions...) + if err != nil { + return nil, err + } + return verifier.Verify(bundle, verify.NewPolicy(artifactPolicyOption, policyOptions...)) +} From 8b3edc99e30955e815823cce090569c182f2e770 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Mon, 4 Nov 2024 16:55:36 -0500 Subject: [PATCH 05/22] Remove accidentally-committed commented code Signed-off-by: Cody Soyland --- pkg/cosign/verify.go | 22 +++++++--------------- pkg/cosign/verify_bundle.go | 1 - 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 50a09534806..40b19e3e546 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -92,14 +92,6 @@ type Identity struct { SubjectRegExp string } -// type CertPool struct { -// certs []*x509.Certificate -// } - -// func (c *CertPool) AddCert(cert *x509.Certificate) { -// c.certs = append(c.certs, cert) -// } - // CheckOpts are the options for checking signatures. type CheckOpts struct { // RegistryClientOpts are the options for interacting with the container registry. @@ -190,11 +182,12 @@ type CheckOpts struct { // Currently, this is only applicable when ExpectSigstoreBundle is true. TrustedMaterial root.TrustedMaterial - // VerifierOptions are the options to be passed to the verifier. - VerifierOptions []verify.VerifierOption + // TODO: Add these to replace above fields? + // // VerifierOptions are the options to be passed to the verifier. + // VerifierOptions []verify.VerifierOption - // PolicyOptions are the policy options to be passed to the verifier. - PolicyOptions []verify.PolicyOption + // // PolicyOptions are the policy options to be passed to the verifier. + // PolicyOptions []verify.PolicyOption } type verifyTrustedMaterial struct { @@ -239,8 +232,7 @@ func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, return nil, nil, nil, err } - policyOptions = append(policyOptions, co.PolicyOptions...) - policyOptions = append(policyOptions, verify.WithCertificateIdentity(certificateIdentities)) + policyOptions = []sgverify.PolicyOption{verify.WithCertificateIdentity(certificateIdentities)} // Wrap TrustedMaterial vTrustedMaterial := &verifyTrustedMaterial{TrustedMaterial: co.TrustedMaterial} @@ -262,7 +254,7 @@ func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, } // Make some educated guesses about verification policy - verifierOptions = append(verifierOptions, co.VerifierOptions...) + verifierOptions = make([]sgverify.VerifierOption, 0) if !co.IgnoreTlog { verifierOptions = append(verifierOptions, verify.WithTransparencyLog(1), verify.WithIntegratedTimestamps(1)) } diff --git a/pkg/cosign/verify_bundle.go b/pkg/cosign/verify_bundle.go index d4da34addae..0ffb82cea18 100644 --- a/pkg/cosign/verify_bundle.go +++ b/pkg/cosign/verify_bundle.go @@ -19,7 +19,6 @@ import ( "context" "github.com/sigstore/sigstore-go/pkg/verify" - // sigs "github.com/sigstore/cosign/v2/pkg/signature" ) // VerifyNewBundle verifies a SigstoreBundle with the given parameters From b17dfd8ffb8ae65abb48bc149325dfbaf32f7732 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Tue, 5 Nov 2024 09:03:08 -0500 Subject: [PATCH 06/22] Add comment about missing output data Signed-off-by: Cody Soyland --- pkg/cosign/verify.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 40b19e3e546..993281ce737 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -1610,7 +1610,10 @@ func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef nam if err != nil { continue } + // TODO: Add additional data to oci.Signature (Cert, Rekor Bundle, Timestamp, etc) + // This can be done by passing a list of static.Option to NewAttestation (e.g. static.WithCertChain()) + // This depends on https://github.com/sigstore/sigstore-go/issues/328 sig, err := static.NewAttestation(payload) if err != nil { continue From 5ae6109290f2b64880f000634341c2c28ba318a2 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Tue, 5 Nov 2024 15:01:14 -0500 Subject: [PATCH 07/22] Add tests and fix a few verifier options Signed-off-by: Cody Soyland --- pkg/cosign/verify.go | 44 ++++++++++++++-------------- test/e2e_test.go | 68 +++++++++++++++++++++++++++----------------- 2 files changed, 65 insertions(+), 47 deletions(-) diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 993281ce737..53faa04ea47 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -201,10 +201,11 @@ func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstra // SigstoreGoOptions returns the verification options for verifying with sigstore-go. func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, verifierOptions []verify.VerifierOption, policyOptions []verify.PolicyOption, err error) { - var sanMatcher verify.SubjectAlternativeNameMatcher - var issuerMatcher verify.IssuerMatcher + policyOptions = make([]sgverify.PolicyOption, 0) if len(co.Identities) > 0 { + var sanMatcher verify.SubjectAlternativeNameMatcher + var issuerMatcher verify.IssuerMatcher if len(co.Identities) > 1 { return nil, nil, nil, fmt.Errorf("unsupported: multiple identities are not supported at this time") } @@ -217,23 +218,22 @@ func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, if err != nil { return nil, nil, nil, err } - } - extensions := certificate.Extensions{ - GithubWorkflowTrigger: co.CertGithubWorkflowTrigger, - GithubWorkflowSHA: co.CertGithubWorkflowSha, - GithubWorkflowName: co.CertGithubWorkflowName, - GithubWorkflowRepository: co.CertGithubWorkflowRepository, - GithubWorkflowRef: co.CertGithubWorkflowRef, - } + extensions := certificate.Extensions{ + GithubWorkflowTrigger: co.CertGithubWorkflowTrigger, + GithubWorkflowSHA: co.CertGithubWorkflowSha, + GithubWorkflowName: co.CertGithubWorkflowName, + GithubWorkflowRepository: co.CertGithubWorkflowRepository, + GithubWorkflowRef: co.CertGithubWorkflowRef, + } - certificateIdentities, err := verify.NewCertificateIdentity(sanMatcher, issuerMatcher, extensions) - if err != nil { - return nil, nil, nil, err + certificateIdentities, err := verify.NewCertificateIdentity(sanMatcher, issuerMatcher, extensions) + if err != nil { + return nil, nil, nil, err + } + policyOptions = []sgverify.PolicyOption{verify.WithCertificateIdentity(certificateIdentities)} } - policyOptions = []sgverify.PolicyOption{verify.WithCertificateIdentity(certificateIdentities)} - // Wrap TrustedMaterial vTrustedMaterial := &verifyTrustedMaterial{TrustedMaterial: co.TrustedMaterial} @@ -245,26 +245,28 @@ func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, } } + verifierOptions = make([]sgverify.VerifierOption, 0) + if co.SigVerifier != nil { + // We are verifying with a public key policyOptions = append(policyOptions, verify.WithKey()) newExpiringKey := root.NewExpiringKey(co.SigVerifier, time.Time{}, time.Time{}) vTrustedMaterial.keyTrustedMaterial = root.NewTrustedPublicKeyMaterial(func(_ string) (root.TimeConstrainedVerifier, error) { return newExpiringKey, nil }) + } else { + if !co.IgnoreSCT { + verifierOptions = append(verifierOptions, verify.WithSignedCertificateTimestamps(1)) + } } - // Make some educated guesses about verification policy - verifierOptions = make([]sgverify.VerifierOption, 0) if !co.IgnoreTlog { verifierOptions = append(verifierOptions, verify.WithTransparencyLog(1), verify.WithIntegratedTimestamps(1)) } if co.UseSignedTimestamps { verifierOptions = append(verifierOptions, verify.WithSignedTimestamps(1)) } - if !co.IgnoreSCT { - verifierOptions = append(verifierOptions, verify.WithSignedCertificateTimestamps(1)) - } - if co.IgnoreSCT && !co.UseSignedTimestamps { + if co.IgnoreTlog && !co.UseSignedTimestamps { verifierOptions = append(verifierOptions, verify.WithoutAnyObserverTimestampsUnsafe()) } diff --git a/test/e2e_test.go b/test/e2e_test.go index 598d7faa5c0..9b0a54bec63 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -277,12 +277,15 @@ func TestImportSignVerifyClean(t *testing.T) { } func TestAttestVerify(t *testing.T) { - attestVerify(t, - "slsaprovenance", - `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`, - `predicate: builder: id: "2"`, - `predicate: builder: id: "1"`, - ) + for _, newBundleFormat := range []bool{false, true} { + attestVerify(t, + newBundleFormat, + "slsaprovenance", + `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`, + `predicate: builder: id: "2"`, + `predicate: builder: id: "1"`, + ) + } } func TestAttestVerifySPDXJSON(t *testing.T) { @@ -290,12 +293,15 @@ func TestAttestVerifySPDXJSON(t *testing.T) { if err != nil { t.Fatal(err) } - attestVerify(t, - "spdxjson", - string(attestationBytes), - `predicate: spdxVersion: "SPDX-2.2"`, - `predicate: spdxVersion: "SPDX-9.9"`, - ) + for _, newBundleFormat := range []bool{false, true} { + attestVerify(t, + newBundleFormat, + "spdxjson", + string(attestationBytes), + `predicate: spdxVersion: "SPDX-2.2"`, + `predicate: spdxVersion: "SPDX-9.9"`, + ) + } } func TestAttestVerifyCycloneDXJSON(t *testing.T) { @@ -303,12 +309,15 @@ func TestAttestVerifyCycloneDXJSON(t *testing.T) { if err != nil { t.Fatal(err) } - attestVerify(t, - "cyclonedx", - string(attestationBytes), - `predicate: specVersion: "1.4"`, - `predicate: specVersion: "7.7"`, - ) + for _, newBundleFormat := range []bool{false, true} { + attestVerify(t, + newBundleFormat, + "cyclonedx", + string(attestationBytes), + `predicate: specVersion: "1.4"`, + `predicate: specVersion: "7.7"`, + ) + } } func TestAttestVerifyURI(t *testing.T) { @@ -316,15 +325,18 @@ func TestAttestVerifyURI(t *testing.T) { if err != nil { t.Fatal(err) } - attestVerify(t, - "https://example.com/TestResult/v1", - string(attestationBytes), - `predicate: passed: true`, - `predicate: passed: false"`, - ) + for _, newBundleFormat := range []bool{false, true} { + attestVerify(t, + newBundleFormat, + "https://example.com/TestResult/v1", + string(attestationBytes), + `predicate: passed: true`, + `predicate: passed: false"`, + ) + } } -func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue string) { +func attestVerify(t *testing.T, newBundleFormat bool, predicateType, attestation, goodCue, badCue string) { repo, stop := reg(t) defer stop() td := t.TempDir() @@ -353,6 +365,10 @@ func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue stri MaxWorkers: 10, } + if newBundleFormat { + verifyAttestation.ExpectSigstoreBundle = true + } + // Fail case when using without type and policy flag mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t) @@ -361,7 +377,7 @@ func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue stri } // Now attest the image - ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} + ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, NewBundleFormat: newBundleFormat} attestCmd := attest.AttestCommand{ KeyOpts: ko, PredicatePath: attestationPath, From 96410a74e8dc59ac60dd4133e2a4dec28f364468 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Tue, 5 Nov 2024 15:42:24 -0500 Subject: [PATCH 08/22] Remove incomplete code Signed-off-by: Cody Soyland --- pkg/cosign/verify.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 53faa04ea47..c2aabb80e74 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -46,7 +46,6 @@ import ( "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" - ocibundle "github.com/sigstore/cosign/v2/pkg/oci/bundle" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/pkg/types" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" @@ -954,24 +953,6 @@ func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name targetSig = []byte(sigRef) } - if co.ExpectSigstoreBundle { - var bundle *sgbundle.Bundle - bundle.Bundle = new(protobundle.Bundle) - - err = bundle.UnmarshalJSON(targetSig) - if err != nil { - return nil, err - } - signature, err := ocibundle.NewSignature(bundle, &ocibundle.Options{}) - if err != nil { - return nil, err - } - - return &fakeOCISignatures{ - signatures: []oci.Signature{signature}, - }, nil - } - _, err = base64.StdEncoding.DecodeString(string(targetSig)) if err == nil { From c5406897cad9aacdbd7bdd8aee5582a580db2f6a Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Wed, 6 Nov 2024 14:46:24 -0500 Subject: [PATCH 09/22] Clean up linter warnings Signed-off-by: Cody Soyland --- pkg/cosign/verify.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index c2aabb80e74..f4a0e2c389e 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -52,7 +52,6 @@ import ( sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/verify" - sgverify "github.com/sigstore/sigstore-go/pkg/verify" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/google/go-containerregistry/pkg/name" @@ -200,7 +199,7 @@ func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstra // SigstoreGoOptions returns the verification options for verifying with sigstore-go. func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, verifierOptions []verify.VerifierOption, policyOptions []verify.PolicyOption, err error) { - policyOptions = make([]sgverify.PolicyOption, 0) + policyOptions = make([]verify.PolicyOption, 0) if len(co.Identities) > 0 { var sanMatcher verify.SubjectAlternativeNameMatcher @@ -230,7 +229,7 @@ func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, if err != nil { return nil, nil, nil, err } - policyOptions = []sgverify.PolicyOption{verify.WithCertificateIdentity(certificateIdentities)} + policyOptions = []verify.PolicyOption{verify.WithCertificateIdentity(certificateIdentities)} } // Wrap TrustedMaterial @@ -244,7 +243,7 @@ func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, } } - verifierOptions = make([]sgverify.VerifierOption, 0) + verifierOptions = make([]verify.VerifierOption, 0) if co.SigVerifier != nil { // We are verifying with a public key @@ -253,7 +252,8 @@ func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, vTrustedMaterial.keyTrustedMaterial = root.NewTrustedPublicKeyMaterial(func(_ string) (root.TimeConstrainedVerifier, error) { return newExpiringKey, nil }) - } else { + } else { //nolint:gocritic + // We are verifiying with a certificate if !co.IgnoreSCT { verifierOptions = append(verifierOptions, verify.WithSignedCertificateTimestamps(1)) } @@ -1577,7 +1577,7 @@ func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef nam return nil, false, err } - artifactPolicyOption := sgverify.WithArtifactDigest(hash.Algorithm, digestBytes) + artifactPolicyOption := verify.WithArtifactDigest(hash.Algorithm, digestBytes) checkedSignatures = make([]oci.Signature, 0, len(bundles)) for _, bundle := range bundles { From 87969012680e1d78b27f5cb1ad292258fafd5ce5 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Wed, 6 Nov 2024 15:16:27 -0500 Subject: [PATCH 10/22] Update docs Signed-off-by: Cody Soyland --- doc/cosign_attest.md | 1 + doc/cosign_dockerfile_verify.md | 4 +++- doc/cosign_manifest_verify.md | 4 +++- doc/cosign_verify-attestation.md | 4 +++- doc/cosign_verify-blob-attestation.md | 5 +++-- doc/cosign_verify-blob.md | 5 +++-- doc/cosign_verify.md | 4 +++- 7 files changed, 19 insertions(+), 8 deletions(-) diff --git a/doc/cosign_attest.md b/doc/cosign_attest.md index 19e201291d6..18e7ec8a54d 100644 --- a/doc/cosign_attest.md +++ b/doc/cosign_attest.md @@ -57,6 +57,7 @@ cosign attest [flags] --insecure-skip-verify skip verifying fulcio published to the SCT (this should only be used for testing). --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --key string path to the private key file, KMS URI or Kubernetes Secret + --new-bundle-format attach a Sigstore bundle using OCI referrers API --no-upload do not upload the generated attestation --oidc-client-id string OIDC client ID for application (default "sigstore") --oidc-client-secret-file string Path to file containing OIDC client secret for application diff --git a/doc/cosign_dockerfile_verify.md b/doc/cosign_dockerfile_verify.md index aea4d4aadde..63bfa503ed2 100644 --- a/doc/cosign_dockerfile_verify.md +++ b/doc/cosign_dockerfile_verify.md @@ -69,6 +69,7 @@ cosign dockerfile verify [flags] --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) + --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log @@ -91,7 +92,8 @@ cosign dockerfile verify [flags] --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp - --use-signed-timestamps use signed timestamps if available + --trusted-root string Path to a Sigstore TrustedRoot JSON file. + --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands diff --git a/doc/cosign_manifest_verify.md b/doc/cosign_manifest_verify.md index dc1af148c48..647cfb1f176 100644 --- a/doc/cosign_manifest_verify.md +++ b/doc/cosign_manifest_verify.md @@ -63,6 +63,7 @@ cosign manifest verify [flags] --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) + --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log @@ -85,7 +86,8 @@ cosign manifest verify [flags] --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp - --use-signed-timestamps use signed timestamps if available + --trusted-root string Path to a Sigstore TrustedRoot JSON file. + --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands diff --git a/doc/cosign_verify-attestation.md b/doc/cosign_verify-attestation.md index 9b747ab9ef0..0f6bedb6284 100644 --- a/doc/cosign_verify-attestation.md +++ b/doc/cosign_verify-attestation.md @@ -73,6 +73,7 @@ cosign verify-attestation [flags] --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) + --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify-attestation --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log @@ -93,8 +94,9 @@ cosign verify-attestation [flags] --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp + --trusted-root string Path to a Sigstore TrustedRoot JSON file. --type string specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI (default "custom") - --use-signed-timestamps use signed timestamps if available + --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands diff --git a/doc/cosign_verify-blob-attestation.md b/doc/cosign_verify-blob-attestation.md index a7c293359d6..20fbdc30939 100644 --- a/doc/cosign_verify-blob-attestation.md +++ b/doc/cosign_verify-blob-attestation.md @@ -43,6 +43,7 @@ cosign verify-blob-attestation [flags] --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims 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. (default true) + --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify-blob-attestation --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log @@ -59,9 +60,9 @@ cosign verify-blob-attestation [flags] --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp - --trusted-root string path to trusted root FILE + --trusted-root string Path to a Sigstore TrustedRoot JSON file. --type string specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI (default "custom") - --use-signed-timestamps use signed timestamps if available + --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md index 92655cccbed..288d2fd63d6 100644 --- a/doc/cosign_verify-blob.md +++ b/doc/cosign_verify-blob.md @@ -78,6 +78,7 @@ cosign verify-blob [flags] --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. + --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify-blob --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log @@ -94,8 +95,8 @@ cosign verify-blob [flags] --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp - --trusted-root string path to trusted root FILE - --use-signed-timestamps use signed timestamps if available + --trusted-root string Path to a Sigstore TrustedRoot JSON file. + --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md index 0852f21fc81..eda284a101e 100644 --- a/doc/cosign_verify.md +++ b/doc/cosign_verify.md @@ -90,6 +90,7 @@ cosign verify [flags] --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) + --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log @@ -112,7 +113,8 @@ cosign verify [flags] --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp - --use-signed-timestamps use signed timestamps if available + --trusted-root string Path to a Sigstore TrustedRoot JSON file. + --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands From 67d421a33b5fd45cbee57160cdeebd7be260d6aa Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Wed, 6 Nov 2024 16:00:07 -0500 Subject: [PATCH 11/22] Use ParsePredicateType helper here Signed-off-by: Cody Soyland --- pkg/policy/attestation.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/policy/attestation.go b/pkg/policy/attestation.go index 1fa82bf8a6f..c8f13f8c535 100644 --- a/pkg/policy/attestation.go +++ b/pkg/policy/attestation.go @@ -60,10 +60,9 @@ func AttestationToPayloadJSON(_ context.Context, predicateType string, verifiedA if predicateType == "" { return nil, "", errors.New("missing predicate type") } - predicateURI, ok := options.PredicateTypeMap[predicateType] - if !ok { - // Not a custom one, use it as is. - predicateURI = predicateType + predicateURI, err := options.ParsePredicateType(predicateType) + if err != nil { + return nil, "", err } var payloadData map[string]interface{} From ba5c7cb9f0eba0f71bdf2fbc5e9ef71bdb221922 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Wed, 6 Nov 2024 16:01:36 -0500 Subject: [PATCH 12/22] Add missing annotations Signed-off-by: Cody Soyland --- cmd/cosign/cli/attest/attest.go | 2 +- pkg/oci/remote/write.go | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index 528481a0fbb..b51e33a5542 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -246,7 +246,7 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { if err != nil { return err } - return ociremote.WriteAttestationNewBundleFormat(digest.Repository, bundleBytes, ociremoteOpts...) + return ociremote.WriteAttestationNewBundleFormat(digest.Repository, bundleBytes, predicateType, ociremoteOpts...) } // We don't actually need to access the remote entity to attach things to it diff --git a/pkg/oci/remote/write.go b/pkg/oci/remote/write.go index efa8f5f9b71..90a6a46f935 100644 --- a/pkg/oci/remote/write.go +++ b/pkg/oci/remote/write.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "os" + "time" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -223,7 +224,7 @@ func (taggable taggableManifest) MediaType() (types.MediaType, error) { return taggable.mediaType, nil } -func WriteAttestationNewBundleFormat(d name.Repository, bundleBytes []byte, opts ...Option) error { +func WriteAttestationNewBundleFormat(d name.Repository, bundleBytes []byte, predicateType string, opts ...Option) error { o := makeOptions(d, opts...) signTarget := d.String() @@ -296,8 +297,11 @@ func WriteAttestationNewBundleFormat(d name.Repository, bundleBytes []byte, opts Digest: desc.Digest, Size: desc.Size, }, - // TODO: Add annotations org.opencontainers.image.created, dev.sigstore.bundle.content, and dev.sigstore.bundle.predicateType - // See https://github.com/sigstore/cosign/blob/main/specs/BUNDLE_SPEC.md + Annotations: map[string]string{ + "org.opencontainers.image.created": time.Now().UTC().Format(time.RFC3339), + "dev.sigstore.bundle.content": "dsse-envelope", + "dev.sigstore.bundle.predicateType": predicateType, + }, }, bundleMediaType} targetRef, err := manifest.targetRef(d) From 7a4c4df792f58a491b3872c49a4919c02c21b8ec Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Wed, 6 Nov 2024 16:02:14 -0500 Subject: [PATCH 13/22] Filter by bundle artifact type Signed-off-by: Cody Soyland --- pkg/cosign/verify.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index f4a0e2c389e..8b58af5c674 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -1551,6 +1551,12 @@ func getBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ( } var bundles = make([]*sgbundle.Bundle, 0, len(index.Manifests)) for _, result := range index.Manifests { + if !strings.HasPrefix(result.ArtifactType, "application/vnd.dev.sigstore.bundle") { + continue + } + // TODO: We could filter by PredicateType here, but we'd need to thread + // the predicate type from the CLI flag to this function (we could add + // it to CheckOpts perhaps?) st, err := name.ParseReference(fmt.Sprintf("%s@%s", digest.Repository, result.Digest.String())) if err != nil { return nil, nil, err From 693b391254843c913829b52df3db12de305f0914 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Thu, 7 Nov 2024 16:56:18 -0500 Subject: [PATCH 14/22] Fix up MediaType logic Signed-off-by: Cody Soyland --- pkg/cosign/verify.go | 98 +++++++++++++++++++++++++----------- pkg/oci/remote/signatures.go | 19 ++++++- 2 files changed, 86 insertions(+), 31 deletions(-) diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 8b58af5c674..c2d3c6efea2 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -1551,19 +1551,15 @@ func getBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ( } var bundles = make([]*sgbundle.Bundle, 0, len(index.Manifests)) for _, result := range index.Manifests { - if !strings.HasPrefix(result.ArtifactType, "application/vnd.dev.sigstore.bundle") { - continue - } - // TODO: We could filter by PredicateType here, but we'd need to thread - // the predicate type from the CLI flag to this function (we could add - // it to CheckOpts perhaps?) st, err := name.ParseReference(fmt.Sprintf("%s@%s", digest.Repository, result.Digest.String())) if err != nil { return nil, nil, err } bundle, err := ociremote.Bundle(st, co.RegistryClientOpts...) if err != nil { - return nil, nil, err + // There may be non-Sigstore referrers in the index, so we can ignore them. + // TODO: Should we surface any errors here (e.g. if the bundle is invalid)? + continue } bundles = append(bundles, bundle) } @@ -1572,7 +1568,7 @@ func getBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ( } // verifyImageAttestationsSigstoreBundle verifies attestations from attached sigstore bundles -func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { +func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { bundles, hash, err := getBundles(ctx, signedImgRef, co) if err != nil { return nil, false, err @@ -1585,30 +1581,74 @@ func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef nam artifactPolicyOption := verify.WithArtifactDigest(hash.Algorithm, digestBytes) - checkedSignatures = make([]oci.Signature, 0, len(bundles)) - for _, bundle := range bundles { - _, err := VerifyNewBundle(ctx, co, artifactPolicyOption, bundle) - if err != nil { - continue - } - dsse, ok := bundle.Content.(*protobundle.Bundle_DsseEnvelope) - if !ok { - continue + attestations := make([]oci.Signature, len(bundles)) + bundlesVerified := make([]bool, len(bundles)) + + workers := co.MaxWorkers + if co.MaxWorkers == 0 { + workers = cosign.DefaultMaxWorkers + } + t := throttler.New(workers, len(bundles)) + for i, bundle := range bundles { + go func(bundle *sgbundle.Bundle, index int) { + var att oci.Signature + if err := func(bundle *sgbundle.Bundle) error { + _, err := VerifyNewBundle(ctx, co, artifactPolicyOption, bundle) + if err != nil { + return err + } + dsse, ok := bundle.Content.(*protobundle.Bundle_DsseEnvelope) + if !ok { + return errors.New("bundle does not contain a DSSE envelope") + } + payload, err := json.Marshal(dsse.DsseEnvelope) + if err != nil { + return fmt.Errorf("marshaling DSSE envelope: %w", err) + } + + // TODO: Add additional data to oci.Signature (Cert, Rekor Bundle, Timestamp, etc) + // This can be done by passing a list of static.Option to NewAttestation (e.g. static.WithCertChain()) + // This depends on https://github.com/sigstore/sigstore-go/issues/328 + att, err = static.NewAttestation(payload) + if err != nil { + return err + } + bundlesVerified[index] = true + + return err + }(bundle); err != nil { + t.Done(err) + return + } + + attestations[index] = att + t.Done(nil) + }(bundle, i) + + // wait till workers are available + t.Throttle() + } + + for _, a := range attestations { + if a != nil { + checkedAttestations = append(checkedAttestations, a) } - payload, err := json.Marshal(dsse.DsseEnvelope) - if err != nil { - continue + } + + for _, verified := range bundlesVerified { + bundleVerified = bundleVerified || verified + } + + if len(checkedAttestations) == 0 { + var combinedErrors []string + for _, err := range t.Errs() { + combinedErrors = append(combinedErrors, err.Error()) } - // TODO: Add additional data to oci.Signature (Cert, Rekor Bundle, Timestamp, etc) - // This can be done by passing a list of static.Option to NewAttestation (e.g. static.WithCertChain()) - // This depends on https://github.com/sigstore/sigstore-go/issues/328 - sig, err := static.NewAttestation(payload) - if err != nil { - continue + return nil, false, &ErrNoMatchingAttestations{ + fmt.Errorf("no matching attestations: %s", strings.Join(combinedErrors, "\n ")), } - checkedSignatures = append(checkedSignatures, sig) - bundleVerified = true } - return checkedSignatures, bundleVerified, nil + + return checkedAttestations, bundleVerified, nil } diff --git a/pkg/oci/remote/signatures.go b/pkg/oci/remote/signatures.go index 279d3c257d5..be29c5beab4 100644 --- a/pkg/oci/remote/signatures.go +++ b/pkg/oci/remote/signatures.go @@ -19,6 +19,7 @@ import ( "errors" "io" "net/http" + "strings" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -52,14 +53,28 @@ func Signatures(ref name.Reference, opts ...Option) (oci.Signatures, error) { } func Bundle(ref name.Reference, opts ...Option) (*sgbundle.Bundle, error) { - signatures, err := Signatures(ref, opts...) + o := makeOptions(ref.Context(), opts...) + img, err := remoteImage(ref, o.ROpt...) if err != nil { return nil, err } - layers, err := signatures.(*sigs).Image.Layers() + // TODO: We can check for a specific predicate type here using + // img.Manifest() and looking at the annotations. To do so, we would need + // to thread the CLI flag through to here. + layers, err := img.Layers() if err != nil { return nil, err } + if len(layers) != 1 { + return nil, errors.New("expected exactly one layer") + } + mediaType, err := layers[0].MediaType() + if err != nil { + return nil, err + } + if !strings.HasPrefix(string(mediaType), "application/vnd.dev.sigstore.bundle") { + return nil, errors.New("expected bundle layer") + } layer0, err := layers[0].Uncompressed() if err != nil { return nil, err From a380b89ef0d983a9f90f64e33cd5db15e1fcc51e Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Tue, 19 Nov 2024 11:17:36 -0500 Subject: [PATCH 15/22] Revert "Use ParsePredicateType helper here" This reverts commit 67d421a33b5fd45cbee57160cdeebd7be260d6aa. Signed-off-by: Cody Soyland --- pkg/policy/attestation.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/policy/attestation.go b/pkg/policy/attestation.go index c8f13f8c535..1fa82bf8a6f 100644 --- a/pkg/policy/attestation.go +++ b/pkg/policy/attestation.go @@ -60,9 +60,10 @@ func AttestationToPayloadJSON(_ context.Context, predicateType string, verifiedA if predicateType == "" { return nil, "", errors.New("missing predicate type") } - predicateURI, err := options.ParsePredicateType(predicateType) - if err != nil { - return nil, "", err + predicateURI, ok := options.PredicateTypeMap[predicateType] + if !ok { + // Not a custom one, use it as is. + predicateURI = predicateType } var payloadData map[string]interface{} From 189e0d535b31f8cd5e9389be75fb0f438d4be744 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Tue, 19 Nov 2024 11:45:15 -0500 Subject: [PATCH 16/22] Rename flag to NewBundleFormat for consistency Signed-off-by: Cody Soyland --- cmd/cosign/cli/options/certificate.go | 4 ++-- cmd/cosign/cli/verify/verify.go | 2 +- cmd/cosign/cli/verify/verify_attestation.go | 14 +++++++------- pkg/cosign/verify.go | 8 ++++---- test/e2e_test.go | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cmd/cosign/cli/options/certificate.go b/cmd/cosign/cli/options/certificate.go index d894380a212..a11e98364a4 100644 --- a/cmd/cosign/cli/options/certificate.go +++ b/cmd/cosign/cli/options/certificate.go @@ -38,7 +38,7 @@ type CertVerifyOptions struct { CertChain string SCT string IgnoreSCT bool - ExpectSigstoreBundle bool + NewBundleFormat bool TrustedRootPath string } @@ -106,7 +106,7 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { "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.ExpectSigstoreBundle, "expect-sigstore-bundle", false, "expect the signature/attestation to be packaged in a Sigstore bundle") + 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) { diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 4fb4e45c82d..04f26724510 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -146,7 +146,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { UseSignedTimestamps: c.UseSignedTimestamps, MaxWorkers: c.MaxWorkers, ExperimentalOCI11: c.ExperimentalOCI11, - ExpectSigstoreBundle: c.ExpectSigstoreBundle, + NewBundleFormat: c.NewBundleFormat, } if c.TrustedRootPath != "" { diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index b65cd99a4ad..a4b0f520f51 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -121,27 +121,27 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e IgnoreTlog: c.IgnoreTlog, UseSignedTimestamps: c.UseSignedTimestamps, MaxWorkers: c.MaxWorkers, - ExpectSigstoreBundle: c.ExpectSigstoreBundle, + NewBundleFormat: c.NewBundleFormat, } if c.CheckClaims { co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier } - if c.ExpectSigstoreBundle { + if c.NewBundleFormat { if err = checkSigstoreBundleUnsupportedOptions(c); err != nil { return err } } // Ignore Signed Certificate Timestamp if the flag is set or a key is provided - if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) && !c.ExpectSigstoreBundle { + if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) && !c.NewBundleFormat { co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) if err != nil { return fmt.Errorf("getting ctlog public keys: %w", err) } } - if c.TSACertChainPath != "" || c.UseSignedTimestamps && !c.ExpectSigstoreBundle { + if c.TSACertChainPath != "" || c.UseSignedTimestamps && !c.NewBundleFormat { tsaCertificates, err := c.loadTSACertificates(ctx) if err != nil { return fmt.Errorf("unable to load TSA certificates: %w", err) @@ -151,7 +151,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts } - if !c.IgnoreTlog && !co.ExpectSigstoreBundle { + if !c.IgnoreTlog && !co.NewBundleFormat { if c.RekorURL != "" { rekorClient, err := rekor.NewClient(c.RekorURL) if err != nil { @@ -197,7 +197,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return fmt.Errorf("initializing piv token verifier: %w", err) } case c.CertRef != "": - if c.ExpectSigstoreBundle { + if c.NewBundleFormat { // This shouldn't happen because we already checked for this above in checkSigstoreBundleUnsupportedOptions return fmt.Errorf("unsupported: certificate reference currently not supported with --expect-sigstore-bundle") } @@ -238,7 +238,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e co.SCT = sct } case c.TrustedRootPath != "": - if !c.ExpectSigstoreBundle { + if !c.NewBundleFormat { return fmt.Errorf("unsupported: trusted root path currently only supported with --expect-sigstore-bundle") } diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index c2d3c6efea2..7988e35cda5 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -174,10 +174,10 @@ type CheckOpts struct { // Defaults to false. ExperimentalOCI11 bool - ExpectSigstoreBundle bool + NewBundleFormat bool // TrustedMaterial is the trusted material to use for verification. - // Currently, this is only applicable when ExpectSigstoreBundle is true. + // Currently, this is only applicable when NewBundleFormat is true. TrustedMaterial root.TrustedMaterial // TODO: Add these to replace above fields? @@ -602,7 +602,7 @@ func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co } } - if co.ExpectSigstoreBundle { + if co.NewBundleFormat { return nil, false, errors.New("bundle support for image signatures is not yet implemented") } @@ -994,7 +994,7 @@ func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, c if co.RootCerts == nil && co.SigVerifier == nil && co.TrustedMaterial == nil { return nil, false, errors.New("one of verifier, root certs, or TrustedMaterial is required") } - if co.ExpectSigstoreBundle { + if co.NewBundleFormat { return verifyImageAttestationsSigstoreBundle(ctx, signedImgRef, co) } diff --git a/test/e2e_test.go b/test/e2e_test.go index 9b0a54bec63..7ec814da58b 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -366,7 +366,7 @@ func attestVerify(t *testing.T, newBundleFormat bool, predicateType, attestation } if newBundleFormat { - verifyAttestation.ExpectSigstoreBundle = true + verifyAttestation.NewBundleFormat = true } // Fail case when using without type and policy flag From ac2935565a8d329dc0817b4d62ae8666f65fd8b5 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Thu, 21 Nov 2024 16:27:06 -0500 Subject: [PATCH 17/22] Add unit tests for cosign.VerifyNewBundle Signed-off-by: Cody Soyland --- go.mod | 2 +- go.sum | 4 +- pkg/cosign/verify_bundle_test.go | 249 +++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 pkg/cosign/verify_bundle_test.go diff --git a/go.mod b/go.mod index 0257a546a4b..c5756487303 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/sigstore/protobuf-specs v0.3.2 github.com/sigstore/rekor v1.3.6 github.com/sigstore/sigstore v1.8.9 - github.com/sigstore/sigstore-go v0.6.1 + github.com/sigstore/sigstore-go v0.6.2 github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8 github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.8 github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8 diff --git a/go.sum b/go.sum index b1cafadcba3..9bfda949ac2 100644 --- a/go.sum +++ b/go.sum @@ -616,8 +616,8 @@ github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= github.com/sigstore/sigstore v1.8.9 h1:NiUZIVWywgYuVTxXmRoTT4O4QAGiTEKup4N1wdxFadk= github.com/sigstore/sigstore v1.8.9/go.mod h1:d9ZAbNDs8JJfxJrYmulaTazU3Pwr8uLL9+mii4BNR3w= -github.com/sigstore/sigstore-go v0.6.1 h1:tGkkv1oDIER+QYU5MrjqlttQOVDWfSkmYwMqkJhB/cg= -github.com/sigstore/sigstore-go v0.6.1/go.mod h1:Xe5GHmUeACRFbomUWzVkf/xYCn8xVifb9DgqJrV2dIw= +github.com/sigstore/sigstore-go v0.6.2 h1:8uiywjt73vzfrGfWYVwVsiB1E1Qmwmpgr1kVpl4fs6A= +github.com/sigstore/sigstore-go v0.6.2/go.mod h1:pOIUH7Jx+ctwMICo+2zNrViOJJN5sGaQgwX4yAVJkA0= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8 h1:2zHmUvaYCwV6LVeTo+OAkTm8ykOGzA9uFlAjwDPAUWM= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8/go.mod h1:OEhheBplZinUsm7W9BupafztVZV3ldkAxEHbpAeC0Pk= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.8 h1:RKk4Z+qMaLORUdT7zntwMqKiYAej1VQlCswg0S7xNSY= diff --git a/pkg/cosign/verify_bundle_test.go b/pkg/cosign/verify_bundle_test.go new file mode 100644 index 00000000000..3f631672bc0 --- /dev/null +++ b/pkg/cosign/verify_bundle_test.go @@ -0,0 +1,249 @@ +// Copyright 2023 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_test + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "testing" + + "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/sigstore-go/pkg/testing/ca" + "github.com/sigstore/sigstore-go/pkg/tlog" + "github.com/sigstore/sigstore-go/pkg/verify" + "github.com/stretchr/testify/assert" +) + +type bundleMutator struct { + verify.SignedEntity + + eraseTSA bool + eraseTlog bool +} + +func (b *bundleMutator) Timestamps() ([][]byte, error) { + if b.eraseTSA { + return [][]byte{}, nil + } + return b.SignedEntity.Timestamps() +} + +func (b *bundleMutator) TlogEntries() ([]*tlog.Entry, error) { + if b.eraseTlog { + return []*tlog.Entry{}, nil + } + return b.SignedEntity.TlogEntries() +} + +func TestVerifyBundleAttestation(t *testing.T) { + virtualSigstore, err := ca.NewVirtualSigstore() + assert.NoError(t, err) + virtualSigstore2, err := ca.NewVirtualSigstore() // for testing invalid trusted material + assert.NoError(t, err) + + artifact := []byte("artifact") + digest := sha256.Sum256(artifact) + digestHex := hex.EncodeToString(digest[:]) + statementFmt := `{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"https://example.com/predicateType","subject":[{"name":"subject","digest":{"sha256":"%s"}}],"predicate":{}}` + statementCorrect := []byte(fmt.Sprintf(statementFmt, digestHex)) + + identity := "foo@example.com" + issuer := "example issuer" + standardIdentities := []cosign.Identity{ + { + Issuer: issuer, + Subject: identity, + }, + } + + entity, err := virtualSigstore.Attest(identity, issuer, statementCorrect) + if err != nil { + t.Fatal(err) + } + + for _, tc := range []struct { + name string + checkOpts *cosign.CheckOpts + artifactPolicyOption verify.ArtifactPolicyOption + entity verify.SignedEntity + wantErr bool + }{ + { + name: "valid", + checkOpts: &cosign.CheckOpts{ + Identities: standardIdentities, + IgnoreSCT: true, + UseSignedTimestamps: true, + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: entity, + wantErr: false, + }, + { + name: "invalid, wrong artifact", + checkOpts: &cosign.CheckOpts{ + Identities: standardIdentities, + IgnoreSCT: true, + UseSignedTimestamps: true, + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader([]byte("not the artifact"))), + entity: entity, + wantErr: true, + }, + { + name: "valid, pattern match issuer", + checkOpts: &cosign.CheckOpts{ + Identities: []cosign.Identity{ + { + IssuerRegExp: ".*issuer", + Subject: "foo@example.com", + }, + }, + IgnoreSCT: true, + UseSignedTimestamps: true, + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: entity, + wantErr: false, + }, + { + name: "valid, pattern match subject", + checkOpts: &cosign.CheckOpts{ + Identities: []cosign.Identity{ + { + Issuer: "example issuer", + SubjectRegExp: ".*@example.com", + }, + }, + IgnoreSCT: true, + UseSignedTimestamps: true, + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: entity, + wantErr: false, + }, + { + name: "invalid, pattern match issuer", + checkOpts: &cosign.CheckOpts{ + Identities: []cosign.Identity{ + { + IssuerRegExp: ".* not my issuer", + Subject: "foo@example.com", + }, + }, + IgnoreSCT: true, + UseSignedTimestamps: true, + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: entity, + wantErr: true, + }, + { + name: "invalid, pattern match subject", + checkOpts: &cosign.CheckOpts{ + Identities: []cosign.Identity{ + { + Issuer: "example issuer", + SubjectRegExp: ".*@otherexample.com", + }, + }, + IgnoreSCT: true, + UseSignedTimestamps: true, + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: entity, + wantErr: true, + }, + { + name: "invalid trusted material", + checkOpts: &cosign.CheckOpts{ + Identities: standardIdentities, + IgnoreSCT: true, + TrustedMaterial: virtualSigstore2, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: entity, + wantErr: true, + }, + { + name: "do not require tlog, missing tlog", + checkOpts: &cosign.CheckOpts{ + Identities: standardIdentities, + IgnoreSCT: true, + IgnoreTlog: true, + UseSignedTimestamps: true, + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: &bundleMutator{SignedEntity: entity, eraseTlog: true}, + wantErr: false, + }, + { + name: "do not require tsa, missing tsa", + checkOpts: &cosign.CheckOpts{ + Identities: standardIdentities, + IgnoreSCT: true, + IgnoreTlog: false, + UseSignedTimestamps: false, + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: &bundleMutator{SignedEntity: entity, eraseTSA: true}, + wantErr: false, + }, + { + name: "require tlog, missing tlog", + checkOpts: &cosign.CheckOpts{ + Identities: standardIdentities, + IgnoreSCT: true, + UseSignedTimestamps: true, + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: &bundleMutator{SignedEntity: entity, eraseTlog: true}, + wantErr: true, + }, + { + name: "require tsa, missing tsa", + checkOpts: &cosign.CheckOpts{ + Identities: standardIdentities, + IgnoreSCT: true, + UseSignedTimestamps: true, + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: &bundleMutator{SignedEntity: entity, eraseTSA: true}, + wantErr: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + _, err = cosign.VerifyNewBundle(context.Background(), tc.checkOpts, tc.artifactPolicyOption, tc.entity) + if tc.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} From 2f3b3211d23889913d26133182054f7dc28f2ff4 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Thu, 21 Nov 2024 16:59:21 -0500 Subject: [PATCH 18/22] Remove redundant new-bundle-format flags Signed-off-by: Cody Soyland --- cmd/cosign/cli/options/verify.go | 16 +++------------- cmd/cosign/cli/verify.go | 4 ++-- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/cmd/cosign/cli/options/verify.go b/cmd/cosign/cli/options/verify.go index 570303a40e4..2502bd785c2 100644 --- a/cmd/cosign/cli/options/verify.go +++ b/cmd/cosign/cli/options/verify.go @@ -158,10 +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 + Key string + Signature string + BundlePath string SecurityKey SecurityKeyOptions CertVerify CertVerifyOptions @@ -189,10 +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.RFC3161TimestampPath, "rfc3161-timestamp", "", "path to RFC3161 timestamp FILE") } @@ -218,7 +213,6 @@ type VerifyBlobAttestationOptions struct { Key string SignaturePath string BundlePath string - NewBundleFormat bool TrustedRootPath string PredicateOptions @@ -251,10 +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().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.") diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index 6ed33467b44..1ce85817f99 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -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, } @@ -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, } From 524f558f20893682c62d6e10983ebcc7e75337df Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Thu, 21 Nov 2024 17:00:36 -0500 Subject: [PATCH 19/22] Update docs Signed-off-by: Cody Soyland --- doc/cosign_dockerfile_verify.md | 2 +- doc/cosign_manifest_verify.md | 2 +- doc/cosign_verify-attestation.md | 2 +- doc/cosign_verify-blob-attestation.md | 3 +-- doc/cosign_verify-blob.md | 3 +-- doc/cosign_verify.md | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/doc/cosign_dockerfile_verify.md b/doc/cosign_dockerfile_verify.md index 63bfa503ed2..0b0cade8a62 100644 --- a/doc/cosign_dockerfile_verify.md +++ b/doc/cosign_dockerfile_verify.md @@ -69,7 +69,6 @@ cosign dockerfile verify [flags] --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) - --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log @@ -78,6 +77,7 @@ cosign dockerfile verify [flags] --key string path to the public key file, KMS URI or Kubernetes Secret --local-image whether the specified image is a path to an image saved locally via 'cosign save' --max-workers int the amount of maximum workers for parallel executions (default 10) + --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification -o, --output string output format for the signing image information (json|text) (default "json") --payload string payload path or remote URL diff --git a/doc/cosign_manifest_verify.md b/doc/cosign_manifest_verify.md index 647cfb1f176..b003a47596d 100644 --- a/doc/cosign_manifest_verify.md +++ b/doc/cosign_manifest_verify.md @@ -63,7 +63,6 @@ cosign manifest verify [flags] --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) - --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log @@ -72,6 +71,7 @@ cosign manifest verify [flags] --key string path to the public key file, KMS URI or Kubernetes Secret --local-image whether the specified image is a path to an image saved locally via 'cosign save' --max-workers int the amount of maximum workers for parallel executions (default 10) + --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification -o, --output string output format for the signing image information (json|text) (default "json") --payload string payload path or remote URL diff --git a/doc/cosign_verify-attestation.md b/doc/cosign_verify-attestation.md index 0f6bedb6284..0c598cb424f 100644 --- a/doc/cosign_verify-attestation.md +++ b/doc/cosign_verify-attestation.md @@ -73,7 +73,6 @@ cosign verify-attestation [flags] --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) - --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify-attestation --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log @@ -82,6 +81,7 @@ cosign verify-attestation [flags] --key string path to the public key file, KMS URI or Kubernetes Secret --local-image whether the specified image is a path to an image saved locally via 'cosign save' --max-workers int the amount of maximum workers for parallel executions (default 10) + --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification -o, --output string output format for the signing image information (json|text) (default "json") --policy strings specify CUE or Rego files with policies to be used for validation diff --git a/doc/cosign_verify-blob-attestation.md b/doc/cosign_verify-blob-attestation.md index 20fbdc30939..7e29e375a91 100644 --- a/doc/cosign_verify-blob-attestation.md +++ b/doc/cosign_verify-blob-attestation.md @@ -43,14 +43,13 @@ cosign verify-blob-attestation [flags] --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims 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. (default true) - --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify-blob-attestation --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log --insecure-ignore-tlog ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log --key string path to the public key file, KMS URI or Kubernetes Secret --max-workers int the amount of maximum workers for parallel executions (default 10) - --new-bundle-format output bundle in new format that contains all verification material + --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification --private-infrastructure skip transparency log verification when verifying artifacts in a privately deployed infrastructure --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md index 288d2fd63d6..ff357443fe7 100644 --- a/doc/cosign_verify-blob.md +++ b/doc/cosign_verify-blob.md @@ -78,14 +78,13 @@ cosign verify-blob [flags] --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. - --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify-blob --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log --insecure-ignore-tlog ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log --key string path to the public key file, KMS URI or Kubernetes Secret --max-workers int the amount of maximum workers for parallel executions (default 10) - --new-bundle-format output bundle in new format that contains all verification material + --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification --private-infrastructure skip transparency log verification when verifying artifacts in a privately deployed infrastructure --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md index eda284a101e..ff9a6a03a68 100644 --- a/doc/cosign_verify.md +++ b/doc/cosign_verify.md @@ -90,7 +90,6 @@ cosign verify [flags] --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) - --expect-sigstore-bundle expect the signature/attestation to be packaged in a Sigstore bundle --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log @@ -99,6 +98,7 @@ cosign verify [flags] --key string path to the public key file, KMS URI or Kubernetes Secret --local-image whether the specified image is a path to an image saved locally via 'cosign save' --max-workers int the amount of maximum workers for parallel executions (default 10) + --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification -o, --output string output format for the signing image information (json|text) (default "json") --payload string payload path or remote URL From 5c1fd5a8b62d07780c11c66f4e88742322ed9dba Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Fri, 22 Nov 2024 15:07:30 -0500 Subject: [PATCH 20/22] This TODO is not needed: go-containerregistry supports tag fallback scheme natively Signed-off-by: Cody Soyland --- pkg/oci/remote/write.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/oci/remote/write.go b/pkg/oci/remote/write.go index 90a6a46f935..7ab96ed0408 100644 --- a/pkg/oci/remote/write.go +++ b/pkg/oci/remote/write.go @@ -313,8 +313,6 @@ func WriteAttestationNewBundleFormat(d name.Repository, bundleBytes []byte, pred return fmt.Errorf("failed to upload manifest: %w", err) } - // TODO: add support for tag fallback scheme for non-compliant registries - return nil } From 4d41f5f4c4280e2df2340cdba97a8cd19504574c Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Fri, 22 Nov 2024 15:10:40 -0500 Subject: [PATCH 21/22] Update flag name Signed-off-by: Cody Soyland --- cmd/cosign/cli/verify/verify_attestation.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index a4b0f520f51..c7b65c0f0eb 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -199,7 +199,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e case c.CertRef != "": if c.NewBundleFormat { // This shouldn't happen because we already checked for this above in checkSigstoreBundleUnsupportedOptions - return fmt.Errorf("unsupported: certificate reference currently not supported with --expect-sigstore-bundle") + return fmt.Errorf("unsupported: certificate reference currently not supported with --new-bundle-format") } cert, err := loadCertFromFileOrURL(c.CertRef) if err != nil { @@ -239,7 +239,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e } case c.TrustedRootPath != "": if !c.NewBundleFormat { - return fmt.Errorf("unsupported: trusted root path currently only supported with --expect-sigstore-bundle") + return fmt.Errorf("unsupported: trusted root path currently only supported with --new-bundle-format") } // If a trusted root path is provided, we will use it to verify the bundle. @@ -357,16 +357,16 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e func checkSigstoreBundleUnsupportedOptions(c *VerifyAttestationCommand) error { if c.Cert != "" || c.CertRef != "" { - return fmt.Errorf("unsupported: certificate may not be provided using --cert when using --expect-sigstore-bundle (cert must be in bundle)") + return fmt.Errorf("unsupported: certificate may not be provided using --cert when using --new-bundle-format (cert must be in bundle)") } if c.CertChain != "" { - return fmt.Errorf("unsupported: certificate chain may not be provided using --cert-chain when using --expect-sigstore-bundle (cert must be in bundle)") + return fmt.Errorf("unsupported: certificate chain may not be provided using --cert-chain when using --new-bundle-format (cert must be in bundle)") } if c.CARoots != "" || c.CAIntermediates != "" { - return fmt.Errorf("unsupported: CA roots/intermediates must be provided using --trusted-root when using --expect-sigstore-bundle") + return fmt.Errorf("unsupported: CA roots/intermediates must be provided using --trusted-root when using --new-bundle-format") } if c.TSACertChainPath != "" { - return fmt.Errorf("unsupported: TSA certificate chain path may only be provided using --trusted-root when using --expect-sigstore-bundle") - } + return fmt.Errorf("unsupported: TSA certificate chain path may only be provided using --trusted-root when using --new-bundle-format") + return nil } From 77be5a85be1d24d4afa949d572968f797fb644a4 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Fri, 22 Nov 2024 15:14:57 -0500 Subject: [PATCH 22/22] Add missing brace Signed-off-by: Cody Soyland --- cmd/cosign/cli/verify/verify_attestation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index c7b65c0f0eb..dd6fa763b50 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -367,6 +367,6 @@ func checkSigstoreBundleUnsupportedOptions(c *VerifyAttestationCommand) error { } if c.TSACertChainPath != "" { return fmt.Errorf("unsupported: TSA certificate chain path may only be provided using --trusted-root when using --new-bundle-format") - + } return nil }