Skip to content

Commit

Permalink
Refactor verifyNewBundle into library function (#4013)
Browse files Browse the repository at this point in the history
* Add new func cosign.VerifyNewBundle which invokes sigstore-go verifier

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Refactor verify-blob to use cosign.VerifyNewBundle

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Refactor verify-blob-attestation to use cosign.VerifyNewBundle

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Add more tests

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Remove old verifyNewBundle

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Add support for verifying by payload digest and custom trusted root

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Add support for custom trusted root path in verify-blob-attestation

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Fix logic: require none of these fields to be set

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Remove RekorURL from list of checked flags

This var has a default value so shouldn't be checked

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Fix a couple of tests

These tests are incorrect: they set the signature field which is not allowed when doing bundle verification. Previously they were passing due to logic errors.

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Update pkg/cosign/verify.go

Co-authored-by: Colleen Murphy <cmurphy@users.noreply.github.com>
Signed-off-by: Cody Soyland <codysoyland@github.com>

* Remove unneeded log

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Do not allow --trusted-root flag without --new-bundle-format

Signed-off-by: Cody Soyland <codysoyland@github.com>

* Ignore context param

Signed-off-by: Cody Soyland <codysoyland@github.com>

---------

Signed-off-by: Cody Soyland <codysoyland@github.com>
Co-authored-by: Colleen Murphy <cmurphy@users.noreply.github.com>
  • Loading branch information
codysoyland and cmurphy authored Feb 7, 2025
1 parent 7fc8e2a commit 737c83c
Show file tree
Hide file tree
Showing 8 changed files with 690 additions and 368 deletions.
162 changes: 108 additions & 54 deletions cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@
package verify

import (
"bytes"
"context"
"crypto"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"

"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
Expand All @@ -38,6 +41,9 @@ import (
"github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key"
"github.com/sigstore/cosign/v2/pkg/oci/static"
sigs "github.com/sigstore/cosign/v2/pkg/signature"
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/sigstore/sigstore/pkg/cryptoutils"
)
Expand Down Expand Up @@ -81,22 +87,6 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
return &options.PubKeyParseError{}
}

if c.KeyOpts.NewBundleFormat || checkNewBundle(c.BundlePath) {
if options.NOf(c.RFC3161TimestampPath, c.TSACertChainPath, c.RekorURL, c.CertChain, c.CARoots, c.CAIntermediates, c.CertRef, c.SigRef, c.SCTRef) > 1 {
return fmt.Errorf("when using --new-bundle-format, please supply signed content with --bundle and verification content with --trusted-root")
}
_, 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
} else if c.TrustedRootPath != "" {
return fmt.Errorf("--trusted-root only supported with --new-bundle-format")
}

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

var identities []cosign.Identity
var err error
if c.KeyRef == "" {
Expand All @@ -106,16 +96,6 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
}
}

sig, err := base64signature(c.SigRef, c.BundlePath)
if err != nil {
return err
}

blobBytes, err := payloadBytes(blobRef)
if err != nil {
return err
}

co := &cosign.CheckOpts{
CertGithubWorkflowTrigger: c.CertGithubWorkflowTrigger,
CertGithubWorkflowSha: c.CertGithubWorkflowSHA,
Expand All @@ -127,8 +107,85 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
Offline: c.Offline,
IgnoreTlog: c.IgnoreTlog,
UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps,
NewBundleFormat: c.KeyOpts.NewBundleFormat || checkNewBundle(c.BundlePath),
}

// Keys are optional!
var cert *x509.Certificate
opts := make([]static.Option, 0)
switch {
case c.KeyRef != "":
co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, c.KeyRef)
if err != nil {
return fmt.Errorf("loading public key: %w", err)
}
pkcs11Key, ok := co.SigVerifier.(*pkcs11key.Key)
if ok {
defer pkcs11Key.Close()
}
case c.Sk:
sk, err := pivkey.GetKeyWithSlot(c.Slot)
if err != nil {
return fmt.Errorf("opening piv token: %w", err)
}
defer sk.Close()
co.SigVerifier, err = sk.Verifier()
if err != nil {
return fmt.Errorf("loading public key from token: %w", err)
}
case c.CertRef != "":
cert, err = loadCertFromFileOrURL(c.CertRef)
if err != nil {
return err
}
}

