diff --git a/pkg/signature/options.go b/pkg/signature/options.go index 0be699f7e..e17e768c2 100644 --- a/pkg/signature/options.go +++ b/pkg/signature/options.go @@ -18,6 +18,7 @@ package signature import ( "context" "crypto" + "crypto/rsa" "io" "github.com/sigstore/sigstore/pkg/signature/options" @@ -55,3 +56,10 @@ type VerifyOption interface { RPCOption MessageOption } + +// LoadOption specifies options to be used when creating a Signer/Verifier +type LoadOption interface { + ApplyHash(*crypto.Hash) + ApplyED25519ph(*bool) + ApplyRSAPSS(**rsa.PSSOptions) +} diff --git a/pkg/signature/options/loadoptions.go b/pkg/signature/options/loadoptions.go new file mode 100644 index 000000000..e5f3f0116 --- /dev/null +++ b/pkg/signature/options/loadoptions.go @@ -0,0 +1,76 @@ +// +// Copyright 2024 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" + "crypto/rsa" +) + +// RequestHash implements the functional option pattern for setting a Hash +// function when loading a signer or verifier +type RequestHash struct { + NoOpOptionImpl + hashFunc crypto.Hash +} + +// ApplyHash sets the hash as requested by the functional option +func (r RequestHash) ApplyHash(hash *crypto.Hash) { + *hash = r.hashFunc +} + +// WithHash specifies that the given hash function should be used when loading a signer or verifier +func WithHash(hash crypto.Hash) RequestHash { + return RequestHash{hashFunc: hash} +} + +// RequestED25519ph implements the functional option pattern for specifying +// ED25519ph (pre-hashed) should be used when loading a signer or verifier and a +// ED25519 key is +type RequestED25519ph struct { + NoOpOptionImpl + useED25519ph bool +} + +// ApplyED25519ph sets the ED25519ph flag as requested by the functional option +func (r RequestED25519ph) ApplyED25519ph(useED25519ph *bool) { + *useED25519ph = r.useED25519ph +} + +// WithED25519ph specifies that the ED25519ph algorithm should be used when a ED25519 key is used +func WithED25519ph() RequestED25519ph { + return RequestED25519ph{useED25519ph: true} +} + +// RequestPSSOptions implements the functional option pattern for specifying RSA +// PSS should be used when loading a signer or verifier and a RSA key is +// detected +type RequestPSSOptions struct { + NoOpOptionImpl + opts *rsa.PSSOptions +} + +// ApplyRSAPSS sets the RSAPSS options as requested by the functional option +func (r RequestPSSOptions) ApplyRSAPSS(opts **rsa.PSSOptions) { + *opts = r.opts +} + +// WithRSAPSS specifies that the RSAPSS algorithm should be used when a RSA key is used +// Note that the RSA PSSOptions contains an hash algorithm, which will override +// the hash function specified with WithHash. +func WithRSAPSS(opts *rsa.PSSOptions) RequestPSSOptions { + return RequestPSSOptions{opts: opts} +} diff --git a/pkg/signature/options/noop.go b/pkg/signature/options/noop.go index c7f1ccb91..0c0e51856 100644 --- a/pkg/signature/options/noop.go +++ b/pkg/signature/options/noop.go @@ -18,6 +18,7 @@ package options import ( "context" "crypto" + "crypto/rsa" "io" ) @@ -47,3 +48,12 @@ func (NoOpOptionImpl) ApplyKeyVersion(_ *string) {} // ApplyKeyVersionUsed is a no-op required to fully implement the requisite interfaces func (NoOpOptionImpl) ApplyKeyVersionUsed(_ **string) {} + +// ApplyHash is a no-op required to fully implement the requisite interfaces +func (NoOpOptionImpl) ApplyHash(_ *crypto.Hash) {} + +// ApplyED25519ph is a no-op required to fully implement the requisite interfaces +func (NoOpOptionImpl) ApplyED25519ph(_ *bool) {} + +// ApplyRSAPSS is a no-op required to fully implement the requisite interfaces +func (NoOpOptionImpl) ApplyRSAPSS(_ **rsa.PSSOptions) {} diff --git a/pkg/signature/signer.go b/pkg/signature/signer.go index dae50ca6f..e26def9fa 100644 --- a/pkg/signature/signer.go +++ b/pkg/signature/signer.go @@ -30,6 +30,7 @@ import ( _ "crypto/sha512" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature/options" // these ensure we have the implementations loaded _ "golang.org/x/crypto/sha3" @@ -59,24 +60,31 @@ func (s SignerOpts) HashFunc() crypto.Hash { // If privateKey is an RSA key, a RSAPKCS1v15Signer will be returned. If a // RSAPSSSigner is desired instead, use the LoadRSAPSSSigner() method directly. func LoadSigner(privateKey crypto.PrivateKey, hashFunc crypto.Hash) (Signer, error) { - return LoadSignerWithOpts(privateKey, WithHash(hashFunc)) + return LoadSignerWithOpts(privateKey, options.WithHash(hashFunc)) } // LoadSignerWithOpts returns a signature.Signer based on the algorithm of the private key // provided. func LoadSignerWithOpts(privateKey crypto.PrivateKey, opts ...LoadOption) (Signer, error) { - o := makeSignerVerifierOpts(opts...) + var rsaPSSOptions *rsa.PSSOptions + var useED25519ph bool + hashFunc := crypto.SHA256 + for _, o := range opts { + o.ApplyED25519ph(&useED25519ph) + o.ApplyHash(&hashFunc) + o.ApplyRSAPSS(&rsaPSSOptions) + } switch pk := privateKey.(type) { case *rsa.PrivateKey: - if o.rsaPSSOptions != nil { - return LoadRSAPSSSigner(pk, o.hashFunc, o.rsaPSSOptions) + if rsaPSSOptions != nil { + return LoadRSAPSSSigner(pk, hashFunc, rsaPSSOptions) } - return LoadRSAPKCS1v15Signer(pk, o.hashFunc) + return LoadRSAPKCS1v15Signer(pk, hashFunc) case *ecdsa.PrivateKey: - return LoadECDSASigner(pk, o.hashFunc) + return LoadECDSASigner(pk, hashFunc) case ed25519.PrivateKey: - if o.useED25519ph { + if useED25519ph { return LoadED25519phSigner(pk) } return LoadED25519Signer(pk) diff --git a/pkg/signature/signer_test.go b/pkg/signature/signer_test.go index bb1ac8751..ee4b660dc 100644 --- a/pkg/signature/signer_test.go +++ b/pkg/signature/signer_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature/options" ) func TestLoadEd25519Signer(t *testing.T) { @@ -61,7 +62,7 @@ func TestLoadEd25519phSigner(t *testing.T) { t.Fatalf("expected ed25519.PrivateKey") } - signer, err := LoadSignerWithOpts(edPriv, WithED25519ph(), WithHash(crypto.SHA512)) + signer, err := LoadSignerWithOpts(edPriv, options.WithED25519ph(), options.WithHash(crypto.SHA512)) if err != nil { t.Fatalf("unexpected error loading verifier: %v", err) } diff --git a/pkg/signature/signerverifier.go b/pkg/signature/signerverifier.go index b097b0455..70253b121 100644 --- a/pkg/signature/signerverifier.go +++ b/pkg/signature/signerverifier.go @@ -25,6 +25,7 @@ import ( "path/filepath" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature/options" ) // SignerVerifier creates and verifies digital signatures over a message using a specified key pair @@ -39,24 +40,31 @@ type SignerVerifier interface { // If privateKey is an RSA key, a RSAPKCS1v15SignerVerifier will be returned. If a // RSAPSSSignerVerifier is desired instead, use the LoadRSAPSSSignerVerifier() method directly. func LoadSignerVerifier(privateKey crypto.PrivateKey, hashFunc crypto.Hash) (SignerVerifier, error) { - return LoadSignerVerifierWithOpts(privateKey, WithHash(hashFunc)) + return LoadSignerVerifierWithOpts(privateKey, options.WithHash(hashFunc)) } // LoadSignerVerifierWithOpts returns a signature.SignerVerifier based on the // algorithm of the private key provided and the user's choice. func LoadSignerVerifierWithOpts(privateKey crypto.PrivateKey, opts ...LoadOption) (SignerVerifier, error) { - o := makeSignerVerifierOpts(opts...) + var rsaPSSOptions *rsa.PSSOptions + var useED25519ph bool + hashFunc := crypto.SHA256 + for _, o := range opts { + o.ApplyED25519ph(&useED25519ph) + o.ApplyHash(&hashFunc) + o.ApplyRSAPSS(&rsaPSSOptions) + } switch pk := privateKey.(type) { case *rsa.PrivateKey: - if o.rsaPSSOptions != nil { - return LoadRSAPSSSignerVerifier(pk, o.hashFunc, o.rsaPSSOptions) + if rsaPSSOptions != nil { + return LoadRSAPSSSignerVerifier(pk, hashFunc, rsaPSSOptions) } - return LoadRSAPKCS1v15SignerVerifier(pk, o.hashFunc) + return LoadRSAPKCS1v15SignerVerifier(pk, hashFunc) case *ecdsa.PrivateKey: - return LoadECDSASignerVerifier(pk, o.hashFunc) + return LoadECDSASignerVerifier(pk, hashFunc) case ed25519.PrivateKey: - if o.useED25519ph { + if useED25519ph { return LoadED25519phSignerVerifier(pk) } return LoadED25519SignerVerifier(pk) diff --git a/pkg/signature/signerverifier_options.go b/pkg/signature/signerverifier_options.go deleted file mode 100644 index ad522e9fc..000000000 --- a/pkg/signature/signerverifier_options.go +++ /dev/null @@ -1,70 +0,0 @@ -// -// Copyright 2024 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 signature - -import ( - "crypto" - "crypto/rsa" -) - -type loadOpts struct { - hashFunc crypto.Hash - useED25519ph bool - rsaPSSOptions *rsa.PSSOptions -} - -// LoadOption specifies options to be used when creating a Signer/Verifier -type LoadOption func(*loadOpts) - -// WithHash specifies the hash function to be used for the Signer/Verifier -func WithHash(hashFunc crypto.Hash) LoadOption { - return func(o *loadOpts) { - o.hashFunc = hashFunc - } -} - -// WithED25519ph specifies that the ED25519ph algorithm should be used when a ED25519 key is used -func WithED25519ph() LoadOption { - return func(o *loadOpts) { - o.useED25519ph = true - } -} - -// WithRSAPSS specifies that the RSAPSS algorithm should be used when a RSA key is used -// Note that the RSA PSSOptions contains an hash algorithm, which will override -// the hash function specified with WithHash. -func WithRSAPSS(opts *rsa.PSSOptions) LoadOption { - return func(o *loadOpts) { - o.rsaPSSOptions = opts - } -} - -// GetSignerVerifierOptionHash returns the hash function specified in the options -func GetSignerVerifierOptionHash(opts ...LoadOption) crypto.Hash { - o := makeSignerVerifierOpts(opts...) - return o.hashFunc -} - -func makeSignerVerifierOpts(opts ...LoadOption) *loadOpts { - o := &loadOpts{ - hashFunc: crypto.SHA256, - } - - for _, opt := range opts { - opt(o) - } - return o -} diff --git a/pkg/signature/signerverifier_test.go b/pkg/signature/signerverifier_test.go index 81aefb4ab..c6acb2fd9 100644 --- a/pkg/signature/signerverifier_test.go +++ b/pkg/signature/signerverifier_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature/options" ) func TestLoadRSAPSSSignerVerifier(t *testing.T) { @@ -33,7 +34,7 @@ func TestLoadRSAPSSSignerVerifier(t *testing.T) { if err != nil { t.Errorf("unexpected error unmarshalling private key: %v", err) } - sv, err := LoadSignerVerifierWithOpts(privateKey, WithHash(crypto.SHA256), WithED25519ph(), WithRSAPSS(opts)) + sv, err := LoadSignerVerifierWithOpts(privateKey, options.WithHash(crypto.SHA256), options.WithED25519ph(), options.WithRSAPSS(opts)) if err != nil { t.Errorf("unexpected error creating signer/verifier: %v", err) } diff --git a/pkg/signature/verifier.go b/pkg/signature/verifier.go index a0b417507..cdde9fc54 100644 --- a/pkg/signature/verifier.go +++ b/pkg/signature/verifier.go @@ -26,6 +26,7 @@ import ( "path/filepath" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature/options" ) // Verifier verifies the digital signature using a specified public key @@ -40,24 +41,31 @@ type Verifier interface { // If publicKey is an RSA key, a RSAPKCS1v15Verifier will be returned. If a // RSAPSSVerifier is desired instead, use the LoadRSAPSSVerifier() method directly. func LoadVerifier(publicKey crypto.PublicKey, hashFunc crypto.Hash) (Verifier, error) { - return LoadVerifierWithOpts(publicKey, WithHash(hashFunc)) + return LoadVerifierWithOpts(publicKey, options.WithHash(hashFunc)) } // LoadVerifierWithOpts returns a signature.Verifier based on the algorithm of the public key // provided that will use the hash function specified when computing digests. func LoadVerifierWithOpts(publicKey crypto.PublicKey, opts ...LoadOption) (Verifier, error) { - o := makeSignerVerifierOpts(opts...) + var rsaPSSOptions *rsa.PSSOptions + var useED25519ph bool + hashFunc := crypto.SHA256 + for _, o := range opts { + o.ApplyED25519ph(&useED25519ph) + o.ApplyHash(&hashFunc) + o.ApplyRSAPSS(&rsaPSSOptions) + } switch pk := publicKey.(type) { case *rsa.PublicKey: - if o.rsaPSSOptions != nil { - return LoadRSAPSSVerifier(pk, o.hashFunc, o.rsaPSSOptions) + if rsaPSSOptions != nil { + return LoadRSAPSSVerifier(pk, hashFunc, rsaPSSOptions) } - return LoadRSAPKCS1v15Verifier(pk, o.hashFunc) + return LoadRSAPKCS1v15Verifier(pk, hashFunc) case *ecdsa.PublicKey: - return LoadECDSAVerifier(pk, o.hashFunc) + return LoadECDSAVerifier(pk, hashFunc) case ed25519.PublicKey: - if o.useED25519ph { + if useED25519ph { return LoadED25519phVerifier(pk) } return LoadED25519Verifier(pk)