Skip to content

Commit

Permalink
Add new bundle support to verify-blob and verify-blob-attestation
Browse files Browse the repository at this point in the history
Part of sigstore#3139

Signed-off-by: Zach Steindler <steiza@github.com>
  • Loading branch information
steiza committed Jul 23, 2024
1 parent c6cdf1b commit da2d86b
Show file tree
Hide file tree
Showing 11 changed files with 365 additions and 12 deletions.
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

0 comments on commit da2d86b

Please sign in to comment.