Skip to content

Commit

Permalink
For sigstore#3700: support trusted root in cosign verification
Browse files Browse the repository at this point in the history
We recently added partial trusted root support to cosign when you are
verifying a protobuf bundle, but this did not cover the case where you
aren't using a bundle.

This implements trusted root support for those cases by assembling the
disparate signed material into a bundle, fixing some TODOs from when we
added protobuf bundle support.

Signed-off-by: Zach Steindler <[email protected]>
  • Loading branch information
steiza committed Aug 27, 2024
1 parent 8defb0e commit 3a86092
Show file tree
Hide file tree
Showing 5 changed files with 391 additions and 244 deletions.
82 changes: 3 additions & 79 deletions cmd/conformance/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,15 @@
package main

import (
"crypto/sha256"
"encoding/base64"
"encoding/pem"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"

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/sigstore-go/pkg/bundle"
"google.golang.org/protobuf/encoding/protojson"
)

var bundlePath *string
Expand Down Expand Up @@ -111,83 +106,12 @@ func main() {
case "verify":
args = append(args, "verify-blob")

// TODO: for now, we handle `verify` by constructing a bundle
// (see https://github.com/sigstore/cosign/issues/3700)
//
// Today cosign only supports `--trusted-root` with the new bundle
// format. When cosign supports `--trusted-root` with detached signed
// material, we can supply this content with `--certificate`
// and `--signature` instead.
fileBytes, err := os.ReadFile(os.Args[len(os.Args)-1])
if err != nil {
log.Fatal(err)
}

fileDigest := sha256.Sum256(fileBytes)

pb := protobundle.Bundle{
MediaType: "application/vnd.dev.sigstore.bundle+json;version=0.1",
}

if signaturePath != nil {
sig, err := os.ReadFile(*signaturePath)
if err != nil {
log.Fatal(err)
}

sigBytes, err := base64.StdEncoding.DecodeString(string(sig))
if err != nil {
log.Fatal(err)
}

pb.Content = &protobundle.Bundle_MessageSignature{
MessageSignature: &protocommon.MessageSignature{
MessageDigest: &protocommon.HashOutput{
Algorithm: protocommon.HashAlgorithm_SHA2_256,
Digest: fileDigest[:],
},
Signature: sigBytes,
},
}
}
if certPath != nil {
cert, err := os.ReadFile(*certPath)
if err != nil {
log.Fatal(err)
}

pemCert, _ := pem.Decode(cert)
if pemCert == nil {
log.Fatalf("unable to load cerficate from %s", *certPath)
}

signingCert := protocommon.X509Certificate{
RawBytes: pemCert.Bytes,
}

pb.VerificationMaterial = &protobundle.VerificationMaterial{
Content: &protobundle.VerificationMaterial_X509CertificateChain{
X509CertificateChain: &protocommon.X509CertificateChain{
Certificates: []*protocommon.X509Certificate{&signingCert},
},
},
}
args = append(args, "--certificate", *certPath)
}

bundleFile, err := os.CreateTemp(os.TempDir(), "bundle.sigstore.json")
if err != nil {
log.Fatal(err)
}
bundleFileName := bundleFile.Name()
pbBytes, err := protojson.Marshal(&pb)
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile(bundleFileName, pbBytes, 0600); err != nil {
log.Fatal(err)
if signaturePath != nil {
args = append(args, "--signature", *signaturePath)
}
bundlePath = &bundleFileName
args = append(args, "--insecure-ignore-tlog")

case "verify-bundle":
args = append(args, "verify-blob")
Expand Down
16 changes: 15 additions & 1 deletion cmd/cosign/cli/attest/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,21 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
var rekorEntry *models.LogEntryAnon

if c.TSAServerURL != "" {
timestampBytes, err = tsa.GetTimestampedSignature(sig, client.NewTSAClient(c.TSAServerURL))
// sig is the entire JSON DSSE Envelope; we just want the signature for TSA
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))
if err != nil {
return err
}
Expand Down
120 changes: 70 additions & 50 deletions cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"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/pkg/cryptoutils"
)

