Skip to content

Commit

Permalink
Add flag for manually specifying a hash algo when verifying
Browse files Browse the repository at this point in the history
While, at the time of writing, it's not possible to manually specify the
signature digest algorithm which should be used when signing an image,
some KMS providers have key types which force a digest algorithm which
isn't sha256; e.g. GCP KMS with RSA4096 / SHA512 keys.

Cosign will happily use these keys for signing and will infer the digest
algorithm based on what the KMS provider mandates, leading to a
situation where cosign generates signatures it can't verify.

This commit adds a new flag which allows the user to manually specify
one of 4 commonly-used secure hashes when using `cosign verify`

Signed-off-by: Ashley Davis <ashley.davis@jetstack.io>
  • Loading branch information
SgtCoDFish committed Nov 19, 2021
1 parent ff2104c commit bc75f0e
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 16 deletions.
85 changes: 85 additions & 0 deletions cmd/cosign/cli/options/signature_digest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// Copyright 2021 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 (
"crypto"
"fmt"
"sort"
"strings"

_ "crypto/sha256" // for sha224 + sha256
_ "crypto/sha512" // for sha384 + sha512

"github.com/spf13/cobra"
)

var supportedSignatureAlgorithms = map[string]crypto.Hash{
"sha224": crypto.SHA224,
"sha256": crypto.SHA256,
"sha384": crypto.SHA384,
"sha512": crypto.SHA512,
}

func supportedSignatureAlgorithmNames() []string {
names := make([]string, 0, len(supportedSignatureAlgorithms))

for name := range supportedSignatureAlgorithms {
names = append(names, name)
}

sort.Strings(names)

return names
}

// SignatureDigestOptions holds options for specifying which digest algorithm should
// be used when processing a signature.
type SignatureDigestOptions struct {
AlgorithmName string
}

var _ Interface = (*SignatureDigestOptions)(nil)

// AddFlags implements Interface
func (o *SignatureDigestOptions) AddFlags(cmd *cobra.Command) {
validSignatureDigestAlgorithms := strings.Join(supportedSignatureAlgorithmNames(), "|")

cmd.Flags().StringVar(&o.AlgorithmName, "signature-digest-algorithm", "sha256",
fmt.Sprintf("digest algorithm to use when processing a signature (%s)", validSignatureDigestAlgorithms))
}

// HashAlgorithm converts the algorithm's name - provided as a string - into a crypto.Hash algorithm.
// Returns an error if the algorithm name doesn't match a supported algorithm, and defaults to SHA256
// in the event that the given algorithm is invalid.
func (o *SignatureDigestOptions) HashAlgorithm() (crypto.Hash, error) {
normalizedAlgo := strings.ToLower(strings.TrimSpace(o.AlgorithmName))

if normalizedAlgo == "" {
return crypto.SHA256, nil
}

algo, exists := supportedSignatureAlgorithms[normalizedAlgo]
if !exists {
return crypto.SHA256, fmt.Errorf("unknown digest algorithm: %s", o.AlgorithmName)
}

if !algo.Available() {
return crypto.SHA256, fmt.Errorf("hash %q is not available on this platform", o.AlgorithmName)
}

return algo, nil
}
4 changes: 3 additions & 1 deletion cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ type VerifyOptions struct {
SecurityKey SecurityKeyOptions
Rekor RekorOptions
// TODO: this seems like it should have the Fulcio options.
Registry RegistryOptions
Registry RegistryOptions
SignatureDigest SignatureDigestOptions
AnnotationOptions
}

Expand All @@ -41,6 +42,7 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.Registry.AddFlags(cmd)
o.SignatureDigest.AddFlags(cmd)
o.AnnotationOptions.AddFlags(cmd)

