diff --git a/client/client.go b/client/client.go index 8246b16a..a66876c5 100644 --- a/client/client.go +++ b/client/client.go @@ -18,6 +18,7 @@ import ( "github.com/cloudflare/cfssl/log" "github.com/cloudflare/gokeyless/protocol" + "github.com/cloudflare/gokeyless/signer" "github.com/cloudflare/gokeyless/tracing" "github.com/lziest/ttlcache" "github.com/opentracing/opentracing-go" @@ -189,11 +190,11 @@ func (c *Client) getRemote(server string) (Remote, error) { return r, nil } -// NewRemoteSignerWithCertID returns a remote keyserver based crypto.Signer +// NewRemoteSignerWithCertID returns a remote keyserver based signer.CtxSigner // ski, sni, serverIP, and certID are used to identify the key by the remote // keyserver. func NewRemoteSignerWithCertID(ctx context.Context, c *Client, keyserver string, ski protocol.SKI, - pub crypto.PublicKey, sni string, certID string, serverIP net.IP) (crypto.Signer, error) { + pub crypto.PublicKey, sni string, certID string, serverIP net.IP) (signer.CtxSigner, error) { span, _ := opentracing.StartSpanFromContext(ctx, "client.NewRemoteSignerWithCertID") defer span.Finish() priv := PrivateKey{ @@ -219,11 +220,11 @@ func NewRemoteSignerWithCertID(ctx context.Context, c *Client, keyserver string, return &priv, nil } -// NewRemoteSigner returns a remote keyserver based crypto.Signer, +// NewRemoteSigner returns a remote keyserver based signer.CtxSigner, // ski, sni, and serverIP are used to identified the key by the remote // keyserver. func NewRemoteSigner(ctx context.Context, c *Client, keyserver string, ski protocol.SKI, - pub crypto.PublicKey, sni string, serverIP net.IP) (crypto.Signer, error) { + pub crypto.PublicKey, sni string, serverIP net.IP) (signer.CtxSigner, error) { span, _ := opentracing.StartSpanFromContext(ctx, "client.NewRemoteSignerWithCertID") defer span.Finish() @@ -250,11 +251,11 @@ func NewRemoteSigner(ctx context.Context, c *Client, keyserver string, ski proto } // NewRemoteSignerTemplate returns a remote keyserver -// based crypto.Signer with the public key. +// based signer.CtxSigner with the public key. // SKI is computed from the public key and along with sni and serverIP, // the remote Signer uses those key identification info to contact the // remote keyserver for keyless operations. -func (c *Client) NewRemoteSignerTemplate(ctx context.Context, keyserver string, pub crypto.PublicKey, sni string, serverIP net.IP) (crypto.Signer, error) { +func (c *Client) NewRemoteSignerTemplate(ctx context.Context, keyserver string, pub crypto.PublicKey, sni string, serverIP net.IP) (signer.CtxSigner, error) { ski, err := protocol.GetSKI(pub) if err != nil { return nil, err @@ -263,10 +264,10 @@ func (c *Client) NewRemoteSignerTemplate(ctx context.Context, keyserver string, } // NewRemoteSignerTemplateWithCertID returns a remote keyserver -// based crypto.Signer with the public key. +// based signer.CtxSigner with the public key. // SKI is computed from public key, and along with sni, serverIP, and // certID the remote signer uses these to contact the remote keyserver. -func (c *Client) NewRemoteSignerTemplateWithCertID(ctx context.Context, keyserver string, pub crypto.PublicKey, sni string, serverIP net.IP, certID string) (crypto.Signer, error) { +func (c *Client) NewRemoteSignerTemplateWithCertID(ctx context.Context, keyserver string, pub crypto.PublicKey, sni string, serverIP net.IP, certID string) (signer.CtxSigner, error) { ski, err := protocol.GetSKI(pub) if err != nil { return nil, err @@ -276,20 +277,20 @@ func (c *Client) NewRemoteSignerTemplateWithCertID(ctx context.Context, keyserve // NewRemoteSignerByPublicKey returns a remote keyserver based signer // with the the public key. -func (c *Client) NewRemoteSignerByPublicKey(ctx context.Context, server string, pub crypto.PublicKey) (crypto.Signer, error) { +func (c *Client) NewRemoteSignerByPublicKey(ctx context.Context, server string, pub crypto.PublicKey) (signer.CtxSigner, error) { return c.NewRemoteSignerTemplate(ctx, server, pub, "", nil) } // NewRemoteSignerByCert returns a remote keyserver based signer // with the the public key contained in a x509.Certificate. -func (c *Client) NewRemoteSignerByCert(ctx context.Context, server string, cert *x509.Certificate) (crypto.Signer, error) { +func (c *Client) NewRemoteSignerByCert(ctx context.Context, server string, cert *x509.Certificate) (signer.CtxSigner, error) { return c.NewRemoteSignerTemplate(ctx, server, cert.PublicKey, "", nil) } // NewRemoteSignerByCertPEM returns a remote keyserver based signer // with the public key extracted from a single PEM cert // (possibly the leaf of a chain of certs). -func (c *Client) NewRemoteSignerByCertPEM(ctx context.Context, server string, certsPEM []byte) (crypto.Signer, error) { +func (c *Client) NewRemoteSignerByCertPEM(ctx context.Context, server string, certsPEM []byte) (signer.CtxSigner, error) { block, _ := pem.Decode(certsPEM) if block == nil { return nil, errors.New("couldn't parse PEM bytes") @@ -309,7 +310,7 @@ var ( ) // ScanDir reads all .pubkey and .crt files from a directory and returns associated PublicKey structs. -func (c *Client) ScanDir(server, dir string, LoadPubKey func([]byte) (crypto.PublicKey, error)) (privkeys []crypto.Signer, err error) { +func (c *Client) ScanDir(server, dir string, LoadPubKey func([]byte) (crypto.PublicKey, error)) (privkeys []signer.CtxSigner, err error) { err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -325,7 +326,7 @@ func (c *Client) ScanDir(server, dir string, LoadPubKey func([]byte) (crypto.Pub return err } - var priv crypto.Signer + var priv signer.CtxSigner if LoadPubKey == nil { LoadPubKey = DefaultLoadPubKey } diff --git a/client/keys.go b/client/keys.go index 8903a4f9..75a6e62c 100644 --- a/client/keys.go +++ b/client/keys.go @@ -93,7 +93,7 @@ type PrivateKey struct { certID string // We have shove the span context inside PrivateKey because - // it's used by calling functions on the `crypto.Signer` interface, which don't take ctx as a parameter. + // it's used by calling functions on the `signer.CtxSigner` interface, which don't take ctx as a parameter. JaegerSpan []byte } @@ -161,8 +161,8 @@ func (key *PrivateKey) execute(ctx context.Context, op protocol.Op, msg []byte) return result.Payload, nil } -// Sign implements the crypto.Signer operation for the given key. -func (key *PrivateKey) Sign(r io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) { +// Sign implements the signer.CtxSigner operation for the given key. +func (key *PrivateKey) Sign(ctx context.Context, r io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) { spanCtx, err := tracing.SpanContextFromBinary(key.JaegerSpan) if err != nil { log.Errorf("failed to extract span: %v", err) diff --git a/client/remote_test.go b/client/remote_test.go index ff018c5f..939f16ac 100644 --- a/client/remote_test.go +++ b/client/remote_test.go @@ -2,7 +2,6 @@ package client import ( "context" - "crypto" "crypto/x509" "encoding/pem" "log" @@ -11,9 +10,8 @@ import ( "testing" "time" - "github.com/cloudflare/cfssl/helpers" - "github.com/cloudflare/cfssl/helpers/derhelpers" "github.com/cloudflare/gokeyless/server" + "github.com/cloudflare/gokeyless/signer" "github.com/stretchr/testify/require" ) @@ -37,8 +35,8 @@ var ( sAddr string s *server.Server c *Client - rsaSigner crypto.Signer - ecdsaSigner crypto.Signer + rsaSigner signer.CtxSigner + ecdsaSigner signer.CtxSigner remote Remote deadRemote Remote ) @@ -48,15 +46,15 @@ func fixedCurrentTime() time.Time { return time.Date(2019, time.March, 1, 0, 0, 0, 0, time.UTC) } -// LoadKey attempts to load a private key from PEM or DER. -func LoadKey(in []byte) (priv crypto.Signer, err error) { - priv, err = helpers.ParsePrivateKeyPEM(in) - if err == nil { - return priv, nil - } +// // LoadKey attempts to load a private key from PEM or DER. +// func LoadKey(in []byte) (priv crypto.Signer, err error) { +// priv, err = helpers.ParsePrivateKeyPEM(in) +// if err == nil { +// return priv, nil +// } - return derhelpers.ParsePrivateKeyDER(in) -} +// return derhelpers.ParsePrivateKeyDER(in) +// } // Set up compatible server and client for use by tests. func TestMain(m *testing.M) { @@ -68,7 +66,7 @@ func TestMain(m *testing.M) { } s.TLSConfig().Time = fixedCurrentTime - keys, err := server.NewKeystoreFromDir("testdata", LoadKey) + keys, err := server.NewKeystoreFromDir("testdata", server.DefaultLoadKey) if err != nil { log.Fatal(err) } @@ -241,7 +239,7 @@ func TestSlowServer(t *testing.T) { } // helper function reads a cert from a file and convert it to a signer -func NewRemoteSignerByCertFile(filepath string) (crypto.Signer, error) { +func NewRemoteSignerByCertFile(filepath string) (signer.CtxSigner, error) { pemBytes, err := os.ReadFile(filepath) if err != nil { return nil, err diff --git a/internal/azure/azure.go b/internal/azure/azure.go index e1ad871e..e194fca9 100644 --- a/internal/azure/azure.go +++ b/internal/azure/azure.go @@ -17,6 +17,7 @@ import ( "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/azure/auth" "github.com/cloudflare/cfssl/log" + "github.com/cloudflare/gokeyless/signer" jose "gopkg.in/square/go-jose.v2" ) @@ -37,9 +38,9 @@ type KeyVaultSigner struct { } // must conform to the interface -var _ crypto.Signer = KeyVaultSigner{} +var _ signer.CtxSigner = KeyVaultSigner{} -// New creates a client that implements `crypto.Signer` backed by Azure Key Vault or Azure Managed HSM at the given uri +// New creates a client that implements `signer.CtxSigner` backed by Azure Key Vault or Azure Managed HSM at the given uri // The URL should be contain the key version (i.e. `https://vault-name.vault.azure.net/keys/key-name/abc`) // Required roles are `/keys/read/action` and `/keys/sign/action`, the minimum built in Role that fuffils these is `Crypto User` // RBAC reference: https://docs.microsoft.com/en-us/azure/key-vault/managed-hsm/built-in-roles#permitted-operations @@ -77,8 +78,8 @@ func (k KeyVaultSigner) Public() crypto.PublicKey { return k.pub } -// Sign makes an API call to sign the provided bytes, mapping the hash in `crypto.SignerOps` to a JWK Signature Algorithm -func (k KeyVaultSigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { +// Sign makes an API call to sign the provided bytes, mapping the hash in `signer.CtxSignerOps` to a JWK Signature Algorithm +func (k KeyVaultSigner) Sign(ctx context.Context, _ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { // base64url required as per https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#data-types payload := base64.RawURLEncoding.EncodeToString(digest) @@ -88,7 +89,7 @@ func (k KeyVaultSigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) return nil, err } - signed, err := k.client.Sign(context.Background(), k.baseURL, k.keyName, k.keyVersion, keyvault.KeySignParameters{Algorithm: algo, Value: &payload}) + signed, err := k.client.Sign(ctx, k.baseURL, k.keyName, k.keyVersion, keyvault.KeySignParameters{Algorithm: algo, Value: &payload}) if err != nil { return nil, fmt.Errorf("azure: failed to sign: %w", err) } @@ -107,7 +108,8 @@ func (k KeyVaultSigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) return convert1363ToAsn1(res) } -// map the signature algirthm to the relevant JWK one +// map the signature algirthm to the relevant JWK one +// // see https://tools.ietf.org/html/rfc7518#section-3.1 func (k KeyVaultSigner) determineSigAlg(opts crypto.SignerOpts) (algo keyvault.JSONWebKeySignatureAlgorithm, err error) { switch { @@ -133,7 +135,7 @@ func (k KeyVaultSigner) determineSigAlg(opts crypto.SignerOpts) (algo keyvault.J // 1. fetch the specified version of the key // 2. Ensure that the key type supports a signing operation that is also supported by keyless. // 3. marshals the JWK into json, so that it can be unmarshalled into jose's JWK format -// 4. extract the public key from the JWK (crypto.Signer's `Public()` must be implemented so keyless can calculate the SKI) +// 4. extract the public key from the JWK (signer.CtxSigner's `Public()` must be implemented so keyless can calculate the SKI) // // note: other similar codebases directly massage the public key out of the keyvault.KeyBundle // see: diff --git a/internal/azure/azure_test.go b/internal/azure/azure_test.go index feb38925..3119dd5f 100644 --- a/internal/azure/azure_test.go +++ b/internal/azure/azure_test.go @@ -67,7 +67,7 @@ func TestSmoke(t *testing.T) { require.Equal(alg, keyvault.RS384) require.NoError(err) - sig, err := k.Sign(nil, []byte("digest"), crypto.SHA384) + sig, err := k.Sign(context.Background(), nil, []byte("digest"), crypto.SHA384) require.Equal(sig, []byte("digest")) require.NoError(err) diff --git a/internal/google/google.go b/internal/google/google.go index 450f09ba..19973727 100644 --- a/internal/google/google.go +++ b/internal/google/google.go @@ -12,6 +12,7 @@ import ( kms "cloud.google.com/go/kms/apiv1" "github.com/cloudflare/cfssl/log" + "github.com/cloudflare/gokeyless/signer" "github.com/googleapis/gax-go/v2" kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" "google.golang.org/protobuf/types/known/wrapperspb" @@ -22,7 +23,7 @@ type kmsClient interface { GetPublicKey(ctx context.Context, req *kmspb.GetPublicKeyRequest, opts ...gax.CallOption) (*kmspb.PublicKey, error) } -// KMSSigner is a crypto.Signer for Google Cloud KMS +// KMSSigner is a signer.CtxSigner for Google Cloud KMS type KMSSigner struct { client kmsClient name string @@ -34,7 +35,7 @@ type KMSSigner struct { } // must conform to the interface -var _ crypto.Signer = KMSSigner{} +var _ signer.CtxSigner = KMSSigner{} // New creates a new signer with the given KMS key resource name func New(name string) (*KMSSigner, error) { @@ -58,8 +59,8 @@ func (k KMSSigner) Public() crypto.PublicKey { return k.pub } -// Sign makes an API call to sign the provided bytes, mapping the hash in `crypto.SignerOps` to a JWK Signature Algorithm -func (k KMSSigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { +// Sign makes an API call to sign the provided bytes, mapping the hash in `signer.CtxSignerOps` to a JWK Signature Algorithm +func (k KMSSigner) Sign(ctx context.Context, _ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { var payload kmspb.Digest switch opts { case crypto.SHA256: // for OpECDSASignSHA256 and OpRSASignSHA256 @@ -85,7 +86,7 @@ func (k KMSSigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (sig } // Call the API. - result, err := k.client.AsymmetricSign(context.Background(), req) + result, err := k.client.AsymmetricSign(ctx, req) if err != nil { return nil, fmt.Errorf("google: failed to sign digest: %w", err) } diff --git a/internal/google/google_test.go b/internal/google/google_test.go index 22d1d4ca..76065394 100644 --- a/internal/google/google_test.go +++ b/internal/google/google_test.go @@ -40,7 +40,7 @@ func TestSmoke(t *testing.T) { } require.Equal(k.Public(), pub) - sig, err := k.Sign(nil, []byte("digest"), crypto.SHA256) + sig, err := k.Sign(context.Background(), nil, []byte("digest"), crypto.SHA256) require.NoError(err) require.Equal(sig, []byte("digest")) diff --git a/internal/rfc7512/rfc7512.go b/internal/rfc7512/rfc7512.go index e4e7b0dc..2ed7e530 100644 --- a/internal/rfc7512/rfc7512.go +++ b/internal/rfc7512/rfc7512.go @@ -3,11 +3,10 @@ // Package rfc7512 provides a parser for the PKCS #11 URI format as specified in // RFC 7512: The PKCS #11 URI Scheme. Additionally, it provides a wrapper around -// the crypto11 package for loading a key pair as a crypto.Signer object. +// the crypto11 package for loading a key pair as a signer.CtxSigner object. package rfc7512 import ( - "crypto" "fmt" "net/url" "strconv" @@ -15,6 +14,7 @@ import ( "time" "github.com/ThalesIgnite/crypto11" + "github.com/cloudflare/gokeyless/signer" ) // PKCS11URI contains the information for accessing a PKCS #11 storage object, @@ -187,7 +187,7 @@ func ParsePKCS11URI(uri string) (*PKCS11URI, error) { // // An error is returned if the crypto11 module cannot find the module, token, // or the specified object. -func LoadPKCS11Signer(pk11uri *PKCS11URI) (crypto.Signer, error) { +func LoadPKCS11Signer(pk11uri *PKCS11URI) (signer.CtxSigner, error) { config := &crypto11.Config{ Path: pk11uri.ModulePath, TokenSerial: pk11uri.Serial, @@ -208,12 +208,12 @@ func LoadPKCS11Signer(pk11uri *PKCS11URI) (crypto.Signer, error) { return nil, fmt.Errorf("pkcs11 Configure: %w", err) } - signer, err := context.FindKeyPair(pk11uri.ID, pk11uri.Object) + s, err := context.FindKeyPair(pk11uri.ID, pk11uri.Object) if err != nil { return nil, fmt.Errorf("pkcs11 FindKeyPair: %w", err) - } else if signer == nil { + } else if s == nil { return nil, fmt.Errorf("not found") } - return signer, nil + return signer.WrapSigner(s), nil } diff --git a/server/keystore.go b/server/keystore.go index f208a467..9008b095 100644 --- a/server/keystore.go +++ b/server/keystore.go @@ -2,7 +2,6 @@ package server import ( "context" - "crypto" "fmt" "os" "path/filepath" @@ -15,6 +14,7 @@ import ( "github.com/cloudflare/gokeyless/internal/google" "github.com/cloudflare/gokeyless/internal/rfc7512" "github.com/cloudflare/gokeyless/protocol" + "github.com/cloudflare/gokeyless/signer" "github.com/cloudflare/cfssl/helpers" "github.com/cloudflare/cfssl/helpers/derhelpers" @@ -30,24 +30,24 @@ type Keystore interface { // this key, so it's advisable to perform any precomputation on this key that // may speed up signing over the course of multiple signatures (e.g., // crypto/rsa.PrivateKey's Precompute method). - Get(context.Context, *protocol.Operation) (crypto.Signer, error) + Get(context.Context, *protocol.Operation) (signer.CtxSigner, error) } // DefaultKeystore is a simple in-memory Keystore. type DefaultKeystore struct { mtx sync.RWMutex - skis map[protocol.SKI]crypto.Signer + skis map[protocol.SKI]signer.CtxSigner } // NewDefaultKeystore returns a new DefaultKeystore. func NewDefaultKeystore() *DefaultKeystore { - return &DefaultKeystore{skis: make(map[protocol.SKI]crypto.Signer)} + return &DefaultKeystore{skis: make(map[protocol.SKI]signer.CtxSigner)} } // NewKeystoreFromDir creates a keystore populated from all of the ".key" files // in dir. For each ".key" file, LoadKey is called to parse the file's contents -// into a crypto.Signer, which is stored in the Keystore. -func NewKeystoreFromDir(dir string, LoadKey func([]byte) (crypto.Signer, error)) (Keystore, error) { +// into a signer.CtxSigner, which is stored in the Keystore. +func NewKeystoreFromDir(dir string, LoadKey func([]byte) (signer.CtxSigner, error)) (Keystore, error) { keys := NewDefaultKeystore() if err := keys.AddFromDir(dir, LoadKey); err != nil { return nil, err @@ -57,8 +57,8 @@ func NewKeystoreFromDir(dir string, LoadKey func([]byte) (crypto.Signer, error)) // AddFromDir adds all of the ".key" files in dir to the keystore. For each // ".key" file, LoadKey is called to parse the file's contents into a -// crypto.Signer, which is stored in the Keystore. -func (keys *DefaultKeystore) AddFromDir(dir string, LoadKey func([]byte) (crypto.Signer, error)) error { +// signer.CtxSigner, which is stored in the Keystore. +func (keys *DefaultKeystore) AddFromDir(dir string, LoadKey func([]byte) (signer.CtxSigner, error)) error { return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -72,9 +72,9 @@ func (keys *DefaultKeystore) AddFromDir(dir string, LoadKey func([]byte) (crypto } // AddFromFile adds the key in the given file to the keystore. LoadKey is called -// to parse the file's contents into a crypto.Signer, which is stored in the +// to parse the file's contents into a signer.CtxSigner, which is stored in the // Keystore. -func (keys *DefaultKeystore) AddFromFile(path string, LoadKey func([]byte) (crypto.Signer, error)) error { +func (keys *DefaultKeystore) AddFromFile(path string, LoadKey func([]byte) (signer.CtxSigner, error)) error { log.Infof("loading %s...", path) in, err := os.ReadFile(path) @@ -91,11 +91,11 @@ func (keys *DefaultKeystore) AddFromFile(path string, LoadKey func([]byte) (cryp } // AddFromURI loads all keys matching the given PKCS#11 or Azure URI to the keystore. LoadPKCS11URI -// is called to parse the URL, connect to the module, and populate a crypto.Signer, +// is called to parse the URL, connect to the module, and populate a signer.CtxSigner, // which is stored in the Keystore. func (keys *DefaultKeystore) AddFromURI(uri string) error { log.Infof("loading %s...", uri) - var priv crypto.Signer + var priv signer.CtxSigner var err error if azure.IsKeyVaultURI(uri) { priv, err = azure.New(uri) @@ -115,7 +115,7 @@ func (keys *DefaultKeystore) AddFromURI(uri string) error { // Add adds a new key to the server's internal store. Stores in maps by SKI and // (if possible) Digest, SNI, Server IP, and Client IP. -func (keys *DefaultKeystore) Add(op *protocol.Operation, priv crypto.Signer) error { +func (keys *DefaultKeystore) Add(op *protocol.Operation, priv signer.CtxSigner) error { ski, err := protocol.GetSKI(priv.Public()) if err != nil { return err @@ -131,17 +131,21 @@ func (keys *DefaultKeystore) Add(op *protocol.Operation, priv crypto.Signer) err } // DefaultLoadKey attempts to load a private key from PEM or DER. -func DefaultLoadKey(in []byte) (priv crypto.Signer, err error) { - priv, err = helpers.ParsePrivateKeyPEM(in) +func DefaultLoadKey(in []byte) (priv signer.CtxSigner, err error) { + rawPriv, err := helpers.ParsePrivateKeyPEM(in) if err == nil { - return priv, nil + return signer.WrapSigner(rawPriv), nil } - return derhelpers.ParsePrivateKeyDER(in) + rawDer, err := derhelpers.ParsePrivateKeyDER(in) + if err != nil { + return nil, err + } + return signer.WrapSigner(rawDer), nil } // Get returns a key from keys, mapped from SKI. -func (keys *DefaultKeystore) Get(ctx context.Context, op *protocol.Operation) (crypto.Signer, error) { +func (keys *DefaultKeystore) Get(ctx context.Context, op *protocol.Operation) (signer.CtxSigner, error) { span, _ := opentracing.StartSpanFromContext(ctx, "DefaultKeystore.Get") defer span.Finish() diff --git a/server/microbench_test.go b/server/microbench_test.go index 93e3df88..32259fe4 100644 --- a/server/microbench_test.go +++ b/server/microbench_test.go @@ -4,6 +4,7 @@ package server import ( + "context" "crypto" "crypto/ecdsa" "crypto/elliptic" @@ -18,6 +19,7 @@ import ( "github.com/cloudflare/gokeyless/internal/rfc7512" "github.com/cloudflare/gokeyless/internal/test/params" + "github.com/cloudflare/gokeyless/signer" "github.com/joshlf/testutil" ) @@ -42,15 +44,15 @@ func mustReadFull(tb testutil.TB, r io.Reader, b []byte) { testutil.MustPrefix(tb, "could not perform full read", err) } -func benchSign(b *testing.B, key crypto.Signer, rnd io.Reader, payload []byte, opts crypto.SignerOpts) { +func benchSign(b *testing.B, key signer.CtxSigner, rnd io.Reader, payload []byte, opts crypto.SignerOpts) { b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := key.Sign(rnd, payload, opts) + _, err := key.Sign(context.Background(), rnd, payload, opts) testutil.MustPrefix(b, "could not create signature", err) } } -func benchSignParallel(b *testing.B, key crypto.Signer, rnd io.Reader, payload []byte, opts crypto.SignerOpts) { +func benchSignParallel(b *testing.B, key signer.CtxSigner, rnd io.Reader, payload []byte, opts crypto.SignerOpts) { // The barrier is used to ensure that goroutines only start running once we // release them. var barrier, wg sync.WaitGroup @@ -60,7 +62,7 @@ func benchSignParallel(b *testing.B, key crypto.Signer, rnd io.Reader, payload [ go func() { barrier.Wait() for i := 0; i < b.N; i++ { - _, err := key.Sign(rnd, payload, opts) + _, err := key.Sign(context.Background(), rnd, payload, opts) testutil.MustPrefix(b, "could not create signature", err) } wg.Done() @@ -134,7 +136,7 @@ func benchRandParallelFor(b *testing.B, op func(rnd io.Reader)) { // benchRandForSignRSA is an RSA signature-specific wrapper around benchRandFor. func benchRandForSignRSA(b *testing.B, params params.RSASignParams) { key, payload := prepareRSASigner(b, 2, 2048, params.PayloadSize, true) - op := func(rnd io.Reader) { key.Sign(rnd, payload, params.Opts) } + op := func(rnd io.Reader) { key.Sign(context.Background(), rnd, payload, params.Opts) } benchRandFor(b, op) } @@ -142,7 +144,7 @@ func benchRandForSignRSA(b *testing.B, params params.RSASignParams) { // benchRandFor. func benchRandParallelForSignRSA(b *testing.B, params params.RSASignParams) { key, payload := prepareRSASigner(b, 2, 2048, params.PayloadSize, true) - op := func(rnd io.Reader) { key.Sign(rnd, payload, params.Opts) } + op := func(rnd io.Reader) { key.Sign(context.Background(), rnd, payload, params.Opts) } benchRandParallelFor(b, op) } @@ -158,7 +160,7 @@ func benchSignParallelRSA(b *testing.B, params params.RSASignParams) { // prepareRSASigner performs the boilerplate of generating values needed to // benchmark RSA signatures. -func prepareRSASigner(b *testing.B, primes, bits int, payloadsize int, precompute bool) (key crypto.Signer, payload []byte) { +func prepareRSASigner(b *testing.B, primes, bits int, payloadsize int, precompute bool) (key signer.CtxSigner, payload []byte) { k, err := rsa.GenerateMultiPrimeKey(rand.Reader, primes, bits) testutil.MustPrefix(b, "could not generate RSA key", err) if !precompute { @@ -166,13 +168,13 @@ func prepareRSASigner(b *testing.B, primes, bits int, payloadsize int, precomput } payload = make([]byte, payloadsize) mustReadFull(b, rand.Reader, payload[:]) - return k, payload + return signer.WrapSigner(k), payload } // benchRandForSignECDSA is an ECDSA signature-specific wrapper around benchRandFor. func benchRandForSignECDSA(b *testing.B, params params.ECDSASignParams) { key, payload := prepareECDSASigner(b, params.Curve, params.PayloadSize) - op := func(rnd io.Reader) { key.Sign(rnd, payload, params.Opts) } + op := func(rnd io.Reader) { key.Sign(context.Background(), rnd, payload, params.Opts) } benchRandFor(b, op) } @@ -180,7 +182,7 @@ func benchRandForSignECDSA(b *testing.B, params params.ECDSASignParams) { // benchRandParallelFor. func benchRandParallelForSignECDSA(b *testing.B, params params.ECDSASignParams) { key, payload := prepareECDSASigner(b, params.Curve, params.PayloadSize) - op := func(rnd io.Reader) { key.Sign(rnd, payload, params.Opts) } + op := func(rnd io.Reader) { key.Sign(context.Background(), rnd, payload, params.Opts) } benchRandParallelFor(b, op) } @@ -196,12 +198,12 @@ func benchSignParallelECDSA(b *testing.B, params params.ECDSASignParams) { // prepareECDSASigner performs the boilerplate of generating values needed to // benchmark ECDSA signatures. -func prepareECDSASigner(b *testing.B, curve elliptic.Curve, payloadsize int) (key crypto.Signer, payload []byte) { +func prepareECDSASigner(b *testing.B, curve elliptic.Curve, payloadsize int) (key signer.CtxSigner, payload []byte) { k, err := ecdsa.GenerateKey(curve, rand.Reader) testutil.MustPrefix(b, "could not generate ECDSA key", err) payload = make([]byte, payloadsize) mustReadFull(b, rand.Reader, payload[:]) - return k, payload + return signer.WrapSigner(k), payload } func benchHSMSign(b *testing.B, params params.HSMSignParams) { @@ -218,7 +220,7 @@ func benchHSMSignParallel(b *testing.B, params params.HSMSignParams) { // prepareHSMSigner performs the boilerplate of generating values needed to // benchmark signatures on a Hardware Security Module. -func prepareHSMSigner(b *testing.B, pk11uri *rfc7512.PKCS11URI, payloadsize int) (key crypto.Signer, payload []byte) { +func prepareHSMSigner(b *testing.B, pk11uri *rfc7512.PKCS11URI, payloadsize int) (key signer.CtxSigner, payload []byte) { k, err := rfc7512.LoadPKCS11Signer(pk11uri) testutil.MustPrefix(b, "could not load PKCS11 key", err) payload = make([]byte, payloadsize) diff --git a/server/nopkcs11.go b/server/nopkcs11.go index 1a89a843..fb588aa7 100644 --- a/server/nopkcs11.go +++ b/server/nopkcs11.go @@ -1,3 +1,4 @@ +//go:build !pkcs11 || !cgo // +build !pkcs11 !cgo package server @@ -7,6 +8,6 @@ import ( "fmt" ) -func loadPKCS11URI(uri string) (crypto.Signer, error) { +func loadPKCS11URI(uri string) (signer.CtxSigner, error) { return nil, fmt.Errorf("pkcs#11 support is not enabled") } diff --git a/server/pkcs11.go b/server/pkcs11.go index 58edeee7..8d40d33c 100644 --- a/server/pkcs11.go +++ b/server/pkcs11.go @@ -4,14 +4,14 @@ package server import ( - "crypto" "fmt" "github.com/cloudflare/gokeyless/internal/rfc7512" + "github.com/cloudflare/gokeyless/signer" ) // DefaultLoadURI attempts to load a signer from a PKCS#11 URI. -func DefaultLoadURI(uri string) (crypto.Signer, error) { +func DefaultLoadURI(uri string) (signer.CtxSigner, error) { // This wrapper is here in case we want to parse vendor specific values // based on the parameters in the URI or perform side operations, such // as waiting for network to be up. @@ -27,6 +27,6 @@ func DefaultLoadURI(uri string) (crypto.Signer, error) { return signer, nil } -func loadPKCS11URI(uri string) (crypto.Signer, error) { +func loadPKCS11URI(uri string) (signer.CtxSigner, error) { return DefaultLoadURI(uri) } diff --git a/server/server.go b/server/server.go index 040f7200..f0259787 100644 --- a/server/server.go +++ b/server/server.go @@ -19,6 +19,7 @@ import ( "time" "github.com/cloudflare/gokeyless/certmetrics" + "github.com/cloudflare/gokeyless/signer" "github.com/cloudflare/gokeyless/tracing" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" @@ -361,12 +362,12 @@ func (s *Server) unlimitedDo(pkt *protocol.Packet, connName string) response { return makeErrResponse(pkt, protocol.ErrKeyNotFound) } - if ed25519Key, ok := key.(ed25519.PrivateKey); ok { + if ed25519Key, ok := key.(*signer.WrappedSigner).Inner.(ed25519.PrivateKey); ok { sig := ed25519.Sign(ed25519Key, pkt.Operation.Payload) return makeRespondResponse(pkt, sig) } - sig, err := key.Sign(rand.Reader, pkt.Operation.Payload, crypto.Hash(0)) + sig, err := key.Sign(ctx, rand.Reader, pkt.Operation.Payload, crypto.Hash(0)) if err != nil { log.Errorf("Connection: %s: Signing error: %v", connName, protocol.ErrCrypto, err) return makeErrResponse(pkt, protocol.ErrCrypto) @@ -389,8 +390,7 @@ func (s *Server) unlimitedDo(pkt *protocol.Packet, connName string) response { log.Errorf("Connection %v: %s: Key is not RSA", connName, protocol.ErrCrypto) return makeErrResponse(pkt, protocol.ErrCrypto) } - - if rsaKey, ok := key.(*rsa.PrivateKey); ok { + if rsaKey, ok := key.(*signer.WrappedSigner).Inner.(*rsa.PrivateKey); ok { // Decrypt without removing padding; that's the client's responsibility. ptxt, err := textbook_rsa.Decrypt(rsaKey, pkt.Operation.Payload) if err != nil { @@ -448,10 +448,10 @@ func (s *Server) unlimitedDo(pkt *protocol.Packet, connName string) response { return makeErrResponse(pkt, protocol.ErrKeyNotFound) } - signSpan, _ := opentracing.StartSpanFromContext(ctx, "execute.Sign") + signSpan, ctx := opentracing.StartSpanFromContext(ctx, "execute.Sign") defer signSpan.Finish() var sig []byte - sig, err = key.Sign(rand.Reader, pkt.Operation.Payload, opts) + sig, err = key.Sign(ctx, rand.Reader, pkt.Operation.Payload, opts) if err != nil { tracing.LogError(span, err) log.Errorf("Connection %v: %s: Signing error: %v\n", connName, protocol.ErrCrypto, err) diff --git a/signer/signer.go b/signer/signer.go new file mode 100644 index 00000000..bf9ea920 --- /dev/null +++ b/signer/signer.go @@ -0,0 +1,30 @@ +package signer + +import ( + "context" + "crypto" + "io" +) + +// CtxSigner wraps crypto.Signer but with context +type CtxSigner interface { + Public() crypto.PublicKey + Sign(ctx context.Context, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) +} +type WrappedSigner struct { + Inner crypto.Signer +} + +func (w *WrappedSigner) Public() crypto.PublicKey { + return w.Inner.Public() +} +func (w *WrappedSigner) Sign(ctx context.Context, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { + return w.Inner.Sign(rand, digest, opts) +} + +func WrapSigner(s crypto.Signer) CtxSigner { + return &WrappedSigner{ + Inner: s, + } + +} diff --git a/tests/client_test.go b/tests/client_test.go index f89fd544..9b1ad989 100644 --- a/tests/client_test.go +++ b/tests/client_test.go @@ -23,6 +23,7 @@ import ( "github.com/cloudflare/gokeyless/client" "github.com/cloudflare/gokeyless/protocol" + "github.com/cloudflare/gokeyless/signer" ) func (s *IntegrationTestSuite) TestConnect() { @@ -103,7 +104,7 @@ func (s *IntegrationTestSuite) TestSign() { tests := []struct { name string - s crypto.Signer + s signer.CtxSigner h crypto.Hash }{ {"rsa-SHA1", s.rsaKey, crypto.SHA1}, @@ -119,7 +120,7 @@ func (s *IntegrationTestSuite) TestSign() { s.T().Run(test.name, func(t *testing.T) { require := require.New(t) - b, err := test.s.Sign(rand.Reader, hashMsg(test.h), test.h) + b, err := test.s.Sign(context.Background(), rand.Reader, hashMsg(test.h), test.h) require.NoError(err) require.NoError(checkSignature(test.s.Public(), test.h, b)) }) @@ -162,7 +163,7 @@ func (s *IntegrationTestSuite) TestEd25519Sign() { s.T().Skip("skipping test") } - b, err := s.ed25519Key.Sign(rand.Reader, testEd25519Msg, crypto.Hash(0)) + b, err := s.ed25519Key.Sign(context.Background(), rand.Reader, testEd25519Msg, crypto.Hash(0)) require.NoError(err) require.NoError(checkSignature(s.ed25519Key.Public(), crypto.Hash(0), b)) } diff --git a/tests/common_test.go b/tests/common_test.go index 3b47133b..54489580 100644 --- a/tests/common_test.go +++ b/tests/common_test.go @@ -2,7 +2,6 @@ package tests import ( "context" - "crypto" "crypto/x509" "encoding/pem" "errors" @@ -25,6 +24,7 @@ import ( "github.com/cloudflare/gokeyless/internal/test/params" "github.com/cloudflare/gokeyless/protocol" "github.com/cloudflare/gokeyless/server" + "github.com/cloudflare/gokeyless/signer" ) const ( @@ -129,7 +129,7 @@ func (d DummyRPC) PipeRead(in int, out *[]byte) (err error) { } // helper function reads a pub key from a file and convert it to a signer -func (s *IntegrationTestSuite) NewRemoteSignerByPubKeyFile(filepath string) (crypto.Signer, error) { +func (s *IntegrationTestSuite) NewRemoteSignerByPubKeyFile(filepath string) (signer.CtxSigner, error) { pemBytes, err := os.ReadFile(filepath) if err != nil { return nil, err diff --git a/tests/proxy_test.go b/tests/proxy_test.go index d56cc239..64b4c246 100644 --- a/tests/proxy_test.go +++ b/tests/proxy_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/cloudflare/gokeyless/server" + "github.com/cloudflare/gokeyless/signer" ) const ( @@ -88,7 +89,7 @@ func (s *IntegrationTestSuite) TestTLSProxy() { p, _ := pem.Decode(pemKey) rsaKey, err := x509.ParseECPrivateKey(p.Bytes) require.NoError(err) - err = keys.Add(nil, rsaKey) + err = keys.Add(nil, signer.WrapSigner(rsaKey)) require.NoError(err) clientConfig := &tls.Config{ diff --git a/tests/tests.go b/tests/tests.go index 50d696ed..4e19497d 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -16,6 +16,7 @@ import ( "github.com/cloudflare/cfssl/log" "github.com/cloudflare/go-metrics" "github.com/cloudflare/gokeyless/client" + "github.com/cloudflare/gokeyless/signer" ) // Results is a registry of metrics representing the success stats of an entire test suite. @@ -160,7 +161,7 @@ func NewPingTest(c *client.Client, server string) TestFunc { } // NewSignTests generates a map of test name to TestFunc that performs an opaque sign and verify. -func NewSignTests(priv crypto.Signer) map[string]TestFunc { +func NewSignTests(priv signer.CtxSigner) map[string]TestFunc { tests := make(map[string]TestFunc) ptxt := []byte("Test Plaintext") r := rand.Reader @@ -183,7 +184,7 @@ func NewSignTests(priv crypto.Signer) map[string]TestFunc { tests[hashName] = func(h crypto.Hash) TestFunc { return func() error { - sig, err := priv.Sign(r, msg, h) + sig, err := priv.Sign(context.Background(), r, msg, h) if err != nil { return err }