Expand Down Expand Up @@ -96,33 +97,25 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
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)
b, err := sgbundle.LoadJSONFromPath(c.BundlePath)
if err != nil {
return err
}
_, err = verifyNewBundle(ctx, b, 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 == "" {
identities, err = c.Identities()
if err != nil {
return err
}
}

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

blobBytes, err := payloadBytes(blobRef)
sigBytes, err := base64.StdEncoding.DecodeString(sig)
if err != nil {
return err
}
Expand All @@ -134,44 +127,9 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
CertGithubWorkflowRepository: c.CertGithubWorkflowRepository,
CertGithubWorkflowRef: c.CertGithubWorkflowRef,
IgnoreSCT: c.IgnoreSCT,
Identities: identities,
Offline: c.Offline,
IgnoreTlog: c.IgnoreTlog,
}
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")
}
if c.TSACertChainPath != "" || c.UseSignedTimestamps {
tsaCertificates, err := c.loadTSACertificates(ctx)
if err != nil {
return err
}
co.TSACertificate = tsaCertificates.LeafCert
co.TSARootCertificates = tsaCertificates.RootCert
co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts
}

if !c.IgnoreTlog {
if c.RekorURL != "" {
rekorClient, err := rekor.NewClient(c.RekorURL)
if err != nil {
return fmt.Errorf("creating Rekor client: %w", err)
}
co.RekorClient = rekorClient
}
// This performs an online fetch of the Rekor public keys, but this is needed
// for verifying tlog entries (both online and offline).
co.RekorPubKeys, err = cosign.GetRekorPubs(ctx)
if err != nil {
return fmt.Errorf("getting Rekor public keys: %w", err)
}
}

if keylessVerification(c.KeyRef, c.Sk) {
if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil {
return err
}
}

// Keys are optional!
switch {
Expand Down Expand Up @@ -200,6 +158,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
return err
}
}

if c.BundlePath != "" {
b, err := cosign.FetchLocalSignedPayloadFromPath(c.BundlePath)
if err != nil {
Expand Down Expand Up @@ -234,8 +193,9 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
}
opts = append(opts, static.WithBundle(b.Bundle))
}

var rfc3161Timestamp bundle.RFC3161Timestamp
if c.RFC3161TimestampPath != "" {
var rfc3161Timestamp bundle.RFC3161Timestamp
ts, err := blob.LoadFileOrURL(c.RFC3161TimestampPath)
if err != nil {
return err
Expand All @@ -245,6 +205,62 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
}
opts = append(opts, static.WithRFC3161Timestamp(&rfc3161Timestamp))
}

if !c.IgnoreTlog {
if c.RekorURL != "" {
rekorClient, err := rekor.NewClient(c.RekorURL)
if err != nil {
return fmt.Errorf("creating Rekor client: %w", err)
}
co.RekorClient = rekorClient
}
// This performs an online fetch of the Rekor public keys, but this is needed
// for verifying tlog entries (both online and offline).
co.RekorPubKeys, err = cosign.GetRekorPubs(ctx)
if err != nil {
return fmt.Errorf("getting Rekor public keys: %w", err)
}
}

if c.TrustedRootPath != "" {
b, err := assembleNewBundle(ctx, sigBytes, rfc3161Timestamp.SignedRFC3161Timestamp, nil, blobRef, cert, c.IgnoreTlog, co.SigVerifier, co.PKOpts, co.RekorClient)
if err != nil {
return err
}

_, err = verifyNewBundle(ctx, b, 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
}

if c.KeyRef == "" {
co.Identities, err = c.Identities()
if err != nil {
return err
}
}

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")
}
if c.TSACertChainPath != "" || c.UseSignedTimestamps {
tsaCertificates, err := c.loadTSACertificates(ctx)
if err != nil {
return err
}
co.TSACertificate = tsaCertificates.LeafCert
co.TSARootCertificates = tsaCertificates.RootCert
co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts
}

if keylessVerification(c.KeyRef, c.Sk) {
if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil {
return err
}
}

// Set an SCT if provided via the CLI.
if c.SCTRef != "" {
sct, err := os.ReadFile(filepath.Clean(c.SCTRef))
Expand Down Expand Up @@ -300,6 +316,10 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
}
}

blobBytes, err := payloadBytes(blobRef)
if err != nil {
return err
}
signature, err := static.NewSignature(blobBytes, sig, opts...)
if err != nil {
return err
Expand Down
Loading

0 comments on commit 3a86092

Please sign in to comment.