if co.NewBundleFormat {
if options.NOf(c.RFC3161TimestampPath, c.TSACertChainPath, c.CertChain, c.CARoots, c.CAIntermediates, c.CertRef, c.SigRef, c.SCTRef) > 0 {
return fmt.Errorf("when using --new-bundle-format, please supply signed content with --bundle and verification content with --trusted-root")
}

if co.TrustedMaterial == nil {
co.TrustedMaterial, err = loadTrustedRoot(ctx, c.TrustedRootPath)
if err != nil {
return err
}
}

bundle, err := sgbundle.LoadJSONFromPath(c.BundlePath)
if err != nil {
return err
}

var artifactPolicyOption sgverify.ArtifactPolicyOption
blobBytes, err := payloadBytes(blobRef)
if err != nil {
alg, digest, payloadDigestError := payloadDigest(blobRef)
if payloadDigestError != nil {
return err
}
artifactPolicyOption = sgverify.WithArtifactDigest(alg, digest)
} else {
artifactPolicyOption = sgverify.WithArtifact(bytes.NewReader(blobBytes))
}

_, err = cosign.VerifyNewBundle(ctx, co, artifactPolicyOption, bundle)
if err != nil {
return err
}

ui.Infof(ctx, "Verified OK")
return nil
}

blobBytes, err := payloadBytes(blobRef)
if err != nil {
return err
}

if c.TrustedRootPath != "" {
return fmt.Errorf("--trusted-root only supported with --new-bundle-format")
}
if c.RFC3161TimestampPath != "" && !co.UseSignedTimestamps {
return fmt.Errorf("when specifying --rfc3161-timestamp-path, you must also specify --use-signed-timestamps or --timestamp-certificate-chain")
} else if c.RFC3161TimestampPath == "" && co.UseSignedTimestamps {
Expand Down Expand Up @@ -165,34 +222,6 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
return err
}
}

// Keys are optional!
switch {
case c.KeyRef != "":
co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, c.KeyRef)
if err != nil {
return fmt.Errorf("loading public key: %w", err)
}
pkcs11Key, ok := co.SigVerifier.(*pkcs11key.Key)
if ok {
defer pkcs11Key.Close()
}
case c.Sk:
sk, err := pivkey.GetKeyWithSlot(c.Slot)
if err != nil {
return fmt.Errorf("opening piv token: %w", err)
}
defer sk.Close()
co.SigVerifier, err = sk.Verifier()
if err != nil {
return fmt.Errorf("loading public key from token: %w", err)
}
case c.CertRef != "":
cert, err = loadCertFromFileOrURL(c.CertRef)
if err != nil {
return err
}
}
if c.BundlePath != "" {
b, err := cosign.FetchLocalSignedPayloadFromPath(c.BundlePath)
if err != nil {
Expand Down Expand Up @@ -293,6 +322,10 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
}
}

sig, err := base64signature(c.SigRef, c.BundlePath)
if err != nil {
return err
}
signature, err := static.NewSignature(blobBytes, sig, opts...)
if err != nil {
return err
Expand Down Expand Up @@ -348,3 +381,24 @@ func payloadBytes(blobRef string) ([]byte, error) {
}
return blobBytes, nil
}

func payloadDigest(blobRef string) (string, []byte, error) {
hexAlg, hexDigest, ok := strings.Cut(blobRef, ":")
if !ok {
return "", nil, fmt.Errorf("invalid digest format")
}
digestBytes, err := hex.DecodeString(hexDigest)
if err != nil {
return "", nil, err
}
return hexAlg, digestBytes, nil
}

func loadTrustedRoot(_ context.Context, trustedRootPath string) (*root.TrustedRoot, error) {
if trustedRootPath != "" {
return root.NewTrustedRootFromPath(trustedRootPath)
}
// Assume we're using public good instance; fetch via TUF
// TODO: allow custom TUF settings
return root.FetchTrustedRoot()
}
115 changes: 69 additions & 46 deletions cmd/cosign/cli/verify/verify_blob_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
internal "github.com/sigstore/cosign/v2/internal/pkg/cosign"
payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
"github.com/sigstore/cosign/v2/internal/ui"
"github.com/sigstore/cosign/v2/pkg/blob"
"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
Expand All @@ -42,6 +43,8 @@ import (
"github.com/sigstore/cosign/v2/pkg/oci/static"
"github.com/sigstore/cosign/v2/pkg/policy"
sigs "github.com/sigstore/cosign/v2/pkg/signature"
sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
sgverify "github.com/sigstore/sigstore-go/pkg/verify"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)

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

if c.KeyOpts.NewBundleFormat || checkNewBundle(c.BundlePath) {
if options.NOf(c.RFC3161TimestampPath, c.TSACertChainPath, c.RekorURL, c.CertChain, c.CARoots, c.CAIntermediates, c.CertRef, c.SCTRef) > 1 {
return fmt.Errorf("when using --new-bundle-format, please supply signed content with --bundle and verification content with --trusted-root")
}
_, 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
} else if c.TrustedRootPath != "" {
return fmt.Errorf("--trusted-root only supported with --new-bundle-format")
}

