Skip to content

Commit

Permalink
Refactor and use CheckOpts to build sigstore-go verification options
Browse files Browse the repository at this point in the history
Signed-off-by: Cody Soyland <[email protected]>
  • Loading branch information
codysoyland committed Nov 1, 2024
1 parent e46acdb commit e509ec5
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 38 deletions.
2 changes: 1 addition & 1 deletion cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) {
"Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp")

cmd.Flags().BoolVar(&o.UseSignedTimestamps, "use-signed-timestamps", false,
"use signed timestamps if available")
"verify rfc3161 timestamps")

cmd.Flags().BoolVar(&o.IgnoreTlog, "insecure-ignore-tlog", false,
"ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts "+
Expand Down
2 changes: 2 additions & 0 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ against the transparency log.`,
Offline: o.CommonVerifyOptions.Offline,
TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath,
IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog,
UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps,
MaxWorkers: o.CommonVerifyOptions.MaxWorkers,
ExperimentalOCI11: o.CommonVerifyOptions.ExperimentalOCI11,
}
Expand Down Expand Up @@ -244,6 +245,7 @@ against the transparency log.`,
Offline: o.CommonVerifyOptions.Offline,
TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath,
IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog,
UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps,
MaxWorkers: o.CommonVerifyOptions.MaxWorkers,
}

Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
Identities: identities,
Offline: c.Offline,
IgnoreTlog: c.IgnoreTlog,
UseSignedTimestamps: c.UseSignedTimestamps,
MaxWorkers: c.MaxWorkers,
ExperimentalOCI11: c.ExperimentalOCI11,
ExpectSigstoreBundle: c.ExpectSigstoreBundle,
Expand Down
45 changes: 39 additions & 6 deletions cmd/cosign/cli/verify/verify_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,29 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
Identities: identities,
Offline: c.Offline,
IgnoreTlog: c.IgnoreTlog,
UseSignedTimestamps: c.UseSignedTimestamps,
MaxWorkers: c.MaxWorkers,
ExpectSigstoreBundle: c.ExpectSigstoreBundle,
}
if c.CheckClaims {
co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
}

if c.ExpectSigstoreBundle {
if err = checkSigstoreBundleUnsupportedOptions(c); err != nil {
return err
}
}

// Ignore Signed Certificate Timestamp if the flag is set or a key is provided
if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) {
if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) && !c.ExpectSigstoreBundle {
co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx)
if err != nil {
return fmt.Errorf("getting ctlog public keys: %w", err)
}
}

