From 5a5e09f123d0986f6f9b51dff11036f09184e054 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Wed, 17 Jan 2024 18:12:34 +0100 Subject: [PATCH 1/4] Used LoadOptions for more flexibility * Use ED25519ph algorithm with sign/verify-blob commands Signed-off-by: Riccardo Schirone Signed-off-by: William Woodruff --- cmd/cosign/cli/sign/sign.go | 32 ++- cmd/cosign/cli/sign/sign_blob.go | 52 ++++- cmd/cosign/cli/verify/verify.go | 16 +- cmd/cosign/cli/verify/verify_blob.go | 13 +- .../cli/verify/verify_blob_attestation.go | 3 +- go.mod | 15 +- go.sum | 41 ++-- internal/pkg/cosign/common.go | 17 +- internal/pkg/cosign/common_test.go | 3 +- internal/pkg/cosign/rekor/signer.go | 15 +- internal/pkg/cosign/rekor/signer_test.go | 2 +- pkg/cosign/keys.go | 15 +- pkg/cosign/tlog.go | 63 +++++- pkg/cosign/verify.go | 188 +++++++++++++----- pkg/signature/keys.go | 47 ++++- 15 files changed, 378 insertions(+), 144 deletions(-) diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index 122aaa3339b..348a985e03f 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -138,7 +138,12 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO ctx, cancel := context.WithTimeout(context.Background(), ro.Timeout) defer cancel() - sv, err := SignerFromKeyOpts(ctx, signOpts.Cert, signOpts.CertChain, ko) + svOptions := []signature.LoadOption{ + signatureoptions.WithHash(crypto.SHA256), + signatureoptions.WithED25519ph(), + } + + sv, err := signerFromKeyOptsWithSVOpts(ctx, signOpts.Cert, signOpts.CertChain, ko, svOptions...) if err != nil { return fmt.Errorf("getting signer: %w", err) } @@ -261,7 +266,12 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti if err != nil { return err } - s = irekor.NewSigner(s, rClient) + + hashAlgorithm, err := getHashAlgorithmFromSignerVerifier(sv) + if err != nil { + return err + } + s = irekor.NewSigner(s, rClient, hashAlgorithm) } ociSig, _, err := s.Sign(ctx, bytes.NewReader(payload)) @@ -391,8 +401,8 @@ func signerFromSecurityKey(ctx context.Context, keySlot string) (*SignerVerifier }, nil } -func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef string, passFunc cosign.PassFunc) (*SignerVerifier, error) { - k, err := sigs.SignerVerifierFromKeyRef(ctx, keyRef, passFunc) +func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef string, passFunc cosign.PassFunc, opts ...signature.LoadOption) (*SignerVerifier, error) { + k, err := sigs.SignerVerifierFromKeyRefWithOpts(ctx, keyRef, passFunc, opts...) if err != nil { return nil, fmt.Errorf("reading key: %w", err) } @@ -521,12 +531,12 @@ func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef strin return certSigner, nil } -func signerFromNewKey() (*SignerVerifier, error) { +func signerFromNewKey(svOpts ...signature.LoadOption) (*SignerVerifier, error) { privKey, err := cosign.GeneratePrivateKey() if err != nil { return nil, fmt.Errorf("generating cert: %w", err) } - sv, err := signature.LoadECDSASignerVerifier(privKey, crypto.SHA256) + sv, err := signature.LoadSignerVerifierWithOpts(privKey, svOpts...) if err != nil { return nil, err } @@ -559,7 +569,7 @@ func keylessSigner(ctx context.Context, ko options.KeyOpts, sv *SignerVerifier) }, nil } -func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts) (*SignerVerifier, error) { +func signerFromKeyOptsWithSVOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts, svOpts ...signature.LoadOption) (*SignerVerifier, error) { var sv *SignerVerifier var err error genKey := false @@ -567,11 +577,11 @@ func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath strin case ko.Sk: sv, err = signerFromSecurityKey(ctx, ko.Slot) case ko.KeyRef != "": - sv, err = signerFromKeyRef(ctx, certPath, certChainPath, ko.KeyRef, ko.PassFunc) + sv, err = signerFromKeyRef(ctx, certPath, certChainPath, ko.KeyRef, ko.PassFunc, svOpts...) default: genKey = true ui.Infof(ctx, "Generating ephemeral keys...") - sv, err = signerFromNewKey() + sv, err = signerFromNewKey(svOpts...) } if err != nil { return nil, err @@ -584,6 +594,10 @@ func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath strin return sv, nil } +func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts) (*SignerVerifier, error) { + return signerFromKeyOptsWithSVOpts(ctx, certPath, certChainPath, ko) +} + type SignerVerifier struct { Cert []byte Chain []byte diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index b60db5b7a28..e7a4467daad 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -17,7 +17,10 @@ package sign import ( "context" - "crypto/sha256" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" "encoding/base64" "encoding/json" "fmt" @@ -33,9 +36,28 @@ import ( "github.com/sigstore/cosign/v2/pkg/cosign" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) +func getHashAlgorithmFromSignerVerifier(sv *SignerVerifier) (crypto.Hash, error) { + publicKey, err := sv.SignerVerifier.PublicKey() + if err != nil { + return crypto.Hash(0), err + } + + switch publicKey.(type) { + case *ecdsa.PublicKey: + return crypto.SHA256, nil + case *rsa.PublicKey: + return crypto.SHA256, nil + case ed25519.PublicKey: + return crypto.SHA512, nil + default: + return crypto.Hash(0), fmt.Errorf("unsupported public key type") + } +} + // nolint func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string, b64 bool, outputSignature string, outputCertificate string, tlogUpload bool) ([]byte, error) { var payload internal.HashReader @@ -44,26 +66,36 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string ctx, cancel := context.WithTimeout(context.Background(), ro.Timeout) defer cancel() + svOptions := []signature.LoadOption{ + signatureoptions.WithHash(crypto.SHA256), + signatureoptions.WithED25519ph(), + } + + sv, err := signerFromKeyOptsWithSVOpts(ctx, "", "", ko, svOptions...) + if err != nil { + return nil, err + } + defer sv.Close() + + hashAlgorithm, err := getHashAlgorithmFromSignerVerifier(sv) + if err != nil { + return nil, err + } + if payloadPath == "-" { - payload = internal.NewHashReader(os.Stdin, sha256.New()) + payload = internal.NewHashReader(os.Stdin, hashAlgorithm) } else { ui.Infof(ctx, "Using payload from: %s", payloadPath) f, err := os.Open(filepath.Clean(payloadPath)) if err != nil { return nil, err } - payload = internal.NewHashReader(f, sha256.New()) + payload = internal.NewHashReader(f, hashAlgorithm) } if err != nil { return nil, err } - sv, err := SignerFromKeyOpts(ctx, "", "", ko) - if err != nil { - return nil, err - } - defer sv.Close() - sig, err := sv.SignMessage(&payload, signatureoptions.WithContext(ctx)) if err != nil { return nil, fmt.Errorf("signing blob: %w", err) @@ -123,7 +155,7 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string if err != nil { return nil, err } - entry, err := cosign.TLogUpload(ctx, rekorClient, sig, &payload, rekorBytes) + entry, err := cosign.TLogUploadWithCustomHash(ctx, rekorClient, sig, &payload, rekorBytes) if err != nil { return nil, err } diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index de91d9229a8..6b8ef256ab6 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -44,6 +44,7 @@ import ( sigs "github.com/sigstore/cosign/v2/pkg/signature" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" + signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" "github.com/sigstore/sigstore/pkg/signature/payload" ) @@ -214,11 +215,16 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { } } + svOpts := []signature.LoadOption{ + signatureoptions.WithHash(crypto.SHA256), + signatureoptions.WithED25519ph(), + } + // Keys are optional! var pubKey signature.Verifier switch { case keyRef != "": - pubKey, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, c.HashAlgorithm) + pubKey, err = sigs.PublicKeyFromKeyRefWithOpts(ctx, keyRef, svOpts...) if err != nil { return fmt.Errorf("loading public key: %w", err) } @@ -251,7 +257,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { if err != nil { return fmt.Errorf("getting Fulcio intermediates: %w", err) } - pubKey, err = cosign.ValidateAndUnpackCert(cert, co) + pubKey, err = cosign.ValidateAndUnpackCertWithOpts(cert, co, cosign.WithSignerVerifierOptions(svOpts...)) if err != nil { return err } @@ -261,7 +267,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { if err != nil { return err } - pubKey, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co) + pubKey, err = cosign.ValidateAndUnpackCertWithOpts(cert, co, cosign.WithChain(chain), cosign.WithSignerVerifierOptions(svOpts...)) if err != nil { return err } @@ -286,7 +292,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { for _, img := range images { if c.LocalImage { - verified, bundleVerified, err := cosign.VerifyLocalImageSignatures(ctx, img, co) + verified, bundleVerified, err := cosign.VerifyLocalImageSignaturesWithOpts(ctx, img, co, svOpts...) if err != nil { return err } @@ -302,7 +308,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { return fmt.Errorf("resolving attachment type %s for image %s: %w", c.Attachment, img, err) } - verified, bundleVerified, err := cosign.VerifyImageSignatures(ctx, ref, co) + verified, bundleVerified, err := cosign.VerifyImageSignaturesWithOpts(ctx, ref, co, svOpts...) if err != nil { return cosignError.WrapError(err) } diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index 9648ef3613a..e53eb8993d9 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -41,6 +41,8 @@ import ( sigs "github.com/sigstore/cosign/v2/pkg/signature" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" + signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) func isb64(data []byte) bool { @@ -170,10 +172,15 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { } } + svOpts := []signature.LoadOption{ + signatureoptions.WithHash(crypto.SHA256), + signatureoptions.WithED25519ph(), + } + // Keys are optional! switch { case c.KeyRef != "": - co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, c.KeyRef) + co.SigVerifier, err = sigs.PublicKeyFromKeyRefWithOpts(ctx, c.KeyRef, svOpts...) if err != nil { return fmt.Errorf("loading public key: %w", err) } @@ -218,7 +225,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { bundleCert, err := loadCertFromPEM(certBytes) if err != nil { // check if cert is actually a public key - co.SigVerifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256) + co.SigVerifier, err = sigs.LoadPublicKeyRawWithOpts(certBytes, svOpts...) if err != nil { return fmt.Errorf("loading verifier from bundle: %w", err) } @@ -297,7 +304,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { if err != nil { return err } - if _, err = cosign.VerifyBlobSignature(ctx, signature, co); err != nil { + if _, err = cosign.VerifyBlobSignatureWithOpts(ctx, signature, co, svOpts...); err != nil { return err } diff --git a/cmd/cosign/cli/verify/verify_blob_attestation.go b/cmd/cosign/cli/verify/verify_blob_attestation.go index 0d9c9b77a61..50e29287362 100644 --- a/cmd/cosign/cli/verify/verify_blob_attestation.go +++ b/cmd/cosign/cli/verify/verify_blob_attestation.go @@ -18,7 +18,6 @@ package verify import ( "context" "crypto" - "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/hex" @@ -118,7 +117,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st } defer f.Close() - payload = internal.NewHashReader(f, sha256.New()) + payload = internal.NewHashReader(f, crypto.SHA256) if _, err := io.ReadAll(&payload); err != nil { return err } diff --git a/go.mod b/go.mod index d331b82aba8..a8ed3cdf4fa 100644 --- a/go.mod +++ b/go.mod @@ -145,19 +145,19 @@ require ( github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-jose/go-jose/v3 v3.0.1 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.22.0 // indirect github.com/go-openapi/errors v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/loads v0.21.5 // indirect - github.com/go-openapi/spec v0.20.13 // indirect - github.com/go-openapi/validate v0.22.3 // indirect + github.com/go-openapi/spec v0.20.14 // indirect + github.com/go-openapi/validate v0.22.6 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang-jwt/jwt/v5 v5.0.0 // indirect + github.com/golang-jwt/jwt/v5 v5.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -270,9 +270,14 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/klog/v2 v2.120.0 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) + +// TODO: REMOVE ME +replace github.com/sigstore/sigstore => github.com/trail-of-forks/sigstore v0.0.0-20240129151206-cff4abcde12e + +replace github.com/sigstore/rekor => github.com/trail-of-forks/rekor v0.0.0-20240129163653-3ac4c89bd056 diff --git a/go.sum b/go.sum index f66fd5b2d4c..ce0d2df33d2 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ cuelang.org/go v0.7.0 h1:gMztinxuKfJwMIxtboFsNc6s8AxwJGgsJV+3CuLffHI= cuelang.org/go v0.7.0/go.mod h1:ix+3dM/bSpdG9xg6qpCgnJnpeLtciZu+O/rDbywoMII= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230618160516-e936619f9f18 h1:rd389Q26LMy03gG4anandGFC2LW/xvjga5GezeeaxQk= -github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230618160516-e936619f9f18/go.mod h1:fgJuSBrJP5qZtKqaMJE0hmhS2tmRH+44IkfZvjtaf1M= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 h1:8+4G8JaejP8Xa6W46PzJEwisNgBXMvFcz78N6zG/ARw= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= @@ -285,10 +285,9 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.22.0 h1:wQ/d07nf78HNj4u+KiSY0sT234IAyePPbMgpUjUJQR0= @@ -303,20 +302,20 @@ github.com/go-openapi/loads v0.21.5 h1:jDzF4dSoHw6ZFADCGltDb2lE4F6De7aWSpe+IcsRz github.com/go-openapi/loads v0.21.5/go.mod h1:PxTsnFBoBe+z89riT+wYt3prmSBP6GDAQh2l9H1Flz8= github.com/go-openapi/runtime v0.26.2 h1:elWyB9MacRzvIVgAZCBJmqTi7hBzU0hlKD4IvfX0Zl0= github.com/go-openapi/runtime v0.26.2/go.mod h1:O034jyRZ557uJKzngbMDJXkcKJVzXJiymdSfgejrcRw= -github.com/go-openapi/spec v0.20.13 h1:XJDIN+dLH6vqXgafnl5SUIMnzaChQ6QTo0/UPMbkIaE= -github.com/go-openapi/spec v0.20.13/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= +github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do= +github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= github.com/go-openapi/strfmt v0.22.0 h1:Ew9PnEYc246TwrEspvBdDHS4BVKXy/AOVsfqGDgAcaI= github.com/go-openapi/strfmt v0.22.0/go.mod h1:HzJ9kokGIju3/K6ap8jL+OlGAbjpSv27135Yr9OivU4= github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= -github.com/go-openapi/validate v0.22.3 h1:KxG9mu5HBRYbecRb37KRCihvGGtND2aXziBAv0NNfyI= -github.com/go-openapi/validate v0.22.3/go.mod h1:kVxh31KbfsxU8ZyoHaDbLBWU5CnMdqBUEtadQ2G4d5M= +github.com/go-openapi/validate v0.22.6 h1:+NhuwcEYpWdO5Nm4bmvhGLW0rt1Fcc532Mu3wpypXfo= +github.com/go-openapi/validate v0.22.6/go.mod h1:eaddXSqKeTg5XpSmj1dYyFTK/95n/XHwcOY+BMxKMyM= github.com/go-piv/piv-go v1.11.0 h1:5vAaCdRTFSIW4PeqMbnsDlUZ7odMYWnHBDGdmtU/Zhg= github.com/go-piv/piv-go v1.11.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -github.com/go-rod/rod v0.114.5 h1:1x6oqnslwFVuXJbJifgxspJUd3O4ntaGhRLHt+4Er9c= -github.com/go-rod/rod v0.114.5/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw= +github.com/go-rod/rod v0.114.6 h1:NrutWvLGn6Vea+0ZpLSHQ2cT5UMTqk9DeO+V6xeJBxw= +github.com/go-rod/rod v0.114.6/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -332,8 +331,8 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= @@ -393,8 +392,8 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= -github.com/google/trillian v1.5.3 h1:3ioA5p09qz+U9/t2riklZtaQdZclaStp0/eQNfewNRg= -github.com/google/trillian v1.5.3/go.mod h1:p4tcg7eBr7aT6DxrAoILpc3uXNfcuAvZSnQKonVg+Eo= +github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4= +github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= @@ -609,10 +608,6 @@ github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/sigstore/fulcio v1.4.3 h1:9JcUCZjjVhRF9fmhVuz6i1RyhCc/EGCD7MOl+iqCJLQ= github.com/sigstore/fulcio v1.4.3/go.mod h1:BQPWo7cfxmJwgaHlphUHUpFkp5+YxeJes82oo39m5og= -github.com/sigstore/rekor v1.3.4 h1:RGIia1iOZU7fOiiP2UY/WFYhhp50S5aUm7YrM8aiA6E= -github.com/sigstore/rekor v1.3.4/go.mod h1:1GubPVO2yO+K0m0wt/3SHFqnilr/hWbsjSOe7Vzxrlg= -github.com/sigstore/sigstore v1.8.1 h1:mAVposMb14oplk2h/bayPmIVdzbq2IhCgy4g6R0ZSjo= -github.com/sigstore/sigstore v1.8.1/go.mod h1:02SL1158BSj15bZyOFz7m+/nJzLZfFd9A8ab3Kz7w/E= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1 h1:rEDdUefulkIQaMJyzLwtgPDLNXBIltBABiFYfb0YmgQ= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1/go.mod h1:RCdYCc1IxCYWzh2IdzdA6Yf7JIY0cMRqH08fpQYechw= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.1 h1:DvRWG99QGWZC5mp42SEde2Xke/Q384Idnj2da7yB+Mk= @@ -677,6 +672,10 @@ github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHT github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/trail-of-forks/rekor v0.0.0-20240129163653-3ac4c89bd056 h1:jTVzEjC2Mm5x9mnZwKyspP7ygDyP1BiZHleUvh81V4Y= +github.com/trail-of-forks/rekor v0.0.0-20240129163653-3ac4c89bd056/go.mod h1:KMOYGlQZ96wQ0MfdHvVHjp8mM0fA8Lh6sqtwYkXP5YE= +github.com/trail-of-forks/sigstore v0.0.0-20240129151206-cff4abcde12e h1:EXVlV8GAQ7nct3uYZwga7JVjQ/GfwWAtMKbmYll3sZ8= +github.com/trail-of-forks/sigstore v0.0.0-20240129151206-cff4abcde12e/go.mod h1:rbZxJoss0Qf/OQeIuyqkQxo9jLKZlyLqOGX0BUaK7/I= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= @@ -960,8 +959,8 @@ k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.120.0 h1:z+q5mfovBj1fKFxiRzsa2DsJLPIVMk/KFL81LMOfK+8= +k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= diff --git a/internal/pkg/cosign/common.go b/internal/pkg/cosign/common.go index a3ad5d7efb7..a9e9d14824e 100644 --- a/internal/pkg/cosign/common.go +++ b/internal/pkg/cosign/common.go @@ -15,6 +15,7 @@ package cosign import ( + "crypto" "errors" "hash" "io" @@ -38,14 +39,17 @@ func FileExists(filename string) (bool, error) { // HashReader hashes while it reads. type HashReader struct { - r io.Reader - h hash.Hash + r io.Reader + h hash.Hash + ch crypto.Hash } -func NewHashReader(r io.Reader, h hash.Hash) HashReader { +func NewHashReader(r io.Reader, ch crypto.Hash) HashReader { + h := ch.New() return HashReader{ - r: io.TeeReader(r, h), - h: h, + r: io.TeeReader(r, h), + h: h, + ch: ch, } } @@ -66,3 +70,6 @@ func (h *HashReader) BlockSize() int { return h.h.BlockSize() } // Write implements hash.Hash func (h *HashReader) Write(p []byte) (int, error) { return 0, errors.New("not implemented") } //nolint: revive + +// HashFunc implements cosign.NamedHash +func (h *HashReader) HashFunc() crypto.Hash { return h.ch } diff --git a/internal/pkg/cosign/common_test.go b/internal/pkg/cosign/common_test.go index 4a54109e435..4f38e07865c 100644 --- a/internal/pkg/cosign/common_test.go +++ b/internal/pkg/cosign/common_test.go @@ -17,6 +17,7 @@ package cosign import ( "bytes" + "crypto" "crypto/sha256" "io" "os" @@ -55,7 +56,7 @@ func Test_FileExists(t *testing.T) { func Test_HashReader(t *testing.T) { input := []byte("hello world") - r := NewHashReader(bytes.NewReader(input), sha256.New()) + r := NewHashReader(bytes.NewReader(input), crypto.SHA256) got, err := io.ReadAll(&r) if err != nil { diff --git a/internal/pkg/cosign/rekor/signer.go b/internal/pkg/cosign/rekor/signer.go index 2fa5e2595ef..612f65c6710 100644 --- a/internal/pkg/cosign/rekor/signer.go +++ b/internal/pkg/cosign/rekor/signer.go @@ -17,7 +17,6 @@ package rekor import ( "context" "crypto" - "crypto/sha256" "encoding/base64" "fmt" "io" @@ -49,7 +48,8 @@ func uploadToTlog(rekorBytes []byte, rClient *client.Rekor, upload tlogUploadFn) type signerWrapper struct { inner cosign.Signer - rClient *client.Rekor + rClient *client.Rekor + hashAlgorithm crypto.Hash } var _ cosign.Signer = (*signerWrapper)(nil) @@ -91,11 +91,11 @@ func (rs *signerWrapper) Sign(ctx context.Context, payload io.Reader) (oci.Signa } bundle, err := uploadToTlog(rekorBytes, rs.rClient, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { - checkSum := sha256.New() + checkSum := cosignv1.NewCryptoNamedHash(rs.hashAlgorithm) if _, err := checkSum.Write(payloadBytes); err != nil { return nil, err } - return cosignv1.TLogUpload(ctx, r, sigBytes, checkSum, b) + return cosignv1.TLogUploadWithCustomHash(ctx, r, sigBytes, checkSum, b) }) if err != nil { return nil, nil, err @@ -110,9 +110,10 @@ func (rs *signerWrapper) Sign(ctx context.Context, payload io.Reader) (oci.Signa } // NewSigner returns a `cosign.Signer` which uploads the signature to Rekor -func NewSigner(inner cosign.Signer, rClient *client.Rekor) cosign.Signer { +func NewSigner(inner cosign.Signer, rClient *client.Rekor, hashAlgorithm crypto.Hash) cosign.Signer { return &signerWrapper{ - inner: inner, - rClient: rClient, + inner: inner, + rClient: rClient, + hashAlgorithm: hashAlgorithm, } } diff --git a/internal/pkg/cosign/rekor/signer_test.go b/internal/pkg/cosign/rekor/signer_test.go index 5f3dfa02351..7d6adbd0d9a 100644 --- a/internal/pkg/cosign/rekor/signer_test.go +++ b/internal/pkg/cosign/rekor/signer_test.go @@ -56,7 +56,7 @@ func TestSigner(t *testing.T) { }}}, } - testSigner := NewSigner(payloadSigner, &mClient) + testSigner := NewSigner(payloadSigner, &mClient, crypto.SHA256) testPayload := "test payload" diff --git a/pkg/cosign/keys.go b/pkg/cosign/keys.go index 9adc22525f8..fb6debfe396 100644 --- a/pkg/cosign/keys.go +++ b/pkg/cosign/keys.go @@ -206,6 +206,10 @@ func PemToECDSAKey(pemBytes []byte) (*ecdsa.PublicKey, error) { // TODO(jason): Move this to pkg/signature, the only place it's used, and unimport it. func LoadPrivateKey(key []byte, pass []byte) (signature.SignerVerifier, error) { + return LoadPrivateKeyWithOpts(key, pass) +} + +func LoadPrivateKeyWithOpts(key []byte, pass []byte, opts ...signature.LoadOption) (signature.SignerVerifier, error) { // Decrypt first p, _ := pem.Decode(key) if p == nil { @@ -224,14 +228,5 @@ func LoadPrivateKey(key []byte, pass []byte) (signature.SignerVerifier, error) { if err != nil { return nil, fmt.Errorf("parsing private key: %w", err) } - switch pk := pk.(type) { - case *rsa.PrivateKey: - return signature.LoadRSAPKCS1v15SignerVerifier(pk, crypto.SHA256) - case *ecdsa.PrivateKey: - return signature.LoadECDSASignerVerifier(pk, crypto.SHA256) - case ed25519.PrivateKey: - return signature.LoadED25519SignerVerifier(pk) - default: - return nil, errors.New("unsupported key type") - } + return signature.LoadSignerVerifierWithOpts(pk, opts...) } diff --git a/pkg/cosign/tlog.go b/pkg/cosign/tlog.go index 83d6f61f179..fd445837082 100644 --- a/pkg/cosign/tlog.go +++ b/pkg/cosign/tlog.go @@ -54,6 +54,24 @@ import ( // This is the rekor transparency log public key target name var rekorTargetStr = `rekor.pub` +type NamedHash interface { + hash.Hash + crypto.SignerOpts +} + +type CryptoNamedHash struct { + hash.Hash + hashType crypto.Hash +} + +func (h CryptoNamedHash) HashFunc() crypto.Hash { + return h.hashType +} + +func NewCryptoNamedHash(hashType crypto.Hash) NamedHash { + return CryptoNamedHash{Hash: hashType.New(), hashType: hashType} +} + // TransparencyLogPubKey contains the ECDSA verification key and the current status // of the key according to TUF metadata, whether it's active or expired. type TransparencyLogPubKey struct { @@ -169,9 +187,28 @@ func rekorPubsFromClient(rekorClient *client.Rekor) (*TrustedTransparencyLogPubK return &publicKeys, nil } +type SHA256NamedHash struct { + hash.Hash +} + +func (h SHA256NamedHash) HashFunc() crypto.Hash { + return crypto.SHA256 +} + +func WrapSHA256Hash(hash hash.Hash) NamedHash { + return SHA256NamedHash{Hash: hash} +} + // TLogUpload will upload the signature, public key and payload to the transparency log. func TLogUpload(ctx context.Context, rekorClient *client.Rekor, signature []byte, sha256CheckSum hash.Hash, pemBytes []byte) (*models.LogEntryAnon, error) { - re := rekorEntry(sha256CheckSum, signature, pemBytes) + cryptoChecksum := WrapSHA256Hash(sha256CheckSum) + return TLogUploadWithCustomHash(ctx, rekorClient, signature, cryptoChecksum, pemBytes) +} + +// TLogUploadWithCustomHash will upload the signature, public key and payload to +// the transparency log. Clients can use this to specify a custom hash function. +func TLogUploadWithCustomHash(ctx context.Context, rekorClient *client.Rekor, signature []byte, checksum NamedHash, pemBytes []byte) (*models.LogEntryAnon, error) { + re := rekorEntry(checksum, signature, pemBytes) returnVal := models.Hashedrekord{ APIVersion: swag.String(re.APIVersion()), Spec: re.HashedRekordObj, @@ -230,16 +267,26 @@ func doUpload(ctx context.Context, rekorClient *client.Rekor, pe models.Proposed return nil, errors.New("bad response from server") } -func rekorEntry(sha256CheckSum hash.Hash, signature, pubKey []byte) hashedrekord_v001.V001Entry { - // TODO: Signatures created on a digest using a hash algorithm other than SHA256 will fail - // upload right now. Plumb information on the hash algorithm used when signing from the - // SignerVerifier to use for the HashedRekordObj.Data.Hash.Algorithm. +func rekorEntryHashAlgorithm(checksum crypto.SignerOpts) string { + switch checksum.HashFunc() { + case crypto.SHA256: + return models.HashedrekordV001SchemaDataHashAlgorithmSha256 + case crypto.SHA384: + return models.HashedrekordV001SchemaDataHashAlgorithmSha384 + case crypto.SHA512: + return models.HashedrekordV001SchemaDataHashAlgorithmSha512 + default: + return models.HashedrekordV001SchemaDataHashAlgorithmSha256 + } +} + +func rekorEntry(checksum NamedHash, signature, pubKey []byte) hashedrekord_v001.V001Entry { return hashedrekord_v001.V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Data: &models.HashedrekordV001SchemaData{ Hash: &models.HashedrekordV001SchemaDataHash{ - Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), - Value: swag.String(hex.EncodeToString(sha256CheckSum.Sum(nil))), + Algorithm: swag.String(rekorEntryHashAlgorithm(checksum)), + Value: swag.String(hex.EncodeToString(checksum.Sum(nil))), }, }, Signature: &models.HashedrekordV001SchemaSignature{ @@ -392,7 +439,7 @@ func proposedEntries(b64Sig string, payload, pubKey []byte) ([]models.ProposedEn } proposedEntry = []models.ProposedEntry{dsseEntry, intotoEntry} } else { - sha256CheckSum := sha256.New() + sha256CheckSum := NewCryptoNamedHash(crypto.SHA256) if _, err := sha256CheckSum.Write(payload); err != nil { return nil, err } diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 141a07eea53..6852bb09d8c 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -20,6 +20,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/sha256" + "crypto/sha512" "crypto/x509" "encoding/asn1" "encoding/base64" @@ -164,6 +165,60 @@ type CheckOpts struct { ExperimentalOCI11 bool } +type validateCertOpts struct { + chain []*x509.Certificate + svOpts []signature.LoadOption + pool *x509.CertPool +} + +type ValidateAndUnpackCertOption func(*validateCertOpts) + +func WithChain(chain []*x509.Certificate) ValidateAndUnpackCertOption { + return func(o *validateCertOpts) { + o.chain = chain + } +} + +func WithPool(pool *x509.CertPool) ValidateAndUnpackCertOption { + return func(o *validateCertOpts) { + o.pool = pool + } +} + +func WithSignerVerifierOptions(svOpts ...signature.LoadOption) ValidateAndUnpackCertOption { + return func(o *validateCertOpts) { + o.svOpts = svOpts + } +} + +func makeValidateAndUnpackCertOpts(co *CheckOpts, opts ...ValidateAndUnpackCertOption) (*validateCertOpts, error) { + o := &validateCertOpts{} + for _, opt := range opts { + opt(o) + } + + // Pool Option has precedence over chain option + if o.pool == nil && o.chain != nil { + if len(o.chain) == 0 { + return nil, errors.New("no chain provided to validate certificate") + } + rootPool := x509.NewCertPool() + rootPool.AddCert(o.chain[len(o.chain)-1]) + co.RootCerts = rootPool + + subPool := x509.NewCertPool() + for _, c := range o.chain[:len(o.chain)-1] { + subPool.AddCert(c) + } + o.pool = subPool + } + // If no pool or chain is provided, use the one from the CheckOpts + if o.pool == nil { + o.pool = co.IntermediateCerts + } + return o, nil +} + // This is a substitutable signature verification function that can be used for verifying // attestations of blobs. type signatureVerificationFn func( @@ -220,14 +275,33 @@ func verifyOCISignature(ctx context.Context, verifier signature.Verifier, sig pa // certificate chains up to a trusted root using intermediate certificate chain coming from CheckOpts. // Optionally verifies the subject and issuer of the certificate. func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Verifier, error) { - return ValidateAndUnpackCertWithIntermediates(cert, co, co.IntermediateCerts) + return ValidateAndUnpackCertWithOpts(cert, co) } // ValidateAndUnpackCertWithIntermediates creates a Verifier from a certificate. Verifies that the // certificate chains up to a trusted root using intermediate cert passed as separate argument. // Optionally verifies the subject and issuer of the certificate. func ValidateAndUnpackCertWithIntermediates(cert *x509.Certificate, co *CheckOpts, intermediateCerts *x509.CertPool) (signature.Verifier, error) { - verifier, err := signature.LoadVerifier(cert.PublicKey, crypto.SHA256) + return ValidateAndUnpackCertWithOpts(cert, co, WithPool(intermediateCerts)) +} + +// ValidateAndUnpackCertWithChain creates a Verifier from a certificate. Verifies that the certificate +// chains up to the provided root. Chain should start with the parent of the certificate and end with the root. +// Optionally verifies the subject and issuer of the certificate. +func ValidateAndUnpackCertWithChain(cert *x509.Certificate, chain []*x509.Certificate, co *CheckOpts) (signature.Verifier, error) { + return ValidateAndUnpackCertWithOpts(cert, co, WithChain(chain)) +} + +// ValidateAndUnpackCertWithOpts creates a Verifier from a certificate. Verifies that the certificate +// chains up to a trusted root. Optionally verifies the subject and issuer of the certificate. +// Accept chain and signerVerifierOptions as optional parameters. +func ValidateAndUnpackCertWithOpts(cert *x509.Certificate, co *CheckOpts, opts ...ValidateAndUnpackCertOption) (signature.Verifier, error) { + o, err := makeValidateAndUnpackCertOpts(co, opts...) + if err != nil { + return nil, err + } + + verifier, err := signature.LoadVerifierWithOpts(cert.PublicKey, o.svOpts...) if err != nil { return nil, fmt.Errorf("invalid certificate found on signature: %w", err) } @@ -247,7 +321,7 @@ func ValidateAndUnpackCertWithIntermediates(cert *x509.Certificate, co *CheckOpt } // Now verify the cert, then the signature. - chains, err := TrustedCert(cert, co.RootCerts, intermediateCerts) + chains, err := TrustedCert(cert, co.RootCerts, o.pool) if err != nil { return nil, err @@ -412,26 +486,6 @@ func validateCertExtensions(ce CertExtensions, co *CheckOpts) error { return nil } -// ValidateAndUnpackCertWithChain creates a Verifier from a certificate. Verifies that the certificate -// chains up to the provided root. Chain should start with the parent of the certificate and end with the root. -// Optionally verifies the subject and issuer of the certificate. -func ValidateAndUnpackCertWithChain(cert *x509.Certificate, chain []*x509.Certificate, co *CheckOpts) (signature.Verifier, error) { - if len(chain) == 0 { - return nil, errors.New("no chain provided to validate certificate") - } - rootPool := x509.NewCertPool() - rootPool.AddCert(chain[len(chain)-1]) - co.RootCerts = rootPool - - subPool := x509.NewCertPool() - for _, c := range chain[:len(chain)-1] { - subPool.AddCert(c) - } - co.IntermediateCerts = subPool - - return ValidateAndUnpackCert(cert, co) -} - func tlogValidateEntry(ctx context.Context, client *client.Rekor, rekorPubKeys *TrustedTransparencyLogPubKeys, sig oci.Signature, pem []byte) (*models.LogEntryAnon, error) { b64sig, err := sig.Base64Signature() @@ -486,9 +540,13 @@ func (fos *fakeOCISignatures) Get() ([]oci.Signature, error) { // Note that if co.ExperimentlOCI11 is set, we will attempt to verify // signatures using the experimental OCI 1.1 behavior. func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { + return VerifyImageSignaturesWithOpts(ctx, signedImgRef, co) +} + +func VerifyImageSignaturesWithOpts(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, svOpts ...signature.LoadOption) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { // Try first using OCI 1.1 behavior if experimental flag is set. if co.ExperimentalOCI11 { - verified, bundleVerified, err := verifyImageSignaturesExperimentalOCI(ctx, signedImgRef, co) + verified, bundleVerified, err := verifyImageSignaturesExperimentalOCI(ctx, signedImgRef, co, svOpts...) if err == nil { return verified, bundleVerified, nil } @@ -533,12 +591,18 @@ func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co } } - return verifySignatures(ctx, sigs, h, co) + return verifySignatures(ctx, sigs, h, co, svOpts...) } // VerifyLocalImageSignatures verifies signatures from a saved, local image, without any network calls, returning the verified signatures. // If there were no valid signatures, we return an error. func VerifyLocalImageSignatures(ctx context.Context, path string, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { + return VerifyLocalImageSignaturesWithOpts(ctx, path, co) +} + +// VerifyLocalImageSignaturesWithOpts verifies signatures from a saved, local image, without any network calls, returning the verified signatures. +// If there were no valid signatures, we return an error. +func VerifyLocalImageSignaturesWithOpts(ctx context.Context, path string, co *CheckOpts, svOpts ...signature.LoadOption) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") @@ -582,10 +646,10 @@ func VerifyLocalImageSignatures(ctx context.Context, path string, co *CheckOpts) return nil, false, fmt.Errorf("no signatures associated with the image saved in %s", path) } - return verifySignatures(ctx, sigs, h, co) + return verifySignatures(ctx, sigs, h, co, svOpts...) } -func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { +func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *CheckOpts, svOpts ...signature.LoadOption) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { sl, err := sigs.Get() if err != nil { return nil, false, err @@ -613,7 +677,7 @@ func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *C return } - verified, err := VerifyImageSignature(ctx, sig, h, co) + verified, err := VerifyImageSignatureWithOpts(ctx, sig, h, co, svOpts...) bundlesVerified[index] = verified if err != nil { t.Done(err) @@ -661,7 +725,7 @@ func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *C // we are in experimental mode). // 3. If a certificate is provided, check it's expiration using the transparency log timestamp. func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, - verifyFn signatureVerificationFn, co *CheckOpts) ( + verifyFn signatureVerificationFn, co *CheckOpts, svOpts ...signature.LoadOption) ( bundleVerified bool, err error) { var acceptableRFC3161Time, acceptableRekorBundleTime *time.Time // Timestamps for the signature we accept, or nil if not applicable. @@ -744,7 +808,7 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, if pool == nil { pool = co.IntermediateCerts } - verifier, err = ValidateAndUnpackCertWithIntermediates(cert, co, pool) + verifier, err = ValidateAndUnpackCertWithOpts(cert, co, WithPool(pool), WithSignerVerifierOptions(svOpts...)) if err != nil { return false, err } @@ -821,13 +885,23 @@ func keyBytes(sig oci.Signature, co *CheckOpts) ([]byte, error) { // VerifyBlobSignature verifies a blob signature. func VerifyBlobSignature(ctx context.Context, sig oci.Signature, co *CheckOpts) (bundleVerified bool, err error) { + return VerifyBlobSignatureWithOpts(ctx, sig, co) +} + +// VerifyBlobSignature verifies a blob signature. +func VerifyBlobSignatureWithOpts(ctx context.Context, sig oci.Signature, co *CheckOpts, svOpts ...signature.LoadOption) (bundleVerified bool, err error) { // The hash of the artifact is unused. - return verifyInternal(ctx, sig, v1.Hash{}, verifyOCISignature, co) + return verifyInternal(ctx, sig, v1.Hash{}, verifyOCISignature, co, svOpts...) } // VerifyImageSignature verifies a signature func VerifyImageSignature(ctx context.Context, sig oci.Signature, h v1.Hash, co *CheckOpts) (bundleVerified bool, err error) { - return verifyInternal(ctx, sig, h, verifyOCISignature, co) + return VerifyImageSignatureWithOpts(ctx, sig, h, co) +} + +// VerifyImageSignature verifies a signature +func VerifyImageSignatureWithOpts(ctx context.Context, sig oci.Signature, h v1.Hash, co *CheckOpts, svOpts ...signature.LoadOption) (bundleVerified bool, err error) { + return verifyInternal(ctx, sig, h, verifyOCISignature, co, svOpts...) } func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name.Reference, co *CheckOpts) (oci.Signatures, error) { @@ -1103,12 +1177,23 @@ func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) { if err != nil { return false, fmt.Errorf("computing bundle hash: %w", err) } - h := sha256.Sum256(payload) - payloadHash := hex.EncodeToString(h[:]) - if alg != "sha256" { + var payloadHash string + switch alg.HashFunc() { + case crypto.SHA256: + h := sha256.Sum256(payload) + payloadHash = hex.EncodeToString(h[:]) + case crypto.SHA384: + h := sha512.Sum384(payload) + payloadHash = hex.EncodeToString(h[:]) + case crypto.SHA512: + h := sha512.Sum512(payload) + payloadHash = hex.EncodeToString(h[:]) + default: return false, fmt.Errorf("unexpected algorithm: %q", alg) - } else if bundlehash != payloadHash { + } + + if bundlehash != payloadHash { return false, fmt.Errorf("matching bundle to payload: bundle=%q, payload=%q", bundlehash, payloadHash) } return true, nil @@ -1229,25 +1314,36 @@ func extractEntryImpl(bundleBody string) (rekor_types.EntryImpl, error) { return rekor_types.UnmarshalEntry(pe) } -func bundleHash(bundleBody, _ string) (string, string, error) { +func HashAlgorithmToCryptoHash(hashAlgorithm string) crypto.Hash { + switch hashAlgorithm { + case "sha384": + return crypto.SHA384 + case "sha512": + return crypto.SHA512 + default: + return crypto.SHA256 + } +} + +func bundleHash(bundleBody, _ string) (crypto.Hash, string, error) { ei, err := extractEntryImpl(bundleBody) if err != nil { - return "", "", err + return crypto.Hash(0), "", err } switch entry := ei.(type) { case *dsse_v001.V001Entry: - return *entry.DSSEObj.EnvelopeHash.Algorithm, *entry.DSSEObj.EnvelopeHash.Value, nil + return HashAlgorithmToCryptoHash(*entry.DSSEObj.EnvelopeHash.Algorithm), *entry.DSSEObj.EnvelopeHash.Value, nil case *hashedrekord_v001.V001Entry: - return *entry.HashedRekordObj.Data.Hash.Algorithm, *entry.HashedRekordObj.Data.Hash.Value, nil + return HashAlgorithmToCryptoHash(*entry.HashedRekordObj.Data.Hash.Algorithm), *entry.HashedRekordObj.Data.Hash.Value, nil case *intoto_v001.V001Entry: - return *entry.IntotoObj.Content.Hash.Algorithm, *entry.IntotoObj.Content.Hash.Value, nil + return HashAlgorithmToCryptoHash(*entry.IntotoObj.Content.Hash.Algorithm), *entry.IntotoObj.Content.Hash.Value, nil case *intoto_v002.V002Entry: - return *entry.IntotoObj.Content.Hash.Algorithm, *entry.IntotoObj.Content.Hash.Value, nil + return HashAlgorithmToCryptoHash(*entry.IntotoObj.Content.Hash.Algorithm), *entry.IntotoObj.Content.Hash.Value, nil case *rekord_v001.V001Entry: - return *entry.RekordObj.Data.Hash.Algorithm, *entry.RekordObj.Data.Hash.Value, nil + return HashAlgorithmToCryptoHash(*entry.RekordObj.Data.Hash.Algorithm), *entry.RekordObj.Data.Hash.Value, nil default: - return "", "", errors.New("unsupported type") + return crypto.Hash(0), "", errors.New("unsupported type") } } @@ -1356,7 +1452,7 @@ func correctAnnotations(wanted, have map[string]interface{}) bool { // verifyImageSignaturesExperimentalOCI does all the main cosign checks in a loop, returning the verified signatures. // If there were no valid signatures, we return an error, using OCI 1.1+ behavior. -func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { +func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, svOpts ...signature.LoadOption) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") @@ -1409,5 +1505,5 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name } } - return verifySignatures(ctx, sigs, h, co) + return verifySignatures(ctx, sigs, h, co, svOpts...) } diff --git a/pkg/signature/keys.go b/pkg/signature/keys.go index dfac964725d..3be333a90c9 100644 --- a/pkg/signature/keys.go +++ b/pkg/signature/keys.go @@ -31,6 +31,7 @@ import ( "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/kms" + "github.com/sigstore/sigstore/pkg/signature/options" ) // LoadPublicKey is a wrapper for VerifierForKeyRef, hardcoding SHA256 as the hash algorithm @@ -41,7 +42,18 @@ func LoadPublicKey(ctx context.Context, keyRef string) (verifier signature.Verif // 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) { + return VerifierForKeyRefWithOpts(ctx, keyRef, options.WithHash(hashAlgorithm)) +} + +// VerifierForKeyRefWithOpts parses the given keyRef, loads the key and returns an appropriate +// verifier using the provided hash algorithm and options +func VerifierForKeyRefWithOpts(ctx context.Context, keyRef string, opts ...signature.LoadOption) (verifier signature.Verifier, err error) { // The key could be plaintext, in a file, at a URL, or in KMS. + hashAlgorithm := crypto.SHA256 + for _, o := range opts { + o.ApplyHash(&hashAlgorithm) + } + var perr *kms.ProviderNotFoundError kmsKey, err := kms.Get(ctx, keyRef, hashAlgorithm) switch { @@ -69,10 +81,10 @@ func VerifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto. return nil, fmt.Errorf("pem to public key: %w", err) } - return signature.LoadVerifier(pubKey, hashAlgorithm) + return signature.LoadVerifierWithOpts(pubKey, opts...) } -func loadKey(keyPath string, pf cosign.PassFunc) (signature.SignerVerifier, error) { +func loadKey(keyPath string, pf cosign.PassFunc, opts ...signature.LoadOption) (signature.SignerVerifier, error) { kb, err := blob.LoadFileOrURL(keyPath) if err != nil { return nil, err @@ -84,16 +96,21 @@ func loadKey(keyPath string, pf cosign.PassFunc) (signature.SignerVerifier, erro return nil, err } } - return cosign.LoadPrivateKey(kb, pass) + return cosign.LoadPrivateKeyWithOpts(kb, pass, opts...) } // LoadPublicKeyRaw loads a verifier from a PEM-encoded public key func LoadPublicKeyRaw(raw []byte, hashAlgorithm crypto.Hash) (signature.Verifier, error) { + return LoadPublicKeyRawWithOpts(raw, options.WithHash(hashAlgorithm)) +} + +// LoadPublicKeyRawWithOpts loads a verifier from a PEM-encoded public key with options +func LoadPublicKeyRawWithOpts(raw []byte, opts ...signature.LoadOption) (signature.Verifier, error) { pub, err := cryptoutils.UnmarshalPEMToPublicKey(raw) if err != nil { return nil, err } - return signature.LoadVerifier(pub, hashAlgorithm) + return signature.LoadVerifierWithOpts(pub, opts...) } func SignerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.Signer, error) { @@ -101,6 +118,10 @@ func SignerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (s } func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.SignerVerifier, error) { + return SignerVerifierFromKeyRefWithOpts(ctx, keyRef, pf) +} + +func SignerVerifierFromKeyRefWithOpts(ctx context.Context, keyRef string, pf cosign.PassFunc, opts ...signature.LoadOption) (signature.SignerVerifier, error) { switch { case strings.HasPrefix(keyRef, pkcs11key.ReferenceScheme): pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig() @@ -129,7 +150,7 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass } if len(s.Data) > 0 { - return cosign.LoadPrivateKey(s.Data["cosign.key"], s.Data["cosign.password"]) + return cosign.LoadPrivateKeyWithOpts(s.Data["cosign.key"], s.Data["cosign.password"], opts...) } case strings.HasPrefix(keyRef, gitlab.ReferenceScheme): split := strings.Split(keyRef, "://") @@ -150,7 +171,7 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass return nil, err } - return cosign.LoadPrivateKey([]byte(pk), []byte(pass)) + return cosign.LoadPrivateKeyWithOpts([]byte(pk), []byte(pass), opts...) } if strings.Contains(keyRef, "://") { @@ -165,14 +186,18 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass // ProviderNotFoundError is okay; loadKey handles other URL schemes } - return loadKey(keyRef, pf) + return loadKey(keyRef, pf, opts...) } func PublicKeyFromKeyRef(ctx context.Context, keyRef string) (signature.Verifier, error) { - return PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, crypto.SHA256) + return PublicKeyFromKeyRefWithOpts(ctx, keyRef, options.WithHash(crypto.SHA256)) } func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (signature.Verifier, error) { + return PublicKeyFromKeyRefWithOpts(ctx, keyRef, options.WithHash(hashAlgorithm)) +} + +func PublicKeyFromKeyRefWithOpts(ctx context.Context, keyRef string, opts ...signature.LoadOption) (signature.Verifier, error) { if strings.HasPrefix(keyRef, kubernetes.KeyReference) { s, err := kubernetes.GetKeyPairSecret(ctx, keyRef) if err != nil { @@ -180,7 +205,7 @@ func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlg } if len(s.Data) > 0 { - return LoadPublicKeyRaw(s.Data["cosign.pub"], hashAlgorithm) + return LoadPublicKeyRawWithOpts(s.Data["cosign.pub"], opts...) } } @@ -219,11 +244,11 @@ func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlg } if len(pubKey) > 0 { - return LoadPublicKeyRaw([]byte(pubKey), hashAlgorithm) + return LoadPublicKeyRawWithOpts([]byte(pubKey), opts...) } } - return VerifierForKeyRef(ctx, keyRef, hashAlgorithm) + return VerifierForKeyRefWithOpts(ctx, keyRef, opts...) } func PublicKeyPem(key signature.PublicKeyProvider, pkOpts ...signature.PublicKeyOption) ([]byte, error) { From 0928190b27a0349a1ab57bf9c5a2cceb40f27775 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Fri, 19 Jan 2024 12:37:46 +0100 Subject: [PATCH 2/4] Attempt verification with ED25519 if ED25519ph is used for compatibility Signed-off-by: Riccardo Schirone --- pkg/cosign/verify.go | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 6852bb09d8c..a38f36755f4 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -19,6 +19,7 @@ import ( "context" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/sha256" "crypto/sha512" "crypto/x509" @@ -260,7 +261,7 @@ func verifyOCISignature(ctx context.Context, verifier signature.Verifier, sig pa if err != nil { return err } - signature, err := base64.StdEncoding.DecodeString(b64sig) + decodedSignature, err := base64.StdEncoding.DecodeString(b64sig) if err != nil { return err } @@ -268,7 +269,33 @@ func verifyOCISignature(ctx context.Context, verifier signature.Verifier, sig pa if err != nil { return err } - return verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), options.WithContext(ctx)) + // For compatibility reasons, if ED25519ph is used, we try both ED25519 and ED25519ph. + // Refusing to verify ED25519 signatures (used e.g. by rekord entries) would break compatibility. + // The signature algorithm to use should be uniquely determined before this point. + verificationErr := verifier.VerifySignature(bytes.NewReader(decodedSignature), bytes.NewReader(payload), options.WithContext(ctx)) + if verificationErr == nil { + return nil + } + + switch verifier.(type) { + case *signature.ED25519phVerifier: + publicKey, err := verifier.PublicKey() + if err != nil { + return err + } + + if edPublicKey, ok := publicKey.(ed25519.PublicKey); ok { + altVerifier, err := signature.LoadED25519Verifier(edPublicKey) + if err != nil { + return err + } + + fmt.Fprintf(os.Stderr, "Failed to verify signature with ED25519ph, falling back to ED25519 for backward compatibility.\n") + verificationErr = altVerifier.VerifySignature(bytes.NewReader(decodedSignature), bytes.NewReader(payload), options.WithContext(ctx)) + } + } + + return verificationErr } // ValidateAndUnpackCert creates a Verifier from a certificate. Verifies that the From 53c5a1fd1aecf3e47f263abaac877d56bead833e Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Fri, 19 Jan 2024 17:53:43 +0100 Subject: [PATCH 3/4] Add --signing-algorithm flag Signed-off-by: Riccardo Schirone --- cmd/cosign/cli/generate/generate_key_pair.go | 14 +++- .../cli/generate/generate_key_pair_test.go | 2 +- cmd/cosign/cli/generate_key_pair.go | 5 +- cmd/cosign/cli/options/generate_key_pair.go | 16 ++++- cmd/cosign/cli/options/key.go | 5 +- cmd/cosign/cli/options/sign.go | 12 ++++ cmd/cosign/cli/options/signblob.go | 12 ++++ cmd/cosign/cli/sign.go | 1 + cmd/cosign/cli/sign/sign.go | 41 ++++++----- cmd/cosign/cli/sign/sign_blob.go | 8 +-- cmd/cosign/cli/signblob.go | 1 + go.mod | 5 +- go.sum | 6 +- pkg/cosign/keys.go | 70 +++++++++++++++++-- 14 files changed, 158 insertions(+), 40 deletions(-) diff --git a/cmd/cosign/cli/generate/generate_key_pair.go b/cmd/cosign/cli/generate/generate_key_pair.go index 6311fe07611..fde11c24abe 100644 --- a/cmd/cosign/cli/generate/generate_key_pair.go +++ b/cmd/cosign/cli/generate/generate_key_pair.go @@ -34,6 +34,7 @@ import ( "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/kubernetes" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/kms" ) @@ -43,7 +44,7 @@ var ( ) // nolint -func GenerateKeyPairCmd(ctx context.Context, kmsVal string, outputKeyPrefixVal string, args []string) error { +func GenerateKeyPairCmd(ctx context.Context, kmsVal string, outputKeyPrefixVal string, signatureAlgorithmName string, args []string) error { privateKeyFileName := outputKeyPrefixVal + ".key" publicKeyFileName := outputKeyPrefixVal + ".pub" @@ -86,7 +87,16 @@ func GenerateKeyPairCmd(ctx context.Context, kmsVal string, outputKeyPrefixVal s return fmt.Errorf("undefined provider: %s", provider) } - keys, err := cosign.GenerateKeyPair(GetPass) + signatureAlgorithm, err := signature.ParseSignatureAlgorithmFlag(signatureAlgorithmName) + if err != nil { + return err + } + algorithmDetails, err := signature.GetAlgorithmDetails(signatureAlgorithm) + if err != nil { + return err + } + + keys, err := cosign.GenerateKeyPairWithAlgo(GetPass, algorithmDetails) if err != nil { return err } diff --git a/cmd/cosign/cli/generate/generate_key_pair_test.go b/cmd/cosign/cli/generate/generate_key_pair_test.go index f860382ea4e..33fab4a75d5 100644 --- a/cmd/cosign/cli/generate/generate_key_pair_test.go +++ b/cmd/cosign/cli/generate/generate_key_pair_test.go @@ -57,7 +57,7 @@ func TestGenerationOfKeys(t *testing.T) { // be default it's set to `cosign`, but this is done by the CLI flag // framework if there is no value set by the user when running the // command. - GenerateKeyPairCmd(context.Background(), "", "my-test", nil) + GenerateKeyPairCmd(context.Background(), "", "my-test", "ecdsa-sha2-256-nistp256", nil) checkIfFileExistsThenDelete(privateKeyName, t) checkIfFileExistsThenDelete(publicKeyName, t) diff --git a/cmd/cosign/cli/generate_key_pair.go b/cmd/cosign/cli/generate_key_pair.go index 3600d771d18..c0478632f89 100644 --- a/cmd/cosign/cli/generate_key_pair.go +++ b/cmd/cosign/cli/generate_key_pair.go @@ -34,6 +34,9 @@ func GenerateKeyPair() *cobra.Command { # generate key-pair and write to cosign.key and cosign.pub files cosign generate-key-pair + # generate ED25519 key-pair and write to cosign.key and cosign.pub files + cosign generate-key-pair --signing-algorithm=ed25519-ph + # generate key-pair and write to custom named my-name.key and my-name.pub files cosign generate-key-pair --output-key-prefix my-name @@ -67,7 +70,7 @@ CAVEATS: PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { - return generate.GenerateKeyPairCmd(cmd.Context(), o.KMS, o.OutputKeyPrefix, args) + return generate.GenerateKeyPairCmd(cmd.Context(), o.KMS, o.OutputKeyPrefix, o.SigningAlgorithm, args) }, } diff --git a/cmd/cosign/cli/options/generate_key_pair.go b/cmd/cosign/cli/options/generate_key_pair.go index 4acd94ae100..3a6a1bd8383 100644 --- a/cmd/cosign/cli/options/generate_key_pair.go +++ b/cmd/cosign/cli/options/generate_key_pair.go @@ -16,14 +16,21 @@ package options import ( + "fmt" + "strings" + + "github.com/sigstore/cosign/v2/pkg/cosign" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/signature" "github.com/spf13/cobra" ) // GenerateKeyPairOptions is the top level wrapper for the generate-key-pair command. type GenerateKeyPairOptions struct { // KMS Key Management Service - KMS string - OutputKeyPrefix string + KMS string + OutputKeyPrefix string + SigningAlgorithm string } var _ Interface = (*GenerateKeyPairOptions)(nil) @@ -34,4 +41,9 @@ func (o *GenerateKeyPairOptions) AddFlags(cmd *cobra.Command) { "create key pair in KMS service to use for signing") cmd.Flags().StringVar(&o.OutputKeyPrefix, "output-key-prefix", "cosign", "name used for generated .pub and .key files (defaults to `cosign`)") + + keyAlgorithmTypes := cosign.GetSupportedAlgorithms() + keyAlgorithmHelp := fmt.Sprintf("signing algorithm to use for signing/hashing (allowed %s)", strings.Join(keyAlgorithmTypes, ", ")) + defaultKeyFlag, _ := signature.FormatSignatureAlgorithmFlag(v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256) + cmd.Flags().StringVar(&o.SigningAlgorithm, "signing-algorithm", defaultKeyFlag, keyAlgorithmHelp) } diff --git a/cmd/cosign/cli/options/key.go b/cmd/cosign/cli/options/key.go index 6af69afda37..0fccdce69e5 100644 --- a/cmd/cosign/cli/options/key.go +++ b/cmd/cosign/cli/options/key.go @@ -15,12 +15,15 @@ package options -import "github.com/sigstore/cosign/v2/pkg/cosign" +import ( + "github.com/sigstore/cosign/v2/pkg/cosign" +) type KeyOpts struct { Sk bool Slot string KeyRef string + SigningAlgorithm string FulcioURL string RekorURL string IDToken string diff --git a/cmd/cosign/cli/options/sign.go b/cmd/cosign/cli/options/sign.go index c7cef860723..2c1491eb4ac 100644 --- a/cmd/cosign/cli/options/sign.go +++ b/cmd/cosign/cli/options/sign.go @@ -16,12 +16,19 @@ package options import ( + "fmt" + "strings" + + "github.com/sigstore/cosign/v2/pkg/cosign" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/signature" "github.com/spf13/cobra" ) // SignOptions is the top level wrapper for the sign command. type SignOptions struct { Key string + SigningAlgorithm string Cert string CertChain string Upload bool @@ -67,6 +74,11 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) { "path to the private key file, KMS URI or Kubernetes Secret") _ = cmd.Flags().SetAnnotation("key", cobra.BashCompFilenameExt, []string{}) + keyAlgorithmTypes := cosign.GetSupportedAlgorithms() + keyAlgorithmHelp := fmt.Sprintf("signing algorithm to use for signing/hashing (allowed %s)", strings.Join(keyAlgorithmTypes, ", ")) + defaultKeyFlag, _ := signature.FormatSignatureAlgorithmFlag(v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256) + cmd.Flags().StringVar(&o.SigningAlgorithm, "signing-algorithm", defaultKeyFlag, keyAlgorithmHelp) + cmd.Flags().StringVar(&o.Cert, "certificate", "", "path to the X.509 certificate in PEM format to include in the OCI Signature") _ = cmd.Flags().SetAnnotation("certificate", cobra.BashCompFilenameExt, []string{"cert"}) diff --git a/cmd/cosign/cli/options/signblob.go b/cmd/cosign/cli/options/signblob.go index 7cddde63dfc..48f0e0e11f0 100644 --- a/cmd/cosign/cli/options/signblob.go +++ b/cmd/cosign/cli/options/signblob.go @@ -16,6 +16,12 @@ package options import ( + "fmt" + "strings" + + "github.com/sigstore/cosign/v2/pkg/cosign" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/signature" "github.com/spf13/cobra" ) @@ -23,6 +29,7 @@ import ( // The new output-certificate flag is only in use when COSIGN_EXPERIMENTAL is enabled type SignBlobOptions struct { Key string + SigningAlgorithm string Base64Output bool Output string // deprecated: TODO remove when the output flag is fully deprecated OutputSignature string // TODO: this should be the root output file arg. @@ -57,6 +64,11 @@ func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) { "path to the private key file, KMS URI or Kubernetes Secret") _ = cmd.Flags().SetAnnotation("key", cobra.BashCompFilenameExt, []string{}) + keyAlgorithmTypes := cosign.GetSupportedAlgorithms() + keyAlgorithmHelp := fmt.Sprintf("signing algorithm to use for signing/hashing (allowed %s)", strings.Join(keyAlgorithmTypes, ", ")) + defaultKeyFlag, _ := signature.FormatSignatureAlgorithmFlag(v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256) + cmd.Flags().StringVar(&o.SigningAlgorithm, "signing-algorithm", defaultKeyFlag, keyAlgorithmHelp) + cmd.Flags().BoolVar(&o.Base64Output, "b64", true, "whether to base64 encode the output") diff --git a/cmd/cosign/cli/sign.go b/cmd/cosign/cli/sign.go index e937be58ae9..a540f365938 100644 --- a/cmd/cosign/cli/sign.go +++ b/cmd/cosign/cli/sign.go @@ -102,6 +102,7 @@ race conditions or (worse) malicious tampering. } ko := options.KeyOpts{ KeyRef: o.Key, + SigningAlgorithm: o.SigningAlgorithm, PassFunc: generate.GetPass, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index 348a985e03f..fde8af222b7 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -18,7 +18,6 @@ package sign import ( "bytes" "context" - "crypto" "crypto/x509" "encoding/base64" "encoding/json" @@ -32,6 +31,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" + pb_go_v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio/fulcioverifier" @@ -138,12 +138,7 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO ctx, cancel := context.WithTimeout(context.Background(), ro.Timeout) defer cancel() - svOptions := []signature.LoadOption{ - signatureoptions.WithHash(crypto.SHA256), - signatureoptions.WithED25519ph(), - } - - sv, err := signerFromKeyOptsWithSVOpts(ctx, signOpts.Cert, signOpts.CertChain, ko, svOptions...) + sv, err := SignerFromKeyOpts(ctx, signOpts.Cert, signOpts.CertChain, ko) if err != nil { return fmt.Errorf("getting signer: %w", err) } @@ -531,8 +526,8 @@ func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef strin return certSigner, nil } -func signerFromNewKey(svOpts ...signature.LoadOption) (*SignerVerifier, error) { - privKey, err := cosign.GeneratePrivateKey() +func signerFromNewKey(algorithmDetails signature.AlgorithmDetails, svOpts ...signature.LoadOption) (*SignerVerifier, error) { + privKey, err := cosign.GeneratePrivateKeyWithAlgo(algorithmDetails) if err != nil { return nil, fmt.Errorf("generating cert: %w", err) } @@ -569,9 +564,27 @@ func keylessSigner(ctx context.Context, ko options.KeyOpts, sv *SignerVerifier) }, nil } -func signerFromKeyOptsWithSVOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts, svOpts ...signature.LoadOption) (*SignerVerifier, error) { +func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts) (*SignerVerifier, error) { + var svOpts []signature.LoadOption + signingAlgorithm, err := signature.ParseSignatureAlgorithmFlag(ko.SigningAlgorithm) + if err != nil { + // Default to ECDSA_SHA2_256_NISTP256 if no algorithm is specified + signingAlgorithm = pb_go_v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256 + } + + algorithmDetails, err := signature.GetAlgorithmDetails(signingAlgorithm) + if err != nil { + return nil, err + } + hashAlgorithm := algorithmDetails.GetHashType() + svOpts = []signature.LoadOption{ + signatureoptions.WithHash(hashAlgorithm), + } + if algorithmDetails.GetSignatureAlgorithm() == pb_go_v1.KnownSignatureAlgorithm_ED25519_PH { + svOpts = append(svOpts, signatureoptions.WithED25519ph()) + } + var sv *SignerVerifier - var err error genKey := false switch { case ko.Sk: @@ -581,7 +594,7 @@ func signerFromKeyOptsWithSVOpts(ctx context.Context, certPath string, certChain default: genKey = true ui.Infof(ctx, "Generating ephemeral keys...") - sv, err = signerFromNewKey(svOpts...) + sv, err = signerFromNewKey(algorithmDetails, svOpts...) } if err != nil { return nil, err @@ -594,10 +607,6 @@ func signerFromKeyOptsWithSVOpts(ctx context.Context, certPath string, certChain return sv, nil } -func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts) (*SignerVerifier, error) { - return signerFromKeyOptsWithSVOpts(ctx, certPath, certChainPath, ko) -} - type SignerVerifier struct { Cert []byte Chain []byte diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index e7a4467daad..60ac52204e3 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -36,7 +36,6 @@ import ( "github.com/sigstore/cosign/v2/pkg/cosign" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/sigstore/pkg/cryptoutils" - "github.com/sigstore/sigstore/pkg/signature" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) @@ -66,12 +65,7 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string ctx, cancel := context.WithTimeout(context.Background(), ro.Timeout) defer cancel() - svOptions := []signature.LoadOption{ - signatureoptions.WithHash(crypto.SHA256), - signatureoptions.WithED25519ph(), - } - - sv, err := signerFromKeyOptsWithSVOpts(ctx, "", "", ko, svOptions...) + sv, err := SignerFromKeyOpts(ctx, "", "", ko) if err != nil { return nil, err } diff --git a/cmd/cosign/cli/signblob.go b/cmd/cosign/cli/signblob.go index ce3035a1d15..389797ff84c 100644 --- a/cmd/cosign/cli/signblob.go +++ b/cmd/cosign/cli/signblob.go @@ -70,6 +70,7 @@ func SignBlob() *cobra.Command { } ko := options.KeyOpts{ KeyRef: o.Key, + SigningAlgorithm: o.SigningAlgorithm, PassFunc: generate.GetPass, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, diff --git a/go.mod b/go.mod index a8ed3cdf4fa..647bc0cae36 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/secure-systems-lab/go-securesystemslib v0.8.0 github.com/sigstore/fulcio v1.4.3 + github.com/sigstore/protobuf-specs v0.3.0-beta.2 github.com/sigstore/rekor v1.3.4 github.com/sigstore/sigstore v1.8.1 github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1 @@ -278,6 +279,6 @@ require ( ) // TODO: REMOVE ME -replace github.com/sigstore/sigstore => github.com/trail-of-forks/sigstore v0.0.0-20240129151206-cff4abcde12e +replace github.com/sigstore/sigstore => /Users/ret2libc/projects/sigstore/sigstore -replace github.com/sigstore/rekor => github.com/trail-of-forks/rekor v0.0.0-20240129163653-3ac4c89bd056 +replace github.com/sigstore/rekor => /Users/ret2libc/projects/sigstore/rekor diff --git a/go.sum b/go.sum index ce0d2df33d2..0795d583fc9 100644 --- a/go.sum +++ b/go.sum @@ -608,6 +608,8 @@ github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/sigstore/fulcio v1.4.3 h1:9JcUCZjjVhRF9fmhVuz6i1RyhCc/EGCD7MOl+iqCJLQ= github.com/sigstore/fulcio v1.4.3/go.mod h1:BQPWo7cfxmJwgaHlphUHUpFkp5+YxeJes82oo39m5og= +github.com/sigstore/protobuf-specs v0.3.0-beta.2 h1:neHS0O1z7qz4q21vyXqSaKuKYxA0upzJERT88NrgYlM= +github.com/sigstore/protobuf-specs v0.3.0-beta.2/go.mod h1:ynKzXpqr3dUj2Xk9O/5ZUhjnpi0F53DNi5AdH6pS3jc= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1 h1:rEDdUefulkIQaMJyzLwtgPDLNXBIltBABiFYfb0YmgQ= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1/go.mod h1:RCdYCc1IxCYWzh2IdzdA6Yf7JIY0cMRqH08fpQYechw= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.1 h1:DvRWG99QGWZC5mp42SEde2Xke/Q384Idnj2da7yB+Mk= @@ -672,10 +674,6 @@ github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHT github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= -github.com/trail-of-forks/rekor v0.0.0-20240129163653-3ac4c89bd056 h1:jTVzEjC2Mm5x9mnZwKyspP7ygDyP1BiZHleUvh81V4Y= -github.com/trail-of-forks/rekor v0.0.0-20240129163653-3ac4c89bd056/go.mod h1:KMOYGlQZ96wQ0MfdHvVHjp8mM0fA8Lh6sqtwYkXP5YE= -github.com/trail-of-forks/sigstore v0.0.0-20240129151206-cff4abcde12e h1:EXVlV8GAQ7nct3uYZwga7JVjQ/GfwWAtMKbmYll3sZ8= -github.com/trail-of-forks/sigstore v0.0.0-20240129151206-cff4abcde12e/go.mod h1:rbZxJoss0Qf/OQeIuyqkQxo9jLKZlyLqOGX0BUaK7/I= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= diff --git a/pkg/cosign/keys.go b/pkg/cosign/keys.go index fb6debfe396..ca5b65f4b24 100644 --- a/pkg/cosign/keys.go +++ b/pkg/cosign/keys.go @@ -19,7 +19,6 @@ import ( "crypto" "crypto/ecdsa" "crypto/ed25519" - "crypto/elliptic" "crypto/rand" "crypto/rsa" _ "crypto/sha256" // for `crypto.SHA256` @@ -29,9 +28,11 @@ import ( "fmt" "os" "path/filepath" + "sort" "github.com/secure-systems-lab/go-securesystemslib/encrypted" "github.com/sigstore/cosign/v2/pkg/oci/static" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" ) @@ -69,9 +70,56 @@ func (k *KeysBytes) Password() []byte { return k.password } +var ClientAlgorithmsRegistry, _ = signature.NewAlgorithmRegistryConfig([]v1.KnownSignatureAlgorithm{ + v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256, + v1.KnownSignatureAlgorithm_ED25519_PH, +}) + +func GetSupportedAlgorithms() []string { + // Get the list of supported algorithms from v1.KnownSignatureAlgorithm_name + // and sort them alphabetically. + algorithms := make([]string, 0, len(v1.KnownSignatureAlgorithm_name)) + for algorithmId := range v1.KnownSignatureAlgorithm_name { + signatureFlag, err := signature.FormatSignatureAlgorithmFlag(v1.KnownSignatureAlgorithm(algorithmId)) + if err != nil { + continue + } + algorithms = append(algorithms, signatureFlag) + } + sort.Strings(algorithms) + return algorithms +} + // TODO(jason): Move this to an internal package. func GeneratePrivateKey() (*ecdsa.PrivateKey, error) { - return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + algorithmDetails, err := signature.GetAlgorithmDetails(v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256) + if err != nil { + return nil, err + } + key, err := GeneratePrivateKeyWithAlgo(algorithmDetails) + return key.(*ecdsa.PrivateKey), err +} + +func GeneratePrivateKeyWithAlgo(signingAlgorithm signature.AlgorithmDetails) (crypto.PrivateKey, error) { + switch signingAlgorithm.GetKeyType() { + case signature.ECDSA: + curve, err := signingAlgorithm.GetECDSACurve() + if err != nil { + return nil, err + } + return ecdsa.GenerateKey(*curve, rand.Reader) + case signature.ED25519: + _, priv, err := ed25519.GenerateKey(rand.Reader) + return priv, err + case signature.RSA: + rsaBits, err := signingAlgorithm.GetRSAKeySize() + if err != nil { + return nil, err + } + return rsa.GenerateKey(rand.Reader, int(rsaBits)) + default: + return nil, fmt.Errorf("unsupported signing algorithm: %s", signingAlgorithm) + } } // TODO(jason): Move this to the only place it's used in cmd/cosign/cli/importkeypair, and unexport it. @@ -182,13 +230,27 @@ func marshalKeyPair(ptype string, keypair Keys, pf PassFunc) (key *KeysBytes, er // TODO(jason): Move this to an internal package. func GenerateKeyPair(pf PassFunc) (*KeysBytes, error) { - priv, err := GeneratePrivateKey() + algorithmDetails, err := signature.GetAlgorithmDetails(v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256) + if err != nil { + return nil, err + } + + return GenerateKeyPairWithAlgo(pf, algorithmDetails) +} + +func GenerateKeyPairWithAlgo(pf PassFunc, signatureAlgorithm signature.AlgorithmDetails) (*KeysBytes, error) { + priv, err := GeneratePrivateKeyWithAlgo(signatureAlgorithm) if err != nil { return nil, err } + privSigner, ok := priv.(crypto.Signer) + if !ok { + return nil, fmt.Errorf("unsupported private key type: %T", priv) + } + // Emit SIGSTORE keys by default - return marshalKeyPair(SigstorePrivateKeyPemType, Keys{priv, priv.Public()}, pf) + return marshalKeyPair(SigstorePrivateKeyPemType, Keys{priv, privSigner.Public()}, pf) } // TODO(jason): Move this to an internal package. From 2990915812274b8f51a52c922e389eee98ad2d02 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Mon, 29 Jan 2024 18:07:25 +0100 Subject: [PATCH 4/4] Added --signing-algorithm flag to verify/verify-blob commands Signed-off-by: Riccardo Schirone --- cmd/cosign/cli/options/verify.go | 38 ++++++++++++++++++++-------- cmd/cosign/cli/verify.go | 2 ++ cmd/cosign/cli/verify/verify.go | 22 +++++++++++++--- cmd/cosign/cli/verify/verify_blob.go | 22 +++++++++++++--- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/cmd/cosign/cli/options/verify.go b/cmd/cosign/cli/options/verify.go index f89d227bfa0..5d88b40565e 100644 --- a/cmd/cosign/cli/options/verify.go +++ b/cmd/cosign/cli/options/verify.go @@ -16,9 +16,15 @@ package options import ( + "fmt" + "strings" + "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/internal/pkg/cosign" + cosign_v2 "github.com/sigstore/cosign/v2/pkg/cosign" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/signature" ) type CommonVerifyOptions struct { @@ -56,13 +62,14 @@ func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) { // VerifyOptions is the top level wrapper for the `verify` command. type VerifyOptions struct { - Key string - CheckClaims bool - Attachment string - Output string - SignatureRef string - PayloadRef string - LocalImage bool + Key string + SigningAlgorithm string + CheckClaims bool + Attachment string + Output string + SignatureRef string + PayloadRef string + LocalImage bool CommonVerifyOptions CommonVerifyOptions SecurityKey SecurityKeyOptions @@ -90,6 +97,11 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) { "path to the public key file, KMS URI or Kubernetes Secret") _ = cmd.Flags().SetAnnotation("key", cobra.BashCompFilenameExt, []string{}) + keyAlgorithmTypes := cosign_v2.GetSupportedAlgorithms() + keyAlgorithmHelp := fmt.Sprintf("accepted signing algorithm to use for verifying the signature (allowed %s)", strings.Join(keyAlgorithmTypes, ", ")) + defaultKeyFlag, _ := signature.FormatSignatureAlgorithmFlag(v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256) + cmd.Flags().StringVar(&o.SigningAlgorithm, "signing-algorithm", defaultKeyFlag, keyAlgorithmHelp) + cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true, "whether to check the claims found") @@ -154,9 +166,10 @@ func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) { // VerifyBlobOptions is the top level wrapper for the `verify blob` command. type VerifyBlobOptions struct { - Key string - Signature string - BundlePath string + Key string + SigningAlgorithm string + Signature string + BundlePath string SecurityKey SecurityKeyOptions CertVerify CertVerifyOptions @@ -178,6 +191,11 @@ func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Key, "key", "", "path to the public key file, KMS URI or Kubernetes Secret") + keyAlgorithmTypes := cosign_v2.GetSupportedAlgorithms() + keyAlgorithmHelp := fmt.Sprintf("accepted signing algorithm to use for verifying the signature (allowed %s)", strings.Join(keyAlgorithmTypes, ", ")) + defaultKeyFlag, _ := signature.FormatSignatureAlgorithmFlag(v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256) + cmd.Flags().StringVar(&o.SigningAlgorithm, "signing-algorithm", defaultKeyFlag, keyAlgorithmHelp) + cmd.Flags().StringVar(&o.Signature, "signature", "", "signature content or path or remote URL") diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index d8c7ed9cd06..6873851e03f 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -125,6 +125,7 @@ against the transparency log.`, Attachment: o.Attachment, Annotations: annotations, HashAlgorithm: hashAlgorithm, + SigningAlgorithm: o.SigningAlgorithm, SignatureRef: o.SignatureRef, PayloadRef: o.PayloadRef, LocalImage: o.LocalImage, @@ -315,6 +316,7 @@ The blob may be specified as a path to a file or - for stdin.`, ko := options.KeyOpts{ KeyRef: o.Key, + SigningAlgorithm: o.SigningAlgorithm, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, RekorURL: o.Rekor.URL, diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 6b8ef256ab6..2d1ea9c8d5b 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -42,6 +42,7 @@ import ( "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" "github.com/sigstore/cosign/v2/pkg/oci" sigs "github.com/sigstore/cosign/v2/pkg/signature" + pb_go_v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" @@ -74,6 +75,7 @@ type VerifyCommand struct { SignatureRef string PayloadRef string HashAlgorithm crypto.Hash + SigningAlgorithm string LocalImage bool NameOptions []name.Option Offline bool @@ -215,9 +217,23 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { } } - svOpts := []signature.LoadOption{ - signatureoptions.WithHash(crypto.SHA256), - signatureoptions.WithED25519ph(), + var svOpts []signature.LoadOption + signingAlgorithm, err := signature.ParseSignatureAlgorithmFlag(c.SigningAlgorithm) + if err != nil { + // Default to ECDSA_SHA2_256_NISTP256 if no algorithm is specified + signingAlgorithm = pb_go_v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256 + } + + algorithmDetails, err := signature.GetAlgorithmDetails(signingAlgorithm) + if err != nil { + return err + } + hashAlgorithm := algorithmDetails.GetHashType() + svOpts = []signature.LoadOption{ + signatureoptions.WithHash(hashAlgorithm), + } + if algorithmDetails.GetSignatureAlgorithm() == pb_go_v1.KnownSignatureAlgorithm_ED25519_PH { + svOpts = append(svOpts, signatureoptions.WithED25519ph()) } // Keys are optional! diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index e53eb8993d9..28993d50c40 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -17,7 +17,6 @@ package verify import ( "context" - "crypto" "crypto/x509" "encoding/base64" "encoding/json" @@ -39,6 +38,7 @@ import ( "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" "github.com/sigstore/cosign/v2/pkg/oci/static" sigs "github.com/sigstore/cosign/v2/pkg/signature" + pb_go_v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" @@ -172,9 +172,23 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { } } - svOpts := []signature.LoadOption{ - signatureoptions.WithHash(crypto.SHA256), - signatureoptions.WithED25519ph(), + var svOpts []signature.LoadOption + signingAlgorithm, err := signature.ParseSignatureAlgorithmFlag(c.KeyOpts.SigningAlgorithm) + if err != nil { + // Default to ECDSA_SHA2_256_NISTP256 if no algorithm is specified + signingAlgorithm = pb_go_v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256 + } + + algorithmDetails, err := signature.GetAlgorithmDetails(signingAlgorithm) + if err != nil { + return err + } + hashAlgorithm := algorithmDetails.GetHashType() + svOpts = []signature.LoadOption{ + signatureoptions.WithHash(hashAlgorithm), + } + if algorithmDetails.GetSignatureAlgorithm() == pb_go_v1.KnownSignatureAlgorithm_ED25519_PH { + svOpts = append(svOpts, signatureoptions.WithED25519ph()) } // Keys are optional!