Skip to content

Commit

Permalink
Provide certificate flags to all verify commands (#1305)
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 <hblauzvern@google.com>
  • Loading branch information
haydentherapper authored Jan 13, 2022
1 parent d58fc63 commit 46cf94b
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 46cf94b

Please sign in to comment.