Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flag for manually specifying a hash algo when verifying #1071

Merged
merged 1 commit into from
Nov 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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