Replace the pkcs11 library with interfaces for easier testing.

Signed-off-by: Ying Li <ying.li@docker.com>
Signed-off-by: David Lawrence <david.lawrence@docker.com>

Signed-off-by: Ying Li <ying.li@docker.com> (github: endophage)
This commit is contained in:
Ying Li 2015-11-10 00:53:11 -08:00 committed by David Lawrence
parent 7108450a21
commit 09c0f9d05b
2 changed files with 73 additions and 17 deletions

View File

@ -0,0 +1,40 @@
// +build pkcs11
// an interface around the pkcs11 library, so that things can be mocked out
// for testing
package yubikey
import "github.com/miekg/pkcs11"
// IPKCS11 is an interface for wrapping github.com/miekg/pkcs11
type pkcs11LibLoader func(module string) IPKCS11Ctx
func defaultLoader(module string) IPKCS11Ctx {
return pkcs11.New(module)
}
// IPKCS11Ctx is an interface for wrapping the parts of
// github.com/miekg/pkcs11.Ctx that yubikeystore requires
type IPKCS11Ctx interface {
Destroy()
Initialize() error
Finalize() error
GetSlotList(tokenPresent bool) ([]uint, error)
OpenSession(slotID uint, flags uint) (pkcs11.SessionHandle, error)
CloseSession(sh pkcs11.SessionHandle) error
Login(sh pkcs11.SessionHandle, userType uint, pin string) error
Logout(sh pkcs11.SessionHandle) error
CreateObject(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) (
pkcs11.ObjectHandle, error)
DestroyObject(sh pkcs11.SessionHandle, oh pkcs11.ObjectHandle) error
GetAttributeValue(sh pkcs11.SessionHandle, o pkcs11.ObjectHandle,
a []*pkcs11.Attribute) ([]*pkcs11.Attribute, error)
FindObjectsInit(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) error
FindObjects(sh pkcs11.SessionHandle, max int) (
[]pkcs11.ObjectHandle, bool, error)
FindObjectsFinal(sh pkcs11.SessionHandle) error
SignInit(sh pkcs11.SessionHandle, m []*pkcs11.Mechanism,
o pkcs11.ObjectHandle) error
Sign(sh pkcs11.SessionHandle, message []byte) ([]byte, error)
}

View File

