Skip to content

Commit

Permalink
Provide certificate flags to all verify commands
Browse files Browse the repository at this point in the history
Small refactor to provide the cert and cert-email
flags to all verify commands. cert-email will be
optionally used to when verifying a Fulcio certificate
to pin the cert identity. This refactor makes it easier
to add additional cert validations.

Signed-off-by: Hayden Blauzvern <[email protected]>
  • Loading branch information
haydentherapper committed Jan 12, 2022
1 parent 754d33e commit 8cace74
Show file tree
Hide file tree
Showing 14 changed files with 102 additions and 70 deletions.
3 changes: 2 additions & 1 deletion cmd/cosign/cli/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ Shell-like variables in the Dockerfile's FROM lines will be substituted with val
RegistryOptions: o.Registry,
CheckClaims: o.CheckClaims,
KeyRef: o.Key,
CertEmail: o.CertEmail,
CertRef: o.CertVerify.Cert,
CertEmail: o.CertVerify.CertEmail,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Output: o.Output,
Expand Down
3 changes: 2 additions & 1 deletion cmd/cosign/cli/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ against the transparency log.`,
RegistryOptions: o.Registry,
CheckClaims: o.CheckClaims,
KeyRef: o.Key,
CertEmail: o.CertEmail,
CertRef: o.CertVerify.Cert,
CertEmail: o.CertVerify.CertEmail,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Output: o.Output,
Expand Down
36 changes: 36 additions & 0 deletions cmd/cosign/cli/options/certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2022 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 options

import (
"github.com/spf13/cobra"
)

// CertVerifyOptions is the wrapper for certificate verification.
type CertVerifyOptions struct {
Cert string
CertEmail string
}

var _ Interface = (*RekorOptions)(nil)

// AddFlags implements Interface
func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Cert, "cert", "",
"path to the public certificate")

cmd.Flags().StringVar(&o.CertEmail, "cert-email", "",
"the email expected in a valid Fulcio certificate")
}
25 changes: 8 additions & 17 deletions cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,15 @@ import (
// VerifyOptions is the top level wrapper for the `verify` command.
type VerifyOptions struct {
Key string
Cert string
CertEmail string // TODO: merge into fulcio option as read mode?
CheckClaims bool
Attachment string
Output string
SignatureRef string
LocalImage bool

SecurityKey SecurityKeyOptions
Rekor RekorOptions
// TODO: this seems like it should have the Fulcio options.
SecurityKey SecurityKeyOptions
CertVerify CertVerifyOptions
Rekor RekorOptions
Registry RegistryOptions
SignatureDigest SignatureDigestOptions
AnnotationOptions
Expand All @@ -44,19 +42,14 @@ var _ Interface = (*VerifyOptions)(nil)
func (o *VerifyOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.CertVerify.AddFlags(cmd)
o.Registry.AddFlags(cmd)
o.SignatureDigest.AddFlags(cmd)
o.AnnotationOptions.AddFlags(cmd)

cmd.Flags().StringVar(&o.Key, "key", "",
"path to the public key file, KMS URI or Kubernetes Secret")

cmd.Flags().StringVar(&o.Cert, "cert", "",
"path to the public certificate")

cmd.Flags().StringVar(&o.CertEmail, "cert-email", "",
"the email expected in a valid fulcio cert")

cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true,
"whether to check the claims found")

Expand All @@ -81,7 +74,7 @@ type VerifyAttestationOptions struct {

SecurityKey SecurityKeyOptions
Rekor RekorOptions
Fulcio FulcioOptions // TODO: the original command did not use id token, mistake?
CertVerify CertVerifyOptions
Registry RegistryOptions
Predicate PredicateRemoteOptions
Policies []string
Expand All @@ -94,7 +87,7 @@ var _ Interface = (*VerifyAttestationOptions)(nil)
func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.Fulcio.AddFlags(cmd)
o.CertVerify.AddFlags(cmd)
o.Registry.AddFlags(cmd)
o.Predicate.AddFlags(cmd)

Expand All @@ -117,10 +110,10 @@ func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) {
// VerifyBlobOptions is the top level wrapper for the `verify blob` command.
type VerifyBlobOptions struct {
Key string
Cert string
Signature string

SecurityKey SecurityKeyOptions
CertVerify CertVerifyOptions
Rekor RekorOptions
Registry RegistryOptions
}
Expand All @@ -131,14 +124,12 @@ var _ Interface = (*VerifyBlobOptions)(nil)
func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.CertVerify.AddFlags(cmd)
o.Registry.AddFlags(cmd)

cmd.Flags().StringVar(&o.Key, "key", "",
"path to the public key file, KMS URI or Kubernetes Secret")

cmd.Flags().StringVar(&o.Cert, "cert", "",
"path to the public certificate")

cmd.Flags().StringVar(&o.Signature, "signature", "",
"signature content or path or remote URL")
}
Expand Down
10 changes: 6 additions & 4 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ against the transparency log.`,
RegistryOptions: o.Registry,
CheckClaims: o.CheckClaims,
KeyRef: o.Key,
CertRef: o.Cert,
CertEmail: o.CertEmail,
CertRef: o.CertVerify.Cert,
CertEmail: o.CertVerify.CertEmail,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Output: o.Output,
Expand Down Expand Up @@ -159,12 +159,13 @@ against the transparency log.`,
v := verify.VerifyAttestationCommand{
RegistryOptions: o.Registry,
CheckClaims: o.CheckClaims,
CertRef: o.CertVerify.Cert,
CertEmail: o.CertVerify.CertEmail,
KeyRef: o.Key,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Output: o.Output,
RekorURL: o.Rekor.URL,
FulcioURL: o.Fulcio.URL,
PredicateType: o.Predicate.Type,
Policies: o.Policies,
LocalImage: o.LocalImage,
Expand Down Expand Up @@ -236,7 +237,8 @@ The blob may be specified as a path to a file or - for stdin.`,
Slot: o.SecurityKey.Slot,
RekorURL: o.Rekor.URL,
}
if err := verify.VerifyBlobCmd(cmd.Context(), ko, o.Cert, o.Signature, args[0]); err != nil {
if err := verify.VerifyBlobCmd(cmd.Context(), ko, o.CertVerify.Cert,
o.CertVerify.CertEmail, o.Signature, args[0]); err != nil {
return errors.Wrapf(err, "verifying blob %s", args)
}
return nil
Expand Down
23 changes: 19 additions & 4 deletions cmd/cosign/cli/verify/verify_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package verify

import (
"context"
"crypto"
"crypto/ecdsa"
"encoding/base64"
"encoding/json"
"flag"
Expand All @@ -30,6 +32,7 @@ import (
"github.com/sigstore/cosign/pkg/cosign/pkcs11key"
"github.com/sigstore/cosign/pkg/cosign/rego"
"github.com/sigstore/cosign/pkg/oci"
"github.com/sigstore/sigstore/pkg/signature"

"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
"github.com/sigstore/cosign/cmd/cosign/cli/options"
Expand All @@ -45,11 +48,12 @@ import (
type VerifyAttestationCommand struct {
options.RegistryOptions
CheckClaims bool
CertRef string
CertEmail string
KeyRef string
Sk bool
Slot string
Output string
FulcioURL string
RekorURL string
PredicateType string
Policies []string
Expand All @@ -62,7 +66,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
return flag.ErrHelp
}

if !options.OneOf(c.KeyRef, c.Sk) && !options.EnableExperimental() {
if !options.OneOf(c.KeyRef, c.Sk, c.CertRef) && !options.EnableExperimental() {
return &options.KeyParseError{}
}

Expand All @@ -72,6 +76,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
}
co := &cosign.CheckOpts{
RegistryClientOpts: ociremoteOpts,
CertEmail: c.CertEmail,
}
if c.CheckClaims {
co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
Expand All @@ -89,7 +94,8 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
keyRef := c.KeyRef

// Keys are optional!
if keyRef != "" {
switch {
case keyRef != "":
co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, keyRef)
if err != nil {
return errors.Wrap(err, "loading public key")
Expand All @@ -98,7 +104,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
if ok {
defer pkcs11Key.Close()
}
} else if c.Sk {
case c.Sk:
sk, err := pivkey.GetKeyWithSlot(c.Slot)
if err != nil {
return errors.Wrap(err, "opening piv token")
Expand All @@ -108,6 +114,15 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
if err != nil {
return errors.Wrap(err, "initializing piv token verifier")
}
case c.CertRef != "":
cert, err := loadCertFromFileOrURL(c.CertRef)
if err != nil {
return errors.Wrap(err, "loading certificate from reference")
}
co.SigVerifier, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return errors.Wrap(err, "creating certificate verifier")
}
}

for _, imageRef := range images {
Expand Down
44 changes: 14 additions & 30 deletions cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ func isb64(data []byte) bool {
}

// nolint
func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, sigRef, blobRef string) error {
var pubKey signature.Verifier
func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, sigRef, blobRef string) error {
var verifier signature.Verifier
var cert *x509.Certificate

if !options.OneOf(ko.KeyRef, ko.Sk, certRef) && !options.EnableExperimental() {
Expand All @@ -75,11 +75,11 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, sigRef, blobRe
// Keys are optional!
switch {
case ko.KeyRef != "":
pubKey, err = sigs.PublicKeyFromKeyRef(ctx, ko.KeyRef)
verifier, err = sigs.PublicKeyFromKeyRef(ctx, ko.KeyRef)
if err != nil {
return errors.Wrap(err, "loading public key")
}
pkcs11Key, ok := pubKey.(*pkcs11key.Key)
pkcs11Key, ok := verifier.(*pkcs11key.Key)
if ok {
defer pkcs11Key.Close()
}
Expand All @@ -89,7 +89,7 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, sigRef, blobRe
return errors.Wrap(err, "opening piv token")
}
defer sk.Close()
pubKey, err = sk.Verifier()
verifier, err = sk.Verifier()
if err != nil {
return errors.Wrap(err, "loading public key from token")
}
Expand All @@ -98,7 +98,7 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, sigRef, blobRe
if err != nil {
return err
}
pubKey, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
verifier, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return err
}
Expand Down Expand Up @@ -126,25 +126,25 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, sigRef, blobRe
if err != nil {
return err
}

co := &cosign.CheckOpts{
RootCerts: fulcio.GetRoots(),
CertEmail: certEmail,
}
cert = certs[0]
pubKey, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
verifier, err = cosign.ValidateAndUnpackCert(cert, co)
if err != nil {
return err
}
}

// verify the signature
if err := pubKey.VerifySignature(bytes.NewReader([]byte(sig)), bytes.NewReader(blobBytes)); err != nil {
return err
}

// verify the cert
if err := verifyCert(cert); err != nil {
if err := verifier.VerifySignature(bytes.NewReader([]byte(sig)), bytes.NewReader(blobBytes)); err != nil {
return err
}

// verify the rekor entry
if err := verifyRekorEntry(ctx, ko, pubKey, cert, b64sig, blobBytes); err != nil {
if err := verifyRekorEntry(ctx, ko, verifier, cert, b64sig, blobBytes); err != nil {
return err
}

Expand Down Expand Up @@ -196,22 +196,6 @@ func payloadBytes(blobRef string) ([]byte, error) {
return blobBytes, nil
}

func verifyCert(cert *x509.Certificate) error {
if cert == nil {
return nil
}
if err := cosign.TrustedCert(cert, fulcio.GetRoots()); err != nil {
return err
}
fmt.Fprintln(os.Stderr, "Certificate is trusted by Fulcio Root CA")
fmt.Fprintln(os.Stderr, "Email:", cert.EmailAddresses)
for _, uri := range cert.URIs {
fmt.Fprintf(os.Stderr, "URI: %s://%s%s\n", uri.Scheme, uri.Host, uri.Path)
}
fmt.Fprintln(os.Stderr, "Issuer: ", sigs.CertIssuerExtension(cert))
return nil
}

func verifyRekorEntry(ctx context.Context, ko sign.KeyOpts, pubKey signature.Verifier, cert *x509.Certificate, b64sig string, blobBytes []byte) error {
if !options.EnableExperimental() {
return nil
Expand Down
2 changes: 1 addition & 1 deletion doc/cosign_dockerfile_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion doc/cosign_manifest_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions doc/cosign_verify-attestation.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8cace74

Please sign in to comment.