Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new bundle support to verify-blob and verify-blob-attestation #3796

Merged
merged 4 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,11 @@ 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
Key string
Signature string
BundlePath string
NewBundleFormat bool
TrustedRootPath string

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

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

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

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

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

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

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

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

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

Expand Down
6 changes: 6 additions & 0 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,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,
RFC3161TimestampPath: o.RFC3161TimestampPath,
TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath,
}
Expand All @@ -344,6 +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,
CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger,
CertGithubWorkflowSHA: o.CertVerify.CertGithubWorkflowSha,
CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName,
Expand All @@ -353,6 +355,7 @@ The blob may be specified as a path to a file or - for stdin.`,
SCTRef: o.CertVerify.SCT,
Offline: o.CommonVerifyOptions.Offline,
IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog,
UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps,
}

ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout)
Expand Down Expand Up @@ -401,6 +404,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,
RFC3161TimestampPath: o.RFC3161TimestampPath,
TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath,
}
Expand All @@ -410,6 +414,7 @@ The blob may be specified as a path to a file.`,
CheckClaims: o.CheckClaims,
SignaturePath: o.SignaturePath,
CertVerifyOptions: o.CertVerify,
TrustedRootPath: o.TrustedRootPath,
CertRef: o.CertVerify.Cert,
CertChain: o.CertVerify.CertChain,
CARoots: o.CertVerify.CARoots,
Expand All @@ -423,6 +428,7 @@ The blob may be specified as a path to a file.`,
SCTRef: o.CertVerify.SCT,
Offline: o.CommonVerifyOptions.Offline,
IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog,
UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps,
}
// We only use the blob if we are checking claims.
if len(args) == 0 && o.CheckClaims {
Expand Down
15 changes: 12 additions & 3 deletions cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type VerifyBlobCmd struct {
CARoots string
CertChain string
SigRef string
TrustedRootPath string
CertGithubWorkflowTrigger string
CertGithubWorkflowSHA string
CertGithubWorkflowName string
Expand All @@ -81,9 +82,6 @@ func (c *VerifyBlobCmd) loadTSACertificates(ctx context.Context) (*cosign.TSACer

// nolint
func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
var cert *x509.Certificate
opts := make([]static.Option, 0)

// Require a certificate/key OR a local bundle file that has the cert.
if options.NOf(c.KeyRef, c.CertRef, c.Sk, c.BundlePath) == 0 {
return fmt.Errorf("provide a key with --key or --sk, a certificate to verify against with --certificate, or a bundle with --bundle")
Expand All @@ -94,6 +92,17 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
return &options.PubKeyParseError{}
}

if c.KeyOpts.NewBundleFormat {
err := verifyNewBundle(ctx, c.BundlePath, c.TrustedRootPath, c.KeyRef, c.Slot, c.CertVerifyOptions.CertOidcIssuer, c.CertVerifyOptions.CertOidcIssuerRegexp, c.CertVerifyOptions.CertIdentity, c.CertVerifyOptions.CertIdentityRegexp, c.CertGithubWorkflowTrigger, c.CertGithubWorkflowSHA, c.CertGithubWorkflowName, c.CertGithubWorkflowRepository, c.CertGithubWorkflowRef, blobRef, c.Sk, c.IgnoreTlog, c.UseSignedTimestamps, c.IgnoreSCT)
if err == nil {
ui.Infof(ctx, "Verified OK")
}
return err
}

var cert *x509.Certificate
opts := make([]static.Option, 0)

var identities []cosign.Identity
var err error
if c.KeyRef == "" {
Expand Down
9 changes: 9 additions & 0 deletions cmd/cosign/cli/verify/verify_blob_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type VerifyBlobAttestationCommand struct {
CertChain string
CAIntermediates string
CARoots string
TrustedRootPath string

CertGithubWorkflowTrigger string
CertGithubWorkflowSHA string
Expand Down Expand Up @@ -91,6 +92,14 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
return &options.KeyParseError{}
}

if c.KeyOpts.NewBundleFormat {
err = verifyNewBundle(ctx, c.BundlePath, c.TrustedRootPath, c.KeyRef, c.Slot, c.CertVerifyOptions.CertOidcIssuer, c.CertVerifyOptions.CertOidcIssuerRegexp, c.CertVerifyOptions.CertIdentity, c.CertVerifyOptions.CertIdentityRegexp, c.CertGithubWorkflowTrigger, c.CertGithubWorkflowSHA, c.CertGithubWorkflowName, c.CertGithubWorkflowRepository, c.CertGithubWorkflowRef, artifactPath, c.Sk, c.IgnoreTlog, c.UseSignedTimestamps, c.IgnoreSCT)
if err == nil {
fmt.Fprintln(os.Stderr, "Verified OK")
}
return err
}

var identities []cosign.Identity
if c.KeyRef == "" {
identities, err = c.Identities()
Expand Down
65 changes: 65 additions & 0 deletions cmd/cosign/cli/verify/verify_blob_attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ import (
"context"
"encoding/base64"
"os"
"path/filepath"
"testing"

protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
protodsse "github.com/sigstore/protobuf-specs/gen/pb-go/dsse"
"google.golang.org/protobuf/encoding/protojson"

"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
)

const pubkey = `-----BEGIN PUBLIC KEY-----
Expand Down Expand Up @@ -53,6 +59,7 @@ func TestVerifyBlobAttestation(t *testing.T) {
tests := []struct {
description string
blobPath string
bundlePath string
signature string
predicateType string
env map[string]string
Expand Down Expand Up @@ -107,6 +114,17 @@ func TestVerifyBlobAttestation(t *testing.T) {
blobPath: hugeBlobPath,
env: map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "128"},
shouldErr: true,
}, {
description: "verify new bundle with public key",
// From blobSLSAProvenanceSignature
bundlePath: makeLocalAttestNewBundle(t, "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJibG9iIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjY1ODc4MWNkNGVkOWJjYTYwZGFjZDA5ZjdiYjkxNGJiNTE1MDJlOGI1ZDYxOWY1N2YzOWExZDY1MjU5NmNjMjQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6IjIifSwiYnVpbGRUeXBlIjoieCIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", "application/vnd.in-toto+json", "MEUCIA8KjZqkrt90fzBojSwwtj3Bqb41E6ruxQk97TLnpzdYAiEAzOAjOTzyvTHqbpFDAn6zhrg6EZv7kxK5faRoVGYMh2c="),
blobPath: blobPath,
}, {
description: "verify new bundle with public key - bad sig",
// From blobSLSAProvenanceSignature
bundlePath: makeLocalAttestNewBundle(t, "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJibG9iIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjY1ODc4MWNkNGVkOWJjYTYwZGFjZDA5ZjdiYjkxNGJiNTE1MDJlOGI1ZDYxOWY1N2YzOWExZDY1MjU5NmNjMjQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6IjIifSwiYnVpbGRUeXBlIjoieCIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", "application/vnd.in-toto+json", "c29tZXRoaW5nCg=="),
blobPath: blobPath,
shouldErr: true,
},
}