cmd.Flags().StringVar(&o.Key, "key", "",
Expand Down
15 changes: 14 additions & 1 deletion cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@ against the transparency log.`,
# (experimental) additionally, verify with the transparency log
COSIGN_EXPERIMENTAL=1 cosign verify <IMAGE>
# verify image with public key
# verify image with an on-disk public key
cosign verify --key cosign.pub <IMAGE>
# verify image with an on-disk public key, manually specifying the
# signature digest algorithm
cosign verify --key cosign.pub --signature-digest-algorithm sha512 <IMAGE>
# verify image with public key provided by URL
cosign verify --key https://host.for/[FILE] <IMAGE>
Expand All @@ -73,6 +77,12 @@ against the transparency log.`,
if err != nil {
return err
}

hashAlgorithm, err := o.SignatureDigest.HashAlgorithm()
if err != nil {
return err
}

v := verify.VerifyCommand{
RegistryOptions: o.Registry,
CheckClaims: o.CheckClaims,
Expand All @@ -84,7 +94,10 @@ against the transparency log.`,
RekorURL: o.Rekor.URL,
Attachment: o.Attachment,
Annotations: annotations,

HashAlgorithm: hashAlgorithm,
}

return v.Exec(cmd.Context(), args)
},
}
Expand Down
10 changes: 9 additions & 1 deletion cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package verify

import (
"context"
"crypto"
"encoding/json"
"flag"
"fmt"
Expand Down Expand Up @@ -50,6 +51,8 @@ type VerifyCommand struct {
RekorURL string
Attachment string
Annotations sigs.AnnotationsMap

HashAlgorithm crypto.Hash
}

// Exec runs the verification command
Expand All @@ -65,6 +68,11 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
return flag.ErrHelp
}

// always default to sha256 if the algorithm hasn't been explicitly set
if c.HashAlgorithm == 0 {
c.HashAlgorithm = crypto.SHA256
}

if !options.OneOf(c.KeyRef, c.Sk) && !options.EnableExperimental() {
return &options.KeyParseError{}
}
Expand All @@ -89,7 +97,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
// Keys are optional!
var pubKey signature.Verifier
if keyRef != "" {
pubKey, err = sigs.PublicKeyFromKeyRef(ctx, keyRef)
pubKey, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, c.HashAlgorithm)
if err != nil {
return errors.Wrap(err, "loading public key")
}
Expand Down
1 change: 1 addition & 0 deletions doc/cosign_dockerfile_verify.md

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

1 change: 1 addition & 0 deletions doc/cosign_manifest_verify.md

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

7 changes: 6 additions & 1 deletion doc/cosign_verify.md

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

26 changes: 19 additions & 7 deletions pkg/signature/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@ import (
"github.com/sigstore/sigstore/pkg/signature/kms"
)

// LoadPublicKey is a wrapper for VerifierForKeyRef, hardcoding SHA256 as the hash algorithm
func LoadPublicKey(ctx context.Context, keyRef string) (verifier signature.Verifier, err error) {
return VerifierForKeyRef(ctx, keyRef, crypto.SHA256)
}

// VerifierForKeyRef parses the given keyRef, loads the key and returns an appropriate
// verifier using the provided hash algorithm
func VerifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (verifier signature.Verifier, err error) {
// The key could be plaintext, in a file, at a URL, or in KMS.
if kmsKey, err := kms.Get(ctx, keyRef, crypto.SHA256); err == nil {
if kmsKey, err := kms.Get(ctx, keyRef, hashAlgorithm); err == nil {
// KMS specified
return kmsKey, nil
}
Expand All @@ -53,7 +60,8 @@ func LoadPublicKey(ctx context.Context, keyRef string) (verifier signature.Verif
if err != nil {
return nil, errors.Wrap(err, "pem to public key")
}
return signature.LoadVerifier(pubKey, crypto.SHA256)

return signature.LoadVerifier(pubKey, hashAlgorithm)
}

func loadKey(keyPath string, pf cosign.PassFunc) (*signature.ECDSASignerVerifier, error) {
Expand All @@ -68,13 +76,13 @@ func loadKey(keyPath string, pf cosign.PassFunc) (*signature.ECDSASignerVerifier
return cosign.LoadECDSAPrivateKey(kb, pass)
}

func loadPublicKey(raw []byte) (signature.Verifier, error) {
func loadPublicKey(raw []byte, hashAlgorithm crypto.Hash) (signature.Verifier, error) {
// PEM encoded file.
ed, err := cosign.PemToECDSAKey(raw)
if err != nil {
return nil, errors.Wrap(err, "pem to ecdsa")
}
return signature.LoadECDSAVerifier(ed, crypto.SHA256)
return signature.LoadECDSAVerifier(ed, hashAlgorithm)
}

func SignerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.Signer, error) {
Expand Down Expand Up @@ -145,14 +153,18 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass
}

func PublicKeyFromKeyRef(ctx context.Context, keyRef string) (signature.Verifier, error) {
return PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, crypto.SHA256)
}

func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (signature.Verifier, error) {
if strings.HasPrefix(keyRef, kubernetes.KeyReference) {
s, err := kubernetes.GetKeyPairSecret(ctx, keyRef)
if err != nil {
return nil, err
}

if len(s.Data) > 0 {
return loadPublicKey(s.Data["cosign.pub"])
return loadPublicKey(s.Data["cosign.pub"], hashAlgorithm)
}
}

Expand Down Expand Up @@ -191,11 +203,11 @@ func PublicKeyFromKeyRef(ctx context.Context, keyRef string) (signature.Verifier
}

if len(pubKey) > 0 {
return loadPublicKey([]byte(pubKey))
return loadPublicKey([]byte(pubKey), hashAlgorithm)
}
}

return LoadPublicKey(ctx, keyRef)
return VerifierForKeyRef(ctx, keyRef, hashAlgorithm)
}

func PublicKeyPem(key signature.PublicKeyProvider, pkOpts ...signature.PublicKeyOption) ([]byte, error) {
Expand Down
12 changes: 7 additions & 5 deletions test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package test
import (
"bytes"
"context"
"crypto"
"encoding/base64"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -73,11 +74,12 @@ var passFunc = func(_ bool) ([]byte, error) {

var verify = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string) error {
cmd := cliverify.VerifyCommand{
KeyRef: keyRef,
RekorURL: rekorURL,
CheckClaims: checkClaims,
Annotations: sigs.AnnotationsMap{Annotations: annotations},
Attachment: attachment,
KeyRef: keyRef,
RekorURL: rekorURL,
CheckClaims: checkClaims,
Annotations: sigs.AnnotationsMap{Annotations: annotations},
Attachment: attachment,
HashAlgorithm: crypto.SHA256,
}

args := []string{imageRef}
Expand Down

0 comments on commit bc75f0e

Please sign in to comment.