Skip to content

Commit

Permalink
Add LoadSigner/Verifier WithOpts functions for more flexibility
Browse files Browse the repository at this point in the history
Before this commit, the Signer/Verifier to load was determined
exclusively by the public/private key type, however there may be
multiple Signers/Verifiers available, like in the case of RSA and
ED25519.

This commit adds LoadVerifierWithOpts, LoadSignerWithOpts, and
LoadSignerVerifierWithOpts to give clients more flexibility, allowing
the user of the API to choose between the available options by using
options.

Signed-off-by: Riccardo Schirone <riccardo.schirone@trailofbits.com>
  • Loading branch information
ret2libc committed Jan 19, 2024
1 parent 880acc2 commit 4c4237b
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 0 deletions.
31 changes: 31 additions & 0 deletions pkg/signature/options.go
Expand Up @@ -18,6 +18,7 @@ package signature
import (
"context"
"crypto"
"crypto/rsa"
"io"

"github.com/sigstore/sigstore/pkg/signature/options"
Expand Down Expand Up @@ -55,3 +56,33 @@ type VerifyOption interface {
RPCOption
MessageOption
}

type signerVerifierOpts struct {
useED25519ph bool
rsaPSSOptions *rsa.PSSOptions
}

// SignerVerifierOption specifies options to be used when creating a SignerVerifier
type SignerVerifierOption func(*signerVerifierOpts)

// WithED25519ph specifies that the ED25519ph algorithm should be used when a ED25519 key is used
func WithED25519ph() SignerVerifierOption {
return func(o *signerVerifierOpts) {
o.useED25519ph = true
}
}

// WithRSAPSS specifies that the RSAPSS algorithm should be used when a RSA key is used
func WithRSAPSS(opts *rsa.PSSOptions) SignerVerifierOption {
return func(o *signerVerifierOpts) {
o.rsaPSSOptions = opts
}
}

func makeSignerVerifierOpts(opts ...SignerVerifierOption) *signerVerifierOpts {
o := &signerVerifierOpts{}
for _, opt := range opts {
opt(o)
}
return o
}
36 changes: 36 additions & 0 deletions pkg/signature/signer.go
Expand Up @@ -70,6 +70,28 @@ func LoadSigner(privateKey crypto.PrivateKey, hashFunc crypto.Hash) (Signer, err
return nil, errors.New("unsupported public key type")
}

// LoadSignerWithOpts returns a signature.Signer based on the algorithm of the private key
// provided.
func LoadSignerWithOpts(privateKey crypto.PrivateKey, hashFunc crypto.Hash, opts ...SignerVerifierOption) (Signer, error) {
o := makeSignerVerifierOpts(opts...)

switch pk := privateKey.(type) {
case *rsa.PrivateKey:
if o.rsaPSSOptions != nil {
return LoadRSAPSSSigner(pk, hashFunc, o.rsaPSSOptions)
}
return LoadRSAPKCS1v15Signer(pk, hashFunc)
case *ecdsa.PrivateKey:
return LoadECDSASigner(pk, hashFunc)
case ed25519.PrivateKey:
if o.useED25519ph {
return LoadED25519phSigner(pk)
}
return LoadED25519Signer(pk)
}
return nil, errors.New("unsupported public key type")
}

// LoadSignerFromPEMFile returns a signature.Signer based on the algorithm of the private key
// in the file. The Signer will use the hash function specified when computing digests.
//
Expand All @@ -87,3 +109,17 @@ func LoadSignerFromPEMFile(path string, hashFunc crypto.Hash, pf cryptoutils.Pas
}
return LoadSigner(priv, hashFunc)
}

// LoadSignerFromPEMFileWithOpts returns a signature.Signer based on the algorithm of the private key
// in the file. The Signer will use the hash function specified when computing digests.
func LoadSignerFromPEMFileWithOpts(path string, hashFunc crypto.Hash, pf cryptoutils.PassFunc, opts ...SignerVerifierOption) (Signer, error) {
fileBytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
return nil, err
}
priv, err := cryptoutils.UnmarshalPEMToPrivateKey(fileBytes, pf)
if err != nil {
return nil, err
}
return LoadSignerWithOpts(priv, hashFunc, opts...)
}
79 changes: 79 additions & 0 deletions pkg/signature/signer_test.go
@@ -0,0 +1,79 @@
// 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 (
"bytes"
"crypto"
"crypto/ed25519"
"encoding/base64"
"testing"

"github.com/sigstore/sigstore/pkg/cryptoutils"
)