var identities []cosign.Identity
if c.KeyRef == "" {
identities, err = c.Identities()
Expand All @@ -124,8 +114,44 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
Offline: c.Offline,
IgnoreTlog: c.IgnoreTlog,
UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps,
NewBundleFormat: c.KeyOpts.NewBundleFormat || checkNewBundle(c.BundlePath),
}

// Keys are optional!
var cert *x509.Certificate
opts := make([]static.Option, 0)
switch {
case c.KeyRef != "":
co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, c.KeyRef)
if err != nil {
return fmt.Errorf("loading public key: %w", err)
}
pkcs11Key, ok := co.SigVerifier.(*pkcs11key.Key)
if ok {
defer pkcs11Key.Close()
}
case c.Sk:
sk, err := pivkey.GetKeyWithSlot(c.Slot)
if err != nil {
return fmt.Errorf("opening piv token: %w", err)
}
defer sk.Close()
co.SigVerifier, err = sk.Verifier()
if err != nil {
return fmt.Errorf("loading public key from token: %w", err)
}
case c.CertRef != "":
cert, err = loadCertFromFileOrURL(c.CertRef)
if err != nil {
return err
}
case c.CARoots != "":
// CA roots + possible intermediates are already loaded into co.RootCerts with the call to
// loadCertsKeylessVerification above.
}

var h v1.Hash
var digest []byte
if c.CheckClaims {
// Get the actual digest of the blob
var payload internal.HashReader
Expand All @@ -147,14 +173,43 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
if _, err := io.ReadAll(&payload); err != nil {
return err
}
digest := payload.Sum(nil)
digest = payload.Sum(nil)
h = v1.Hash{
Hex: hex.EncodeToString(digest),
Algorithm: "sha256",
}
co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
}

if co.NewBundleFormat {
if options.NOf(c.RFC3161TimestampPath, c.TSACertChainPath, c.CertChain, c.CARoots, c.CAIntermediates, c.CertRef, c.SCTRef) > 0 {
return fmt.Errorf("when using --new-bundle-format, please supply signed content with --bundle and verification content with --trusted-root")
}

if co.TrustedMaterial == nil {
co.TrustedMaterial, err = loadTrustedRoot(ctx, c.TrustedRootPath)
if err != nil {
return err
}
}

bundle, err := sgbundle.LoadJSONFromPath(c.BundlePath)
if err != nil {
return err
}

_, err = cosign.VerifyNewBundle(ctx, co, sgverify.WithArtifactDigest(h.Algorithm, digest), bundle)
if err != nil {
return err
}

ui.Infof(ctx, "Verified OK")
return nil
}

if c.TrustedRootPath != "" {
return fmt.Errorf("--trusted-root only supported with --new-bundle-format")
}
if c.RFC3161TimestampPath != "" && !co.UseSignedTimestamps {
return fmt.Errorf("when specifying --rfc3161-timestamp-path, you must also specify --use-signed-timestamps or --timestamp-certificate-chain")
} else if c.RFC3161TimestampPath == "" && co.UseSignedTimestamps {
Expand Down Expand Up @@ -207,38 +262,6 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
}
}

// Keys are optional!
var cert *x509.Certificate
opts := make([]static.Option, 0)
switch {
case c.KeyRef != "":
co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, c.KeyRef)
if err != nil {
return fmt.Errorf("loading public key: %w", err)
}
pkcs11Key, ok := co.SigVerifier.(*pkcs11key.Key)
if ok {
defer pkcs11Key.Close()
}
case c.Sk:
sk, err := pivkey.GetKeyWithSlot(c.Slot)
if err != nil {
return fmt.Errorf("opening piv token: %w", err)
}
defer sk.Close()
co.SigVerifier, err = sk.Verifier()
if err != nil {
return fmt.Errorf("loading public key from token: %w", err)
}
case c.CertRef != "":
cert, err = loadCertFromFileOrURL(c.CertRef)
if err != nil {
return err
}
case c.CARoots != "":
// CA roots + possible intermediates are already loaded into co.RootCerts with the call to
// loadCertsKeylessVerification above.
}
if c.BundlePath != "" {
b, err := cosign.FetchLocalSignedPayloadFromPath(c.BundlePath)
if err != nil {
Expand Down
Loading

0 comments on commit 737c83c

Please sign in to comment.