Expand All @@ -128,6 +146,11 @@ func TestVerifyBlobAttestation(t *testing.T) {
CheckClaims: true,
PredicateType: test.predicateType,
}
if test.bundlePath != "" {
cmd.KeyOpts.BundlePath = test.bundlePath
cmd.KeyOpts.NewBundleFormat = true
cmd.TrustedRootPath = writeTrustedRootFile(t, td, "{\"mediaType\":\"application/vnd.dev.sigstore.trustedroot+json;version=0.1\"}")
}
err = cmd.Exec(ctx, test.blobPath)

if (err != nil) != test.shouldErr {
Expand Down Expand Up @@ -191,3 +214,45 @@ func TestVerifyBlobAttestationNoCheckClaims(t *testing.T) {
})
}
}

func makeLocalAttestNewBundle(t *testing.T, payload, payloadType, sig string) string {
b, err := bundle.MakeProtobufBundle("hint", []byte{}, nil, []byte{})
if err != nil {
t.Fatal(err)
}

decodedPayload, err := base64.StdEncoding.DecodeString(payload)
if err != nil {
t.Fatal(err)
}

decodedSig, err := base64.StdEncoding.DecodeString(sig)
if err != nil {
t.Fatal(err)
}

b.Content = &protobundle.Bundle_DsseEnvelope{
DsseEnvelope: &protodsse.Envelope{
Payload: decodedPayload,
PayloadType: payloadType,
Signatures: []*protodsse.Signature{
{
Sig: decodedSig,
},
},
},
}

contents, err := protojson.Marshal(b)
if err != nil {
t.Fatal(err)
}

// write bundle to disk
td := t.TempDir()
bundlePath := filepath.Join(td, "bundle.sigstore.json")
if err := os.WriteFile(bundlePath, contents, 0644); err != nil {
t.Fatal(err)
}
return bundlePath
}
66 changes: 66 additions & 0 deletions cmd/cosign/cli/verify/verify_blob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import (
sigs "github.com/sigstore/cosign/v2/pkg/signature"
ctypes "github.com/sigstore/cosign/v2/pkg/types"
"github.com/sigstore/cosign/v2/test"
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/pki"
"github.com/sigstore/rekor/pkg/types"
Expand All @@ -57,6 +59,7 @@ import (
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/dsse"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
"google.golang.org/protobuf/encoding/protojson"
)

func TestSignaturesRef(t *testing.T) {
Expand Down Expand Up @@ -235,6 +238,7 @@ func TestVerifyBlob(t *testing.T) {
key []byte
cert *x509.Certificate
bundlePath string
newBundle bool
// The rekor entry response when Rekor is enabled
rekorEntry []*models.LogEntry
skipTlogVerify bool
Expand Down Expand Up @@ -320,6 +324,26 @@ func TestVerifyBlob(t *testing.T) {
pubKeyBytes, true),
shouldErr: true,
},
{
name: "valid signature with public key - new bundle",
blob: blobBytes,
signature: blobSignature,
key: pubKeyBytes,
bundlePath: makeLocalNewBundle(t, []byte(blobSignature), sha256.Sum256(blobBytes)),
newBundle: true,
skipTlogVerify: true,
shouldErr: false,
},
{
name: "invalid signature with public key - new bundle",
blob: blobBytes,
signature: otherSignature,
key: pubKeyBytes,
bundlePath: makeLocalNewBundle(t, []byte(blobSignature), sha256.Sum256(blobBytes)),
newBundle: true,
skipTlogVerify: true,
shouldErr: false,
},
{
name: "invalid signature with public key",
blob: blobBytes,
Expand Down Expand Up @@ -563,6 +587,7 @@ func TestVerifyBlob(t *testing.T) {
cmd := VerifyBlobCmd{
KeyOpts: options.KeyOpts{
BundlePath: tt.bundlePath,
NewBundleFormat: tt.newBundle,
RekorURL: testServer.URL,
RFC3161TimestampPath: tt.tsPath,
TSACertChainPath: tt.tsChainPath,
Expand Down Expand Up @@ -592,6 +617,9 @@ func TestVerifyBlob(t *testing.T) {
keyPath := writeBlobFile(t, td, string(tt.key), "key.pem")
cmd.KeyRef = keyPath
}
if tt.newBundle {
cmd.TrustedRootPath = writeTrustedRootFile(t, td, "{\"mediaType\":\"application/vnd.dev.sigstore.trustedroot+json;version=0.1\"}")
}

err := cmd.Exec(context.Background(), blobPath)
if (err != nil) != tt.shouldErr {
Expand Down Expand Up @@ -757,6 +785,36 @@ func makeLocalBundleWithoutRekorBundle(t *testing.T, sig []byte, svBytes []byte)
return bundlePath
}

func makeLocalNewBundle(t *testing.T, sig []byte, digest [32]byte) string {
b, err := bundle.MakeProtobufBundle("hint", []byte{}, nil, []byte{})
if err != nil {
t.Fatal(err)
}

b.Content = &protobundle.Bundle_MessageSignature{
MessageSignature: &protocommon.MessageSignature{
MessageDigest: &protocommon.HashOutput{
Algorithm: protocommon.HashAlgorithm_SHA2_256,
Digest: digest[:],
},
Signature: sig,
},
}

contents, err := protojson.Marshal(b)
if err != nil {
t.Fatal(err)
}

// write bundle to disk
td := t.TempDir()
bundlePath := filepath.Join(td, "bundle.sigstore.json")
if err := os.WriteFile(bundlePath, contents, 0644); err != nil {
t.Fatal(err)
}
return bundlePath
}

func TestVerifyBlobCmdWithBundle(t *testing.T) {
keyless := newKeylessStack(t)
defer os.RemoveAll(keyless.td)
Expand Down Expand Up @@ -1574,3 +1632,11 @@ func writeTimestampFile(t *testing.T, td string, ts *bundle.RFC3161Timestamp, na
}
return path
}

func writeTrustedRootFile(t *testing.T, td, contents string) string {
path := filepath.Join(td, "trusted_root.json")
if err := os.WriteFile(path, []byte(contents), 0644); err != nil {
t.Fatal(err)
}
return path
}
Loading
Loading