func TestLoadEd25519Signer(t *testing.T) {
privateKey, err := cryptoutils.UnmarshalPEMToPrivateKey([]byte(ed25519Priv), cryptoutils.SkipPassword)
if err != nil {
t.Fatalf("unexpected error unmarshalling public key: %v", err)
}
edPriv, ok := privateKey.(ed25519.PrivateKey)
if !ok {
t.Fatalf("expected ed25519.PrivateKey")
}

signer, err := LoadSigner(edPriv, crypto.SHA256)
if err != nil {
t.Fatalf("unexpected error loading verifier: %v", err)
}

msg := []byte("sign me")
sig, err := signer.SignMessage(bytes.NewReader(msg))
if err != nil {
t.Fatalf("unexpected error signing message: %v", err)
}

expectedSig, _ := base64.StdEncoding.DecodeString("cnafwd8DKq2nQ564eN66ckYV8anVFGFi5vaYiQg2aal7ej/J0/OE0PPdKHLHe9wdzWRMFy5MpurRD/2cGXGLBQ==")
if !bytes.Equal(sig, expectedSig) {
t.Fatalf("signature was not as expected")
}
}

func TestLoadEd25519phSigner(t *testing.T) {
privateKey, err := cryptoutils.UnmarshalPEMToPrivateKey([]byte(ed25519Priv), cryptoutils.SkipPassword)
if err != nil {
t.Fatalf("unexpected error unmarshalling public key: %v", err)
}
edPriv, ok := privateKey.(ed25519.PrivateKey)
if !ok {
t.Fatalf("expected ed25519.PrivateKey")
}

signer, err := LoadSignerWithOpts(edPriv, crypto.SHA512, WithED25519ph())
if err != nil {
t.Fatalf("unexpected error loading verifier: %v", err)
}

msg := []byte("sign me")
sig, err := signer.SignMessage(bytes.NewReader(msg))
if err != nil {
t.Fatalf("unexpected error signing message: %v", err)
}

expectedSig, _ := base64.StdEncoding.DecodeString("9D4pA8jutZnbqKy4fFRl+kDsVUCO50qrOD1lxmsiUFk6NX+7OXUK5BCMkE2KYPRDxjkDFBzbDZEQhaFdDV5tDg==")
if !bytes.Equal(sig, expectedSig) {
t.Fatalf("signature was not as expected")
}
}
36 changes: 36 additions & 0 deletions pkg/signature/signerverifier.go
Expand Up @@ -50,6 +50,28 @@ func LoadSignerVerifier(privateKey crypto.PrivateKey, hashFunc crypto.Hash) (Sig
return nil, errors.New("unsupported public key type")
}

// LoadSignerVerifierWithOpts returns a signature.SignerVerifier based on the
// algorithm of the private key provided and the user's choice.
func LoadSignerVerifierWithOpts(privateKey crypto.PrivateKey, hashFunc crypto.Hash, opts ...SignerVerifierOption) (SignerVerifier, error) {
o := makeSignerVerifierOpts(opts...)

switch pk := privateKey.(type) {
case *rsa.PrivateKey:
if o.rsaPSSOptions != nil {
return LoadRSAPSSSignerVerifier(pk, hashFunc, o.rsaPSSOptions)
}
return LoadRSAPKCS1v15SignerVerifier(pk, hashFunc)
case *ecdsa.PrivateKey:
return LoadECDSASignerVerifier(pk, hashFunc)
case ed25519.PrivateKey:
if o.useED25519ph {
return LoadED25519phSignerVerifier(pk)
}
return LoadED25519SignerVerifier(pk)
}
return nil, errors.New("unsupported public key type")
}

// LoadSignerVerifierFromPEMFile returns a signature.SignerVerifier based on the algorithm of the private key
// in the file. The SignerVerifier will use the hash function specified when computing digests.
//
Expand All @@ -67,3 +89,17 @@ func LoadSignerVerifierFromPEMFile(path string, hashFunc crypto.Hash, pf cryptou
}
return LoadSignerVerifier(priv, hashFunc)
}

// LoadSignerVerifierFromPEMFileWithOpts returns a signature.SignerVerifier based on the algorithm of the private key
// in the file. The SignerVerifier will use the hash function specified when computing digests.
func LoadSignerVerifierFromPEMFileWithOpts(path string, hashFunc crypto.Hash, pf cryptoutils.PassFunc, opts ...SignerVerifierOption) (SignerVerifier, error) {
fileBytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
return nil, err
}
priv, err := cryptoutils.UnmarshalPEMToPrivateKey(fileBytes, pf)
if err != nil {
return nil, err
}
return LoadSignerVerifierWithOpts(priv, hashFunc, opts...)
}
54 changes: 54 additions & 0 deletions pkg/signature/signerverifier_test.go
@@ -0,0 +1,54 @@
// 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 (
"bytes"
"crypto"
"crypto/rsa"
"encoding/base64"
"testing"

"github.com/sigstore/sigstore/pkg/cryptoutils"
)

