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

Refactor verifyNewBundle into library function #4013

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
163 changes: 109 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.RekorURL, c.CertChain, c.CARoots, c.CAIntermediates, c.CertRef, c.SigRef, c.SCTRef) > 1 {
codysoyland marked this conversation as resolved.
Show resolved Hide resolved
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,25 @@ 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(ctx context.Context, trustedRootPath string) (*root.TrustedRoot, error) {
if trustedRootPath != "" {
return root.NewTrustedRootFromPath(trustedRootPath)
}
ui.Infof(ctx, "no --trusted-root specified; fetching public good instance verification material via TUF")
// Assume we're using public good instance; fetch via TUF
// TODO: allow custom TUF settings
return root.FetchTrustedRoot()
}
112 changes: 66 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),
codysoyland marked this conversation as resolved.
Show resolved Hide resolved
}

// 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,40 @@ 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.RekorURL, c.CertChain, c.CARoots, c.CAIntermediates, c.CertRef, c.SCTRef) > 1 {
codysoyland marked this conversation as resolved.
Show resolved Hide resolved
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.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 +259,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
Loading