if c.TSACertChainPath != "" || c.UseSignedTimestamps {
if c.TSACertChainPath != "" || c.UseSignedTimestamps && !c.ExpectSigstoreBundle {
tsaCertificates, err := c.loadTSACertificates(ctx)
if err != nil {
return fmt.Errorf("unable to load TSA certificates: %w", err)
Expand All @@ -143,7 +151,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts
}

if !c.IgnoreTlog {
if !c.IgnoreTlog && !co.ExpectSigstoreBundle {
if c.RekorURL != "" {
rekorClient, err := rekor.NewClient(c.RekorURL)
if err != nil {
Expand Down Expand Up @@ -189,6 +197,10 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
return fmt.Errorf("initializing piv token verifier: %w", err)
}
case c.CertRef != "":
if c.ExpectSigstoreBundle {
// This shouldn't happen because we already checked for this above in checkSigstoreBundleUnsupportedOptions
return fmt.Errorf("unsupported: certificate reference currently not supported with --expect-sigstore-bundle")
}
cert, err := loadCertFromFileOrURL(c.CertRef)
if err != nil {
return fmt.Errorf("loading certificate from reference: %w", err)
Expand Down Expand Up @@ -229,9 +241,14 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
if !c.ExpectSigstoreBundle {
return fmt.Errorf("unsupported: trusted root path currently only supported with --expect-sigstore-bundle")
}
co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath)
if err != nil {
return fmt.Errorf("loading trusted root: %w", err)

// If a trusted root path is provided, we will use it to verify the bundle.
// Otherwise, the verifier will default to the public good instance.
if c.TrustedRootPath == "" {
co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath)
if err != nil {
return fmt.Errorf("creating trusted root from path: %w", err)
}
}
case c.CARoots != "":
// CA roots + possible intermediates are already loaded into co.RootCerts with the call to
Expand Down Expand Up @@ -337,3 +354,19 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e

return nil
}

func checkSigstoreBundleUnsupportedOptions(c *VerifyAttestationCommand) error {
if c.Cert != "" || c.CertRef != "" {
return fmt.Errorf("unsupported: certificate may not be provided using --cert when using --expect-sigstore-bundle (cert must be in bundle)")
}
if c.CertChain != "" {
return fmt.Errorf("unsupported: certificate chain may not be provided using --cert-chain when using --expect-sigstore-bundle (cert must be in bundle)")
}
if c.CARoots != "" || c.CAIntermediates != "" {
return fmt.Errorf("unsupported: CA roots/intermediates must be provided using --trusted-root when using --expect-sigstore-bundle")
}
if c.TSACertChainPath != "" {
return fmt.Errorf("unsupported: TSA certificate chain path may only be provided using --trusted-root when using --expect-sigstore-bundle")
}
return nil
}
1 change: 1 addition & 0 deletions cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
Identities: identities,
Offline: c.Offline,
IgnoreTlog: c.IgnoreTlog,
UseSignedTimestamps: c.UseSignedTimestamps,
}
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")
Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/verify/verify_blob_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
IgnoreSCT: c.IgnoreSCT,
Offline: c.Offline,
IgnoreTlog: c.IgnoreTlog,
UseSignedTimestamps: c.UseSignedTimestamps,
}
var h v1.Hash
if c.CheckClaims {
Expand Down
127 changes: 96 additions & 31 deletions pkg/cosign/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ import (
"github.com/sigstore/cosign/v2/pkg/blob"
cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle"

"github.com/sigstore/sigstore-go/pkg/fulcio/certificate"

ocibundle "github.com/sigstore/cosign/v2/pkg/oci/bundle"

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / conformance

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / Run e2e tests

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / Verify Docgen

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / attest / verify-attestation test (v1.27.x, remote)

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / attest / verify-attestation test (v1.27.x, air-gap)

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / e2e-cross (ubuntu-latest)

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / Run PowerShell E2E tests

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / e2e-kms

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / Run unit tests (ubuntu-latest)

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / Run unit tests (ubuntu-latest)

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:

Check failure on line 49 in pkg/cosign/verify.go

View workflow job for this annotation

GitHub Actions / e2e-registry

no required module provides package github.com/sigstore/cosign/v2/pkg/oci/bundle; to add it:
"github.com/sigstore/cosign/v2/pkg/oci/static"
"github.com/sigstore/cosign/v2/pkg/types"
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/verify"
sgverify "github.com/sigstore/sigstore-go/pkg/verify"

"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
Expand Down Expand Up @@ -170,6 +173,9 @@ type CheckOpts struct {
// IgnoreTlog skip tlog verification
IgnoreTlog bool

// UseSignedTimestamps use signed timestamps if available
UseSignedTimestamps bool

// The amount of maximum workers for parallel executions.
// Defaults to 10.
MaxWorkers int
Expand All @@ -184,9 +190,93 @@ type CheckOpts struct {
// Currently, this is only applicable when ExpectSigstoreBundle is true.
TrustedMaterial root.TrustedMaterial

// TODO: Add the following, deprecate overlapping fields
//CertificateIdentities verify.CertificateIdentities
//VerifierOptions []verify.VerifierOption
// VerifierOptions are the options to be passed to the verifier.
VerifierOptions []verify.VerifierOption

// PolicyOptions are the policy options to be passed to the verifier.
PolicyOptions []verify.PolicyOption
}

type verifyTrustedMaterial struct {
root.TrustedMaterial
keyTrustedMaterial root.TrustedMaterial
}

func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstrainedVerifier, error) {
return v.keyTrustedMaterial.PublicKeyVerifier(hint)
}

// SigstoreGoOptions returns the verification options for verifying with sigstore-go.
func (co *CheckOpts) SigstoreGoOptions() (trustedMaterial root.TrustedMaterial, verifierOptions []verify.VerifierOption, policyOptions []verify.PolicyOption, err error) {
var sanMatcher verify.SubjectAlternativeNameMatcher
var issuerMatcher verify.IssuerMatcher

if len(co.Identities) > 0 {
if len(co.Identities) > 1 {
return nil, nil, nil, fmt.Errorf("unsupported: multiple identities are not supported at this time")
}
sanMatcher, err = verify.NewSANMatcher(co.Identities[0].Subject, co.Identities[0].SubjectRegExp)
if err != nil {
return nil, nil, nil, err
}

issuerMatcher, err = verify.NewIssuerMatcher(co.Identities[0].Issuer, co.Identities[0].IssuerRegExp)
if err != nil {
return nil, nil, nil, err
}
}

extensions := certificate.Extensions{
GithubWorkflowTrigger: co.CertGithubWorkflowTrigger,
GithubWorkflowSHA: co.CertGithubWorkflowSha,
GithubWorkflowName: co.CertGithubWorkflowName,
GithubWorkflowRepository: co.CertGithubWorkflowRepository,
GithubWorkflowRef: co.CertGithubWorkflowRef,
}

certificateIdentities, err := verify.NewCertificateIdentity(sanMatcher, issuerMatcher, extensions)
if err != nil {
return nil, nil, nil, err
}

policyOptions = append(policyOptions, co.PolicyOptions...)
policyOptions = append(policyOptions, verify.WithCertificateIdentity(certificateIdentities))

// Wrap TrustedMaterial
vTrustedMaterial := &verifyTrustedMaterial{TrustedMaterial: co.TrustedMaterial}

// If TrustedMaterial is not set, fetch it from TUF
if vTrustedMaterial.TrustedMaterial == nil {
vTrustedMaterial.TrustedMaterial, err = root.FetchTrustedRoot()
if err != nil {
return nil, nil, nil, err
}
}

if co.SigVerifier != nil {
policyOptions = append(policyOptions, verify.WithKey())
newExpiringKey := root.NewExpiringKey(co.SigVerifier, time.Time{}, time.Time{})
vTrustedMaterial.keyTrustedMaterial = root.NewTrustedPublicKeyMaterial(func(_ string) (root.TimeConstrainedVerifier, error) {
return newExpiringKey, nil
})
}

// Make some educated guesses about verification policy
verifierOptions = append(verifierOptions, co.VerifierOptions...)
if !co.IgnoreTlog {
verifierOptions = append(verifierOptions, verify.WithTransparencyLog(1), verify.WithIntegratedTimestamps(1))
}
if co.UseSignedTimestamps {
verifierOptions = append(verifierOptions, verify.WithSignedTimestamps(1))
}
if !co.IgnoreSCT {
verifierOptions = append(verifierOptions, verify.WithSignedCertificateTimestamps(1))
}
if co.IgnoreSCT && !co.UseSignedTimestamps {
verifierOptions = append(verifierOptions, verify.WithoutAnyObserverTimestampsUnsafe())
}

return vTrustedMaterial, verifierOptions, policyOptions, nil
}

// This is a substitutable signature verification function that can be used for verifying
Expand Down Expand Up @@ -1463,12 +1553,7 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name
return verifySignatures(ctx, sigs, h, co)
}

func getBundles(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) ([]*sgbundle.Bundle, *v1.Hash, error) {
// Enforce this up front.
if co.TrustedMaterial == nil {
return nil, nil, errors.New("Sigstore bundle verification requires TrustedMaterial")
}

func getBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ([]*sgbundle.Bundle, *v1.Hash, error) {
// This is a carefully optimized sequence for fetching the signatures of the
// entity that minimizes registry requests when supplied with a digest input
digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...)
Expand Down Expand Up @@ -1517,31 +1602,11 @@ func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef nam
return nil, false, err
}

// TODO: build verifierConfig from CheckOpts
verifierConfig := []sgverify.VerifierOption{
sgverify.WithSignedCertificateTimestamps(1),
sgverify.WithTransparencyLog(1),
sgverify.WithIntegratedTimestamps(1),
}

sev, err := sgverify.NewSignedEntityVerifier(co.TrustedMaterial, verifierConfig...)
if err != nil {
return nil, false, err
}

policyOptions := make([]sgverify.PolicyOption, 0, len(co.Identities))
for _, i := range co.Identities {
id, err := sgverify.NewShortCertificateIdentity(i.Issuer, i.IssuerRegExp, i.Subject, i.SubjectRegExp)
if err != nil {
return nil, false, err
}
policyOptions = append(policyOptions, sgverify.WithCertificateIdentity(id))
}
policy := sgverify.NewPolicy(sgverify.WithArtifactDigest(hash.Algorithm, digestBytes), policyOptions...)
artifactPolicyOption := sgverify.WithArtifactDigest(hash.Algorithm, digestBytes)

checkedSignatures = make([]oci.Signature, 0, len(bundles))
for _, bundle := range bundles {
_, err := sev.Verify(bundle, policy)
_, err := VerifyNewBundle(ctx, co, artifactPolicyOption, bundle)
if err != nil {
continue
}
Expand Down
36 changes: 36 additions & 0 deletions pkg/cosign/verify_bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Copyright 2024 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cosign

import (
"context"

"github.com/sigstore/sigstore-go/pkg/verify"
// sigs "github.com/sigstore/cosign/v2/pkg/signature"
)

// VerifyNewBundle verifies a SigstoreBundle with the given parameters
func VerifyNewBundle(_ context.Context, co *CheckOpts, artifactPolicyOption verify.ArtifactPolicyOption, bundle verify.SignedEntity) (*verify.VerificationResult, error) {
trustedMaterial, verifierOptions, policyOptions, err := co.SigstoreGoOptions()
if err != nil {
return nil, err
}
verifier, err := verify.NewSignedEntityVerifier(trustedMaterial, verifierOptions...)
if err != nil {
return nil, err
}
return verifier.Verify(bundle, verify.NewPolicy(artifactPolicyOption, policyOptions...))
}

0 comments on commit e509ec5

Please sign in to comment.