From 57246ae4aea1e098f0c8bbca3e2ccc1259e7a54b Mon Sep 17 00:00:00 2001 From: Matthew McPherrin Date: Tue, 31 Dec 2024 14:59:32 -0500 Subject: [PATCH] Remove v2 code pkcs11key has switched to the module-enabled v4 a long time ago. The v2 code at the top level should be removed. We cannot tag any more v2 releases after this, but we aren't planning to. Fixes #35 --- config.go | 10 - doc.go | 6 - key.go | 453 ---------------------------------------- key_test.go | 295 -------------------------- pkcs11key_bench_test.go | 130 ------------ pool.go | 100 --------- 6 files changed, 994 deletions(-) delete mode 100644 config.go delete mode 100644 doc.go delete mode 100644 key.go delete mode 100644 key_test.go delete mode 100644 pkcs11key_bench_test.go delete mode 100644 pool.go diff --git a/config.go b/config.go deleted file mode 100644 index 36283d5..0000000 --- a/config.go +++ /dev/null @@ -1,10 +0,0 @@ -package pkcs11key - -// Config contains configuration information required to use a PKCS -// #11 key. -type Config struct { - Module string - TokenLabel string - PIN string - PublicKeyPath string -} diff --git a/doc.go b/doc.go deleted file mode 100644 index fa9a1b5..0000000 --- a/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -package pkcs11key - -// This package represents "v2" of the pkcs11key package, which was not -// compatible with Go modules and is now frozen in amber. You should instead -// import github.com/letsencrypt/pkcs11key/v3, which has the same interface but -// will be kept up-to-date and is compatible with Go modules. diff --git a/key.go b/key.go deleted file mode 100644 index c72bf96..0000000 --- a/key.go +++ /dev/null @@ -1,453 +0,0 @@ -// Package pkcs11key implements crypto.Signer for PKCS #11 private keys. -// See https://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/pkcs11-base-v2.40.pdf -// for details of the Cryptoki PKCS#11 API. -// See https://github.com/letsencrypt/pkcs11key/blob/master/test.sh for examples -// of how to test and/or benchmark. -// Latest version of this package is v4: import "github.com/letsencrypt/pkcs11key/v4" -package pkcs11key - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rsa" - "encoding/asn1" - "errors" - "fmt" - "io" - "math/big" - "sync" - - "github.com/miekg/pkcs11" -) - -// from src/pkg/crypto/rsa/pkcs1v15.go -var hashPrefixes = map[crypto.Hash][]byte{ - crypto.MD5: {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10}, - crypto.SHA1: {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}, - crypto.SHA224: {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c}, - crypto.SHA256: {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}, - crypto.SHA384: {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30}, - crypto.SHA512: {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}, - crypto.MD5SHA1: {}, // A special TLS case which doesn't use an ASN1 prefix. - crypto.RIPEMD160: {0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14}, -} - -// from src/pkg/crypto/x509/x509.go -var ( - oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33} - oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} - oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} - oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} -) - -var curveOIDs = map[string]asn1.ObjectIdentifier{ - "P-224": oidNamedCurveP224, - "P-256": oidNamedCurveP256, - "P-384": oidNamedCurveP384, - "P-521": oidNamedCurveP521, -} - -// ctx defines the subset of pkcs11.ctx's methods that we use, so we can inject -// a different ctx for testing. -type ctx interface { - CloseSession(sh pkcs11.SessionHandle) error - FindObjectsFinal(sh pkcs11.SessionHandle) error - FindObjectsInit(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) error - FindObjects(sh pkcs11.SessionHandle, max int) ([]pkcs11.ObjectHandle, bool, error) - GetAttributeValue(sh pkcs11.SessionHandle, o pkcs11.ObjectHandle, a []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) - GetSlotList(tokenPresent bool) ([]uint, error) - GetTokenInfo(slotID uint) (pkcs11.TokenInfo, error) - Initialize() error - Login(sh pkcs11.SessionHandle, userType uint, pin string) error - Logout(sh pkcs11.SessionHandle) error - OpenSession(slotID uint, flags uint) (pkcs11.SessionHandle, error) - SignInit(sh pkcs11.SessionHandle, m []*pkcs11.Mechanism, o pkcs11.ObjectHandle) error - Sign(sh pkcs11.SessionHandle, message []byte) ([]byte, error) -} - -// Key is an implementation of the crypto.Signer interface using a key stored -// in a PKCS#11 hardware token. This enables the use of PKCS#11 tokens with -// the Go x509 library's methods for signing certificates. -// -// Each Key represents one session. Its session handle is protected internally -// by a mutex, so at most one Sign operation can be active at a time. For best -// performance you may want to instantiate multiple Keys using pkcs11key.Pool. -// Each one will have its own session and can be used concurrently. Note that -// some smartcards like the Yubikey Neo do not support multiple simultaneous -// sessions and will error out on creation of the second Key object. -// -// Note: If you instantiate multiple Keys without using Pool, it is *highly* -// recommended that you create all your Key objects serially, on your main -// thread, checking for errors each time, and then farm them out for use by -// different goroutines. If you fail to do this, your application may attempt -// to login repeatedly with an incorrect PIN, locking the PKCS#11 token. -type Key struct { - // The PKCS#11 library to use - module ctx - - // The label of the token to be used (mandatory). - // We will automatically search for this in the slot list. - tokenLabel string - - // The PIN to be used to log in to the device - pin string - - // The public key corresponding to the private key. - publicKey crypto.PublicKey - - // The an ObjectHandle pointing to the private key on the HSM. - privateKeyHandle pkcs11.ObjectHandle - - // A handle to the session used by this Key. - session *pkcs11.SessionHandle - sessionMu sync.Mutex - - // True if the private key has the CKA_ALWAYS_AUTHENTICATE attribute set. - alwaysAuthenticate bool -} - -var modules = make(map[string]ctx) -var modulesMu sync.Mutex - -// initialize loads the given PKCS#11 module (shared library) if it is not -// already loaded. It's an error to load a PKCS#11 module multiple times, so we -// maintain a map of loaded modules. Note that there is no facility yet to -// unload a module ("finalize" in PKCS#11 parlance). In general, modules will -// be unloaded at the end of the process. The only place where you are likely -// to need to explicitly unload a module is if you fork your process after a -// Key has already been created, and the child process also needs to use -// that module. -func initialize(modulePath string) (ctx, error) { - modulesMu.Lock() - defer modulesMu.Unlock() - module, ok := modules[modulePath] - if ok { - return module, nil - } - - newModule := pkcs11.New(modulePath) - if newModule == nil { - return nil, fmt.Errorf("failed to load module '%s'", modulePath) - } - - err := newModule.Initialize() - if err != nil { - return nil, fmt.Errorf("failed to initialize module: %s", err) - } - - modules[modulePath] = ctx(newModule) - - return ctx(newModule), nil -} - -// New instantiates a new handle to a PKCS #11-backed key. -// -// This function will find the private key to sign with by finding a copy of the -// provided public key in the token, then looking for a private key object that -// has the same CKA_ID as that public key. From -// https://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/pkcs11-base-v2.40.pdf: -// -// "The CKA_ID field is intended to distinguish among multiple keys. In -// the case of public and private keys, this field assists in handling -// multiple keys held by the same subject; the key identifier for a -// public key and its corresponding private key should be the same." -func New(modulePath, tokenLabel, pin string, publicKey crypto.PublicKey) (*Key, error) { - module, err := initialize(modulePath) - if err != nil { - return nil, fmt.Errorf("pkcs11key: %s", err) - } - if module == nil { - err = fmt.Errorf("pkcs11key: nil module") - return nil, err - } - - // Initialize a partial key - ps := &Key{ - module: module, - tokenLabel: tokenLabel, - pin: pin, - publicKey: publicKey, - } - - err = ps.setup() - if err != nil { - return nil, fmt.Errorf("pkcs11key: %s", err) - } - return ps, nil -} - -// findObject finds an object in the PKCS#11 token according to a template. It -// returns error if there is not exactly one result, or if there was an error -// during the find calls. It must be called with the ps.sessionMu lock held. -func (ps *Key) findObject(template []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) { - if err := ps.module.FindObjectsInit(*ps.session, template); err != nil { - return 0, err - } - - handles, moreAvailable, err := ps.module.FindObjects(*ps.session, 1) - if err != nil { - return 0, err - } - if moreAvailable { - return 0, errors.New("too many objects returned from FindObjects") - } - if err = ps.module.FindObjectsFinal(*ps.session); err != nil { - return 0, err - } else if len(handles) == 0 { - return 0, errors.New("no objects found") - } - return handles[0], nil -} - -// getPublicKeyID looks up the given public key in the PKCS#11 token, and -// returns its ID as a []byte, for use in looking up the corresponding private -// key. It must be called with the ps.sessionMu lock held. -func (ps *Key) getPublicKeyID(publicKey crypto.PublicKey) ([]byte, error) { - var template []*pkcs11.Attribute - switch key := publicKey.(type) { - case *rsa.PublicKey: - template = []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY), - pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA), - pkcs11.NewAttribute(pkcs11.CKA_MODULUS, key.N.Bytes()), - pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(key.E)).Bytes()), - } - case *ecdsa.PublicKey: - // https://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/os/pkcs11-curr-v2.40-os.html#_ftn1 - // PKCS#11 v2.20 specified that the CKA_EC_POINT was to be store in a DER-encoded - // OCTET STRING. - rawValue := asn1.RawValue{ - Tag: 4, // in Go 1.6+ this is asn1.TagOctetString - Bytes: elliptic.Marshal(key.Curve, key.X, key.Y), - } - marshalledPoint, err := asn1.Marshal(rawValue) - if err != nil { - return nil, err - } - curveOID, err := asn1.Marshal(curveOIDs[key.Curve.Params().Name]) - if err != nil { - return nil, err - } - template = []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY), - pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_EC), - pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, curveOID), - pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, marshalledPoint), - } - default: - return nil, fmt.Errorf("unsupported public key of type %T", publicKey) - } - - publicKeyHandle, err := ps.findObject(template) - if err != nil { - return nil, err - } - - attrs, err := ps.module.GetAttributeValue(*ps.session, publicKeyHandle, []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_ID, nil), - }) - if err != nil { - return nil, err - } - if len(attrs) > 0 && attrs[0].Type == pkcs11.CKA_ID { - return attrs[0].Value, nil - } - return nil, fmt.Errorf("invalid result from GetAttributeValue") -} - -func (ps *Key) setup() error { - // Open a session - ps.sessionMu.Lock() - defer ps.sessionMu.Unlock() - session, err := ps.openSession() - if err != nil { - return fmt.Errorf("pkcs11key: opening session: %s", err) - } - ps.session = &session - - publicKeyID, err := ps.getPublicKeyID(ps.publicKey) - if err != nil { - ps.module.CloseSession(session) - return fmt.Errorf("looking up public key: %s", err) - } - - // Fetch the private key by matching its id to the public key handle. - privateKeyHandle, err := ps.getPrivateKey(ps.module, session, publicKeyID) - if err != nil { - ps.module.CloseSession(session) - return fmt.Errorf("getting private key: %s", err) - } - ps.privateKeyHandle = privateKeyHandle - return nil -} - -// getPrivateKey gets a handle to the private key whose CKA_ID matches the -// provided publicKeyID. It must be called with the ps.sessionMu lock held. -func (ps *Key) getPrivateKey(module ctx, session pkcs11.SessionHandle, publicKeyID []byte) (pkcs11.ObjectHandle, error) { - var noHandle pkcs11.ObjectHandle - privateKeyHandle, err := ps.findObject([]*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY), - pkcs11.NewAttribute(pkcs11.CKA_ID, publicKeyID), - }) - if err != nil { - return noHandle, err - } - - // Check whether the key has the CKA_ALWAYS_AUTHENTICATE attribute. - // If so, fail: we don't want to have to re-authenticate for each sign - // operation. - attributes, err := module.GetAttributeValue(session, privateKeyHandle, []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_ALWAYS_AUTHENTICATE, false), - }) - // The PKCS#11 spec states that C_GetAttributeValue may return - // CKR_ATTRIBUTE_TYPE_INVALID if an object simply does not posses a given - // attribute. We don't consider that an error: the absence of the - // CKR_ATTRIBUTE_TYPE_INVALID property is just fine. - if err != nil && err == pkcs11.Error(pkcs11.CKR_ATTRIBUTE_TYPE_INVALID) { - return privateKeyHandle, nil - } else if err != nil { - return noHandle, err - } - if len(attributes) > 0 && len(attributes[0].Value) > 0 && attributes[0].Value[0] == 1 { - ps.alwaysAuthenticate = true - } - - return privateKeyHandle, nil -} - -// Destroy tears down a Key by closing the session. It should be -// called before the key gets GC'ed, to avoid leaving dangling sessions. -func (ps *Key) Destroy() error { - if ps.session != nil { - // NOTE: We do not want to call module.Logout here. module.Logout applies - // application-wide. So if there are multiple sessions active, the other ones - // would be logged out as well, causing CKR_OBJECT_HANDLE_INVALID next - // time they try to sign something. It's also unnecessary to log out explicitly: - // module.CloseSession will log out once the last session in the application is - // closed. - ps.sessionMu.Lock() - defer ps.sessionMu.Unlock() - err := ps.module.CloseSession(*ps.session) - ps.session = nil - if err != nil { - return fmt.Errorf("pkcs11key: close session: %s", err) - } - } - return nil -} - -func (ps *Key) openSession() (pkcs11.SessionHandle, error) { - var noSession pkcs11.SessionHandle - slots, err := ps.module.GetSlotList(true) - if err != nil { - return noSession, err - } - - for _, slot := range slots { - // Check that token label matches. - tokenInfo, err := ps.module.GetTokenInfo(slot) - if err != nil { - return noSession, err - } - if tokenInfo.Label != ps.tokenLabel { - continue - } - - // Open session - session, err := ps.module.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION) - if err != nil { - return session, err - } - - // Login - // Note: Logged-in status is application-wide, not per session. But in - // practice it appears to be okay to login to a token multiple times with the same - // credentials. - if err = ps.module.Login(session, pkcs11.CKU_USER, ps.pin); err != nil { - if err == pkcs11.Error(pkcs11.CKR_USER_ALREADY_LOGGED_IN) { - // But if the token says we're already logged in, it's ok. - err = nil - } else { - ps.module.CloseSession(session) - return session, err - } - } - - return session, err - } - return noSession, fmt.Errorf("no slot found matching token label %q", ps.tokenLabel) -} - -// Public returns the public key for the PKCS #11 key. -func (ps *Key) Public() crypto.PublicKey { - return ps.publicKey -} - -// Sign performs a signature using the PKCS #11 key. -func (ps *Key) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) { - ps.sessionMu.Lock() - defer ps.sessionMu.Unlock() - if ps.session == nil { - return nil, errors.New("pkcs11key: session was nil") - } - - // When the alwaysAuthenticate bit is true (e.g. on a Yubikey NEO in PIV mode), - // each Sign has to include a Logout/Login, or the next Sign request will get - // CKR_USER_NOT_LOGGED_IN. This is very slow, but on the NEO it's not possible - // to clear the CKA_ALWAYS_AUTHENTICATE bit, so this is the only available - // workaround. - // Also, since logged in / logged out is application state rather than session - // state, we take a global lock while we do the logout and login, and during - // the signing. - if ps.alwaysAuthenticate { - modulesMu.Lock() - defer modulesMu.Unlock() - if err := ps.module.Logout(*ps.session); err != nil { - return nil, fmt.Errorf("pkcs11key: logout: %s", err) - } - if err = ps.module.Login(*ps.session, pkcs11.CKU_CONTEXT_SPECIFIC, ps.pin); err != nil { - return nil, fmt.Errorf("pkcs11key: login: %s", err) - } - } - - // Verify that the length of the hash is as expected - hash := opts.HashFunc() - hashLen := hash.Size() - if len(msg) != hashLen { - err = fmt.Errorf("pkcs11key: input size does not match hash function output size: %d vs %d", len(msg), hashLen) - return - } - - // Add DigestInfo prefix - var mechanism []*pkcs11.Mechanism - var signatureInput []byte - - switch ps.publicKey.(type) { - case *rsa.PublicKey: - mechanism = []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil)} - prefix, ok := hashPrefixes[hash] - if !ok { - err = errors.New("pkcs11key: unknown hash function") - return - } - signatureInput = append(prefix, msg...) - case *ecdsa.PublicKey: - mechanism = []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_ECDSA, nil)} - signatureInput = msg - default: - return nil, fmt.Errorf("unrecognized key type %T", ps.publicKey) - } - - // Perform the sign operation - err = ps.module.SignInit(*ps.session, mechanism, ps.privateKeyHandle) - if err != nil { - return nil, fmt.Errorf("pkcs11key: sign init: %s", err) - } - - signature, err = ps.module.Sign(*ps.session, signatureInput) - if err != nil { - return nil, fmt.Errorf("pkcs11key: sign: %s", err) - } - return -} diff --git a/key_test.go b/key_test.go deleted file mode 100644 index c89eb13..0000000 --- a/key_test.go +++ /dev/null @@ -1,295 +0,0 @@ -package pkcs11key - -import ( - "bytes" - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "fmt" - "math/big" - "reflect" - "testing" - - "github.com/miekg/pkcs11" -) - -type mockCtx struct { - currentSearch []*pkcs11.Attribute -} - -const sessionHandle = pkcs11.SessionHandle(17) - -// A trivial RSA public key for use in testing. We provide a CKA_ID and a -// marshalled copy so we can return the relevent items from the mocked pkcs11 -// module. -var rsaKey = &rsa.PublicKey{N: big.NewInt(1), E: 1} - -const rsaPrivateKeyHandle = pkcs11.ObjectHandle(23) -const rsaPublicKeyHandle = pkcs11.ObjectHandle(24) -const rsaKeyID = byte(0x04) - -// A fake EC public key for use in testing. See RSA above. -var ecKey = &ecdsa.PublicKey{X: big.NewInt(1), Y: big.NewInt(1), Curve: elliptic.P256()} - -const ecPrivateKeyHandle = pkcs11.ObjectHandle(32) -const ecPublicKeyHandle = pkcs11.ObjectHandle(33) -const ecKeyID = byte(0x03) - -var slots = []uint{7, 8, 9} -var tokenInfo = pkcs11.TokenInfo{ - Label: "token label", -} - -func (c *mockCtx) CloseSession(sh pkcs11.SessionHandle) error { - return nil -} - -func (c *mockCtx) FindObjectsFinal(sh pkcs11.SessionHandle) error { - c.currentSearch = []*pkcs11.Attribute{} - return nil -} - -func (c *mockCtx) FindObjectsInit(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) error { - c.currentSearch = temp - return nil -} - -func (c *mockCtx) FindObjects(sh pkcs11.SessionHandle, max int) ([]pkcs11.ObjectHandle, bool, error) { - if reflect.DeepEqual(c.currentSearch, []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY), - pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA), - pkcs11.NewAttribute(pkcs11.CKA_MODULUS, rsaKey.N.Bytes()), - pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(rsaKey.E)).Bytes()), - }) { - return []pkcs11.ObjectHandle{rsaPublicKeyHandle}, false, nil - } - if reflect.DeepEqual(c.currentSearch, []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY), - pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{rsaKeyID}), - }) { - return []pkcs11.ObjectHandle{rsaPrivateKeyHandle}, false, nil - } - - if reflect.DeepEqual(c.currentSearch, []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY), - pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_EC), - pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []uint8{0x6, 0x8, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7}), - pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []uint8{0x4, 0x41, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}), - }) { - return []pkcs11.ObjectHandle{ecPublicKeyHandle}, false, nil - } - if reflect.DeepEqual(c.currentSearch, []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY), - pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{ecKeyID}), - }) { - return []pkcs11.ObjectHandle{ecPrivateKeyHandle}, false, nil - } - fmt.Println("unrecognized search:") - for _, v := range c.currentSearch { - fmt.Printf(" Type: %x, Value: %x\n", v.Type, v.Value) - } - return nil, false, nil -} - -func p11Attribute(Type uint, Value []byte) *pkcs11.Attribute { - return &pkcs11.Attribute{ - Type: Type, - Value: Value, - } -} - -func rsaPublicAttributes(template []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) { - var output []*pkcs11.Attribute - for _, a := range template { - if a.Type == pkcs11.CKA_ID { - output = append(output, p11Attribute(a.Type, []byte{rsaKeyID})) - } - } - return output, nil -} - -func rsaPrivateAttributes(template []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) { - var output []*pkcs11.Attribute - for _, a := range template { - // Return CKA_ALWAYS_AUTHENTICATE = 1 (true) - if a.Type == pkcs11.CKA_ALWAYS_AUTHENTICATE { - output = append(output, p11Attribute(a.Type, []byte{byte(1)})) - } - } - return output, nil -} - -func ecPublicKeyAttributes(template []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) { - var output []*pkcs11.Attribute - for _, a := range template { - switch a.Type { - case pkcs11.CKA_ID: - output = append(output, p11Attribute(a.Type, []byte{byte(ecKeyID)})) - } - } - return output, nil -} - -func (c *mockCtx) GetAttributeValue(sh pkcs11.SessionHandle, o pkcs11.ObjectHandle, template []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) { - switch o { - case rsaPrivateKeyHandle: - return rsaPrivateAttributes(template) - case rsaPublicKeyHandle: - return rsaPublicAttributes(template) - case ecPublicKeyHandle: - return ecPublicKeyAttributes(template) - default: - return nil, nil - } -} - -func (c *mockCtx) GetSlotList(tokenPresent bool) ([]uint, error) { - return slots, nil -} - -func (c *mockCtx) GetTokenInfo(slotID uint) (pkcs11.TokenInfo, error) { - return tokenInfo, nil -} - -func (c *mockCtx) Initialize() error { - return nil -} - -func (c *mockCtx) Login(sh pkcs11.SessionHandle, userType uint, pin string) error { - return nil -} - -func (c *mockCtx) Logout(sh pkcs11.SessionHandle) error { - return nil -} - -func (c *mockCtx) OpenSession(slotID uint, flags uint) (pkcs11.SessionHandle, error) { - return sessionHandle, nil -} - -func (c *mockCtx) SignInit(sh pkcs11.SessionHandle, m []*pkcs11.Mechanism, o pkcs11.ObjectHandle) error { - return nil -} - -func (c *mockCtx) Sign(sh pkcs11.SessionHandle, message []byte) ([]byte, error) { - return message, nil -} - -func setup(t *testing.T, pubKey crypto.PublicKey) *Key { - ps := Key{ - module: &mockCtx{}, - tokenLabel: "token label", - pin: "unused", - publicKey: pubKey, - } - err := ps.setup() - if err != nil { - t.Fatalf("Failed to set up Key of type %T: %s", pubKey, err) - } - return &ps -} - -var signInput = []byte("1234567890 1234567890 1234567890") - -func sign(t *testing.T, ps *Key) []byte { - // Sign input must be exactly 32 bytes to match SHA256 size. In normally - // usage, Sign would be called by e.g. x509.CreateCertificate, which would - // handle padding to the necessary size. - output, err := ps.Sign(rand.Reader, signInput, crypto.SHA256) - if err != nil { - t.Fatalf("Failed to sign: %s", err) - } - - if len(output) < len(signInput) { - t.Fatalf("Invalid signature size %d, expected at least %d", len(output), len(signInput)) - } - - i := len(output) - len(signInput) - if !bytes.Equal(output[i:], signInput) { - t.Fatal("Incorrect sign output") - } - return output -} - -func TestInitializeBadModule(t *testing.T) { - ctx, err := initialize("/dev/null") - if err == nil { - t.Errorf("Expected failure when initializing modulePath /dev/null, got none") - } - if ctx != nil { - t.Errorf("Expected nil ctx when initializing modulePath /dev/null") - } -} - -func TestInitializeKeyNotFound(t *testing.T) { - pubKey := &rsa.PublicKey{N: big.NewInt(2), E: 2} - ps := Key{ - module: &mockCtx{}, - tokenLabel: "token label", - pin: "unused", - publicKey: pubKey, - } - err := ps.setup() - expectedText := "looking up public key: no objects found" - if err == nil { - t.Errorf("Expected error looking up nonexistent key") - } else if err.Error() != expectedText { - t.Errorf("Expected error to contain %q, got %q", expectedText, err) - } -} - -func TestSign(t *testing.T) { - ps := setup(t, rsaKey) - sig := sign(t, ps) - - // Check that the RSA signature starts with the SHA256 hash prefix - var sha256Pre = []byte{0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, - 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20} - if !(bytes.Equal(sha256Pre, sig[0:19])) { - t.Fatal("RSA signature doesn't start with prefix") - } - - pub := ps.Public() - // Check public key is of right type - _, ok := pub.(*rsa.PublicKey) - if !ok { - t.Errorf("Attempted to get RSA key from Key, got key of type %s. Expected *rsa.PublicKey", reflect.TypeOf(pub)) - } - - ps = setup(t, ecKey) - sig = sign(t, ps) - - if !(bytes.Equal(signInput, sig)) { - t.Fatal("ECDSA signature error") - } -} - -// This is a version of the mock that gives CKR_ATTRIBUTE_TYPE_INVALID when -// asked about the CKA_ALWAYS_AUTHENTICATE attribute. -type mockCtxFailsAlwaysAuthenticate struct { - mockCtx -} - -func (c *mockCtxFailsAlwaysAuthenticate) GetAttributeValue(sh pkcs11.SessionHandle, o pkcs11.ObjectHandle, template []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) { - for _, a := range template { - if a.Type == pkcs11.CKA_ALWAYS_AUTHENTICATE { - return nil, pkcs11.Error(pkcs11.CKR_ATTRIBUTE_TYPE_INVALID) - } - } - return c.mockCtx.GetAttributeValue(sh, o, template) -} - -func TestAttributeTypeInvalid(t *testing.T) { - ps := &Key{ - module: &mockCtxFailsAlwaysAuthenticate{}, - tokenLabel: "token label", - pin: "unused", - publicKey: rsaKey, - } - err := ps.setup() - if err != nil { - t.Errorf("Failed to set up with a token that returns CKR_ATTRIBUTE_TYPE_INVALID: %s", err) - } -} diff --git a/pkcs11key_bench_test.go b/pkcs11key_bench_test.go deleted file mode 100644 index 124d7e2..0000000 --- a/pkcs11key_bench_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package pkcs11key - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "flag" - "fmt" - "io/ioutil" - "math/big" - "runtime" - "testing" - "time" -) - -var module = flag.String("module", "", "Path to PKCS11 module") -var tokenLabel = flag.String("tokenLabel", "", "Token label") -var pin = flag.String("pin", "", "PIN") -var certFile = flag.String("cert", "", "Certificate to sign with (PEM)") -var sessionCount = flag.Int("sessions", runtime.GOMAXPROCS(-1), `Number of PKCS#11 sessions to use. -For SoftHSM, GOMAXPROCS is appropriate, but for an external HSM the optimum session count depends on the HSM's parallelism.`) - -func readCert(certContents []byte) (*x509.Certificate, error) { - block, _ := pem.Decode(certContents) - if block == nil { - return nil, fmt.Errorf("no PEM found") - } else if block.Type != "CERTIFICATE" { - return nil, fmt.Errorf("incorrect PEM type %s", block.Type) - } - return x509.ParseCertificate(block.Bytes) -} - -// BenchmarkPKCS11 signs a certificate repeatedly using a PKCS11 token and -// measures speed. To run (with SoftHSM): -// go test -bench=. -benchtime 5s ./crypto/pkcs11key/ \ -// -module /usr/lib/softhsm/libsofthsm.so -token-label "softhsm token" \ -// -pin 1234 -private-key-label "my key" -cpu 4 -// You can adjust benchtime if you want to run for longer or shorter, and change -// the number of CPUs to select the parallelism you want. -func BenchmarkPKCS11(b *testing.B) { - if *module == "" || *tokenLabel == "" || *pin == "" || *certFile == "" { - b.Fatal("Must pass all flags: module, tokenLabel, pin, and cert") - return - } - - certContents, err := ioutil.ReadFile(*certFile) - if err != nil { - b.Fatalf("failed to read %s: %s", *certFile, err) - } - cert, err := readCert(certContents) - if err != nil { - b.Fatalf("failed to parse %s: %s", *certFile, err) - } - - // A minimal, bogus certificate to be signed. - // Note: we choose a large N to make up for some of the missing fields in the - // bogus certificate, so we wind up something approximately the size of a real - // certificate. - N := big.NewInt(1) - N.Lsh(N, 6000) - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - PublicKeyAlgorithm: x509.RSA, - NotBefore: time.Now(), - NotAfter: time.Now(), - - PublicKey: &rsa.PublicKey{ - N: N, - E: 1 << 17, - }, - } - - pool, err := NewPool(*sessionCount, *module, *tokenLabel, *pin, cert.PublicKey) - if err != nil { - b.Fatal(err) - return - } - defer pool.Destroy() - - instance := pool.get() - if instance.alwaysAuthenticate { - b.Log("WARNING: Token has CKA_ALWAYS_AUTHENTICATE attribute, which makes signing slow.") - } - pool.put(instance) - - // Reset the benchmarking timer so we don't include setup time. - b.ResetTimer() - - // Start recording total time. Go's benchmarking code is interested in - // nanoseconds per op, but we're also interested in the total throughput. - start := time.Now() - - // Note: In high-performance HSMs, we expect there to be multiple cores, - // allowing multiple signing operations to be inflight at once - that's what - // the sessionCount parameter is for. However, each individual call to - // CreateCertificate (which in turn calls pool.Sign) will block until it is - // done. For instance, consider an HSM with 32 cores. If the benchmark-running - // machine has 4 CPUs and thus GOMAXPROCS=4, b.RunParallel will run 4 - // goroutines requesting signing, and the benchmark will not reach peak - // performance. - // - // Note that this does not have to do with CGO and the Go scheduler at all. If - // a C function blocks for more than 20us (signatures take roughly 10ms), the Go - // scheduler will spawn an extra thread so other goroutines can keep running. - // http://stackoverflow.com/a/28356944/363869 - // - // In practice, this means that code using a Pool should be calling it from at - // least as many goroutines as there are entries in the pool. In a typical - // HTTP or RPC setting, where each request is handled in its own goroutine, - // this will not be a problem. - b.SetParallelism(*sessionCount) - - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - _, err = x509.CreateCertificate(rand.Reader, &template, &template, template.PublicKey, pool) - if err != nil { - b.Fatal(err) - return - } - } - }) - - elapsedTime := time.Now().Sub(start) - b.Logf("Time, count, ops / second: %s, %d, %g", elapsedTime, b.N, float64(b.N)*float64(time.Second)/float64(elapsedTime)) -} - -// Dummy test to avoid getting "warning: no tests found" -func TestNothing(t *testing.T) { -} diff --git a/pool.go b/pool.go deleted file mode 100644 index 860305d..0000000 --- a/pool.go +++ /dev/null @@ -1,100 +0,0 @@ -package pkcs11key - -import ( - "crypto" - "fmt" - "io" - "sync" -) - -// Pool is a pool of Keys suitable for high performance parallel work. Key -// on its own is suitable for multi-threaded use because it has built-in -// locking, but one Key can have at most one operation inflight at a time. -// If you are using an HSM that supports multiple sessions, you may want to -// use a Pool instead, which contains multiple signers. Pool satisfies the -// Signer interface just as Key does, and farms out work to multiple sessions -// under the hood. This assumes you are calling Sign from multiple goroutines -// (as would be common in an RPC or HTTP environment). If you only call Sign -// from a single goroutine, you will only ever get single-session performance. -type Pool struct { - // This slice acts more or less like a concurrent stack. Keys are popped off - // the top for use, and then pushed back on when they are no longer in use. - signers []*Key - // The initial length of signers, before any are popped off for use. - totalCount int - // This variable signals the condition that there are Keys available to be - // used. - cond *sync.Cond -} - -func (p *Pool) get() *Key { - p.cond.L.Lock() - for len(p.signers) == 0 { - p.cond.Wait() - } - - instance := p.signers[len(p.signers)-1] - p.signers = p.signers[:len(p.signers)-1] - p.cond.L.Unlock() - return instance -} - -func (p *Pool) put(instance *Key) { - p.cond.L.Lock() - p.signers = append(p.signers, instance) - p.cond.Signal() - p.cond.L.Unlock() -} - -// Sign performs a signature using an available PKCS #11 key. If there is no key -// available, it blocks until there is. -func (p *Pool) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) { - instance := p.get() - defer p.put(instance) - return instance.Sign(rand, msg, opts) -} - -// Public returns the public key of any one of the signers in the pool. Since -// they were all created with the same arguments, the public key should be the -// same for each one. -func (p *Pool) Public() crypto.PublicKey { - instance := p.get() - defer p.put(instance) - return instance.Public() -} - -// NewPool creates a pool of Keys of size n. -func NewPool(n int, modulePath, tokenLabel, pin string, publicKey crypto.PublicKey) (*Pool, error) { - var err error - signers := make([]*Key, n) - for i := 0; i < n; i++ { - signers[i], err = New(modulePath, tokenLabel, pin, publicKey) - // If any of the signers fail, exit early. This could be, e.g., a bad PIN, - // and we want to make sure not to lock the token. - if err != nil { - for j := 0; j < i; j++ { - signers[j].Destroy() - } - return nil, fmt.Errorf("pkcs11key: problem making Key: %s", err) - } - } - - var mutex sync.Mutex - return &Pool{ - signers: signers, - totalCount: len(signers), - cond: sync.NewCond(&mutex), - }, nil -} - -// Destroy calls destroy for each of the member keys, shutting down their -// sessions. -func (p *Pool) Destroy() error { - for i := 0; i < p.totalCount; i++ { - err := p.get().Destroy() - if err != nil { - return fmt.Errorf("pkcs11key: destroy: %s", err) - } - } - return nil -}