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