func TestLoadRSAPSSSignerVerifier(t *testing.T) {
opts := &rsa.PSSOptions{
Hash: crypto.SHA256,
}

privateKey, err := cryptoutils.UnmarshalPEMToPrivateKey([]byte(rsaKey), cryptoutils.SkipPassword)
if err != nil {
t.Errorf("unexpected error unmarshalling private key: %v", err)
}
sv, err := LoadSignerVerifierWithOpts(privateKey, crypto.SHA256, WithED25519ph(), WithRSAPSS(opts))
if err != nil {
t.Errorf("unexpected error creating signer/verifier: %v", err)
}

message := []byte("sign me")
sig, err := sv.SignMessage(bytes.NewReader(message))
if err != nil {
t.Fatalf("unexpected error signing message: %v", err)
}
if err := sv.VerifySignature(bytes.NewReader(sig), bytes.NewReader(message)); err != nil {
t.Fatalf("unexpected error verifying calculated signature: %v", err)
}

expectedSig, _ := base64.StdEncoding.DecodeString("UyouJxmgAKdm/Qfi9YA7aK71/eqyLcytmDN8CQqSCgcbGSln7S5fgIAmrwUfGp1tcxKjuNjLScn11+fqawiG9y66740VEC6GfS1hgElC2k3i/v8ly2mlt+4JYs3euzYxtWnxwQr4csc7Jy2V2cjoeQm6GTxkR4E6TRJM8/UxXvjKtp3rxRD8OuyfuGFkI0lU48vjKLgbuZKQqQdWuNUOnsPvtrHxvGRY/F1C0Ig3b7SoTyAjWSXQG42faKsFT+W1L/UdRK+m73TYdxMleI4uIGtl0k0Weui1/gK7Uh2FUP5+/F1ZoQRYk/DMz0M4QPmPsYLGwc8oduoF6JvNMGKymg==")
if err := sv.VerifySignature(bytes.NewReader(expectedSig), bytes.NewReader(message)); err != nil {
t.Fatalf("unexpected error verifying expected signature: %v", err)
}
}
38 changes: 38 additions & 0 deletions pkg/signature/verifier.go
Expand Up @@ -51,6 +51,28 @@ func LoadVerifier(publicKey crypto.PublicKey, hashFunc crypto.Hash) (Verifier, e
return nil, errors.New("unsupported public key type")
}

// 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, hashFunc crypto.Hash, opts ...SignerVerifierOption) (Verifier, error) {
o := makeSignerVerifierOpts(opts...)

switch pk := publicKey.(type) {
case *rsa.PublicKey:
if o.rsaPSSOptions != nil {
return LoadRSAPSSVerifier(pk, hashFunc, o.rsaPSSOptions)
}
return LoadRSAPKCS1v15Verifier(pk, hashFunc)
case *ecdsa.PublicKey:
return LoadECDSAVerifier(pk, hashFunc)
case ed25519.PublicKey:
if o.useED25519ph {
return LoadED25519phVerifier(pk)
}
return LoadED25519Verifier(pk)
}
return nil, errors.New("unsupported public key type")
}

// LoadUnsafeVerifier returns a signature.Verifier based on the algorithm of the public key
// provided that will use SHA1 when computing digests for RSA and ECDSA signatures.
//
Expand Down Expand Up @@ -98,3 +120,19 @@ func LoadVerifierFromPEMFile(path string, hashFunc crypto.Hash) (Verifier, error

return LoadVerifier(pubKey, hashFunc)
}

// LoadVerifierFromPEMFileWithOpts returns a signature.Verifier based on the contents of a
// file located at path. The Verifier wil use the hash function specified when computing digests.
func LoadVerifierFromPEMFileWithOpts(path string, hashFunc crypto.Hash, opts ...SignerVerifierOption) (Verifier, error) {
fileBytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
return nil, err
}

pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(fileBytes)
if err != nil {
return nil, err
}

return LoadVerifierWithOpts(pubKey, hashFunc, opts...)
}
16 changes: 16 additions & 0 deletions pkg/signature/verifier_test.go
Expand Up @@ -15,6 +15,7 @@
package signature

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"testing"
Expand All @@ -34,3 +35,18 @@ func TestLoadUnsafeVerifier(t *testing.T) {
t.Fatalf("public keys were not equal")
}
}

func TestLoadVerifier(t *testing.T) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("unexpected error generating key: %v", err)
}
verifier, err := LoadVerifier(key.Public(), crypto.SHA256)
if err != nil {
t.Fatalf("unexpected error loading verifier: %v", err)
}
pubKey, _ := verifier.PublicKey()
if !key.PublicKey.Equal(pubKey) {
t.Fatalf("public keys were not equal")
}
}

0 comments on commit 4c4237b

Please sign in to comment.