@ -112,17 +112,21 @@ type YubiPrivateKey struct {
data.ECDSAPublicKey
passRetriever passphrase.Retriever
slot []byte
libLoader pkcs11LibLoader
}
type YubikeySigner struct {
YubiPrivateKey
}
func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey, passRetriever passphrase.Retriever) *YubiPrivateKey {
func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey,
passRetriever passphrase.Retriever) *YubiPrivateKey {
return &YubiPrivateKey{
ECDSAPublicKey: pubKey,
passRetriever: passRetriever,
slot: slot,
libLoader: defaultLoader,
}
}
@ -135,6 +139,10 @@ func (ys *YubikeySigner) Public() crypto.PublicKey {
return publicKey
}
func (y *YubiPrivateKey) setLibLoader(loader pkcs11LibLoader) {
y.libLoader = loader
}
// CryptoSigner returns a crypto.Signer tha wraps the YubiPrivateKey. Needed for
// Certificate generation only
func (y *YubiPrivateKey) CryptoSigner() crypto.Signer {
@ -153,7 +161,7 @@ func (y YubiPrivateKey) SignatureAlgorithm() data.SigAlgorithm {
}
func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
ctx, session, err := SetupHSMEnv(pkcs11Lib)
ctx, session, err := SetupHSMEnv(pkcs11Lib, y.libLoader)
if err != nil {
return nil, err
}
@ -186,7 +194,7 @@ func ensurePrivateKeySize(payload []byte) []byte {
// addECDSAKey adds a key to the yubikey
func addECDSAKey(
ctx *pkcs11.Ctx,
ctx IPKCS11Ctx,
session pkcs11.SessionHandle,
privKey data.PrivateKey,
pkcs11KeyID []byte,
@ -255,7 +263,7 @@ func addECDSAKey(
return nil
}
func getECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte) (*data.ECDSAPublicKey, string, error) {
func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte) (*data.ECDSAPublicKey, string, error) {
findTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
@ -313,7 +321,7 @@ func getECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []by
}
// Sign returns a signature for a given signature request
func sign(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, payload []byte) ([]byte, error) {
func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, payload []byte) ([]byte, error) {
err := login(ctx, session, passRetriever, pkcs11.CKU_USER, USER_PIN)
if err != nil {
return nil, fmt.Errorf("error logging in: %v", err)
@ -367,7 +375,7 @@ func sign(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, pas
return sig[:], nil
}
func yubiRemoveKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, keyID string) error {
func yubiRemoveKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, keyID string) error {
err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SO_USER_PIN)
if err != nil {
return err
@ -408,7 +416,7 @@ func yubiRemoveKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []
return nil
}
func yubiListKeys(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) (keys map[string]yubiSlot, err error) {
func yubiListKeys(ctx IPKCS11Ctx, session pkcs11.SessionHandle) (keys map[string]yubiSlot, err error) {
keys = make(map[string]yubiSlot)
findTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
@ -501,7 +509,7 @@ func yubiListKeys(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) (keys map[strin
return
}
func getNextEmptySlot(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) ([]byte, error) {
func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, error) {
findTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
}
@ -571,6 +579,7 @@ type YubiKeyStore struct {
passRetriever passphrase.Retriever
keys map[string]yubiSlot
backupStore trustmanager.KeyStore
libLoader pkcs11LibLoader
}
// NewYubiKeyStore returns a YubiKeyStore, given a backup key store to write any
@ -582,6 +591,7 @@ func NewYubiKeyStore(backupStore trustmanager.KeyStore, passphraseRetriever pass
passRetriever: passphraseRetriever,
keys: make(map[string]yubiSlot),
backupStore: backupStore,
libLoader: defaultLoader,
}
s.ListKeys() // populate keys field
return s, nil
@ -593,11 +603,15 @@ func (s YubiKeyStore) Name() string {
return "yubikey"
}
func (s *YubiKeyStore) setLibLoader(loader pkcs11LibLoader) {
s.libLoader = loader
}
func (s *YubiKeyStore) ListKeys() map[string]string {
if len(s.keys) > 0 {
return buildKeyMap(s.keys)
}
ctx, session, err := SetupHSMEnv(pkcs11Lib)
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
if err != nil {
logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
return nil
@ -625,7 +639,7 @@ func (s *YubiKeyStore) addKey(
return fmt.Errorf("yubikey only supports storing root keys, got %s for key: %s", role, keyID)
}
ctx, session, err := SetupHSMEnv(pkcs11Lib)
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
if err != nil {
logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
return err
@ -668,7 +682,7 @@ func (s *YubiKeyStore) addKey(
// GetKey retrieves a key from the Yubikey only (it does not look inside the
// backup store)
func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
ctx, session, err := SetupHSMEnv(pkcs11Lib)
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
if err != nil {
logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
return nil, "", err
@ -700,7 +714,7 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
// RemoveKey deletes a key from the Yubikey only (it does not remove it from the
// backup store)
func (s *YubiKeyStore) RemoveKey(keyID string) error {
ctx, session, err := SetupHSMEnv(pkcs11Lib)
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
if err != nil {
logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
return nil
@ -741,18 +755,20 @@ func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error {
return s.addKey(privKey.ID(), "root", privKey, false)
}
func cleanup(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) {
func cleanup(ctx IPKCS11Ctx, session pkcs11.SessionHandle) {
ctx.CloseSession(session)
ctx.Finalize()
ctx.Destroy()
}
// SetupHSMEnv is a method that depends on the existences
func SetupHSMEnv(libraryPath string) (*pkcs11.Ctx, pkcs11.SessionHandle, error) {
func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) (
IPKCS11Ctx, pkcs11.SessionHandle, error) {
if libraryPath == "" {
return nil, 0, errors.New("No library found.")
}
p := pkcs11.New(libraryPath)
p := libLoader(libraryPath)
if p == nil {
return nil, 0, errors.New("Failed to init library")
@ -786,7 +802,7 @@ func YubikeyAccessible() bool {
if pkcs11Lib == "" {
return false
}
ctx, session, err := SetupHSMEnv(pkcs11Lib)
ctx, session, err := SetupHSMEnv(pkcs11Lib, defaultLoader)
if err != nil {
return false
}
@ -794,7 +810,7 @@ func YubikeyAccessible() bool {
return true
}
func login(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, passRetriever passphrase.Retriever, userFlag uint, defaultPassw string) error {
func login(ctx IPKCS11Ctx, session pkcs11.SessionHandle, passRetriever passphrase.Retriever, userFlag uint, defaultPassw string) error {
// try default password
err := ctx.Login(session, userFlag, defaultPassw)
if err == nil {