1// +build pkcs11
2
3package yubikey
4
5import (
6	"crypto"
7	"crypto/ecdsa"
8	"crypto/elliptic"
9	"crypto/rand"
10	"crypto/sha256"
11	"crypto/x509"
12	"errors"
13	"fmt"
14	"io"
15	"math/big"
16	"os"
17	"time"
18
19	"github.com/miekg/pkcs11"
20	"github.com/sirupsen/logrus"
21	"github.com/theupdateframework/notary"
22	"github.com/theupdateframework/notary/trustmanager"
23	"github.com/theupdateframework/notary/tuf/data"
24	"github.com/theupdateframework/notary/tuf/signed"
25	"github.com/theupdateframework/notary/tuf/utils"
26)
27
28const (
29	// UserPin is the user pin of a yubikey (in PIV parlance, is the PIN)
30	UserPin = "123456"
31	// SOUserPin is the "Security Officer" user pin - this is the PIV management
32	// (MGM) key, which is different than the admin pin of the Yubikey PGP interface
33	// (which in PIV parlance is the PUK, and defaults to 12345678)
34	SOUserPin = "010203040506070801020304050607080102030405060708"
35	numSlots  = 4 // number of slots in the yubikey
36
37	// KeymodeNone means that no touch or PIN is required to sign with the yubikey
38	KeymodeNone = 0
39	// KeymodeTouch means that only touch is required to sign with the yubikey
40	KeymodeTouch = 1
41	// KeymodePinOnce means that the pin entry is required once the first time to sign with the yubikey
42	KeymodePinOnce = 2
43	// KeymodePinAlways means that pin entry is required every time to sign with the yubikey
44	KeymodePinAlways = 4
45
46	// the key size, when importing a key into yubikey, MUST be 32 bytes
47	ecdsaPrivateKeySize = 32
48
49	sigAttempts = 5
50)
51
52// what key mode to use when generating keys
53var (
54	yubikeyKeymode = KeymodeTouch | KeymodePinOnce
55	// order in which to prefer token locations on the yubikey.
56	// corresponds to: 9c, 9e, 9d, 9a
57	slotIDs = []int{2, 1, 3, 0}
58)
59
60// SetYubikeyKeyMode - sets the mode when generating yubikey keys.
61// This is to be used for testing.  It does nothing if not building with tag
62// pkcs11.
63func SetYubikeyKeyMode(keyMode int) error {
64	// technically 7 (1 | 2 | 4) is valid, but KeymodePinOnce +
65	// KeymdoePinAlways don't really make sense together
66	if keyMode < 0 || keyMode > 5 {
67		return errors.New("Invalid key mode")
68	}
69	yubikeyKeymode = keyMode
70	return nil
71}
72
73// SetTouchToSignUI - allows configurable UX for notifying a user that they
74// need to touch the yubikey to sign. The callback may be used to provide a
75// mechanism for updating a GUI (such as removing a modal) after the touch
76// has been made
77func SetTouchToSignUI(notifier func(), callback func()) {
78	touchToSignUI = notifier
79	if callback != nil {
80		touchDoneCallback = callback
81	}
82}
83
84var touchToSignUI = func() {
85	fmt.Println("Please touch the attached Yubikey to perform signing.")
86}
87
88var touchDoneCallback = func() {
89	// noop
90}
91
92var pkcs11Lib string
93
94func init() {
95	for _, loc := range possiblePkcs11Libs {
96		_, err := os.Stat(loc)
97		if err == nil {
98			p := pkcs11.New(loc)
99			if p != nil {
100				pkcs11Lib = loc
101				return
102			}
103		}
104	}
105}
106
107// ErrBackupFailed is returned when a YubiStore fails to back up a key that
108// is added
109type ErrBackupFailed struct {
110	err string
111}
112
113func (err ErrBackupFailed) Error() string {
114	return fmt.Sprintf("Failed to backup private key to: %s", err.err)
115}
116
117// An error indicating that the HSM is not present (as opposed to failing),
118// i.e. that we can confidently claim that the key is not stored in the HSM
119// without notifying the user about a missing or failing HSM.
120type errHSMNotPresent struct {
121	err string
122}
123
124func (err errHSMNotPresent) Error() string {
125	return err.err
126}
127
128type yubiSlot struct {
129	role   data.RoleName
130	slotID []byte
131}
132
133// YubiPrivateKey represents a private key inside of a yubikey
134type YubiPrivateKey struct {
135	data.ECDSAPublicKey
136	passRetriever notary.PassRetriever
137	slot          []byte
138	libLoader     pkcs11LibLoader
139}
140
141// yubikeySigner wraps a YubiPrivateKey and implements the crypto.Signer interface
142type yubikeySigner struct {
143	YubiPrivateKey
144}
145
146// NewYubiPrivateKey returns a YubiPrivateKey, which implements the data.PrivateKey
147// interface except that the private material is inaccessible
148func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey,
149	passRetriever notary.PassRetriever) *YubiPrivateKey {
150
151	return &YubiPrivateKey{
152		ECDSAPublicKey: pubKey,
153		passRetriever:  passRetriever,
154		slot:           slot,
155		libLoader:      defaultLoader,
156	}
157}
158
159// Public is a required method of the crypto.Signer interface
160func (ys *yubikeySigner) Public() crypto.PublicKey {
161	publicKey, err := x509.ParsePKIXPublicKey(ys.YubiPrivateKey.Public())
162	if err != nil {
163		return nil
164	}
165
166	return publicKey
167}
168
169func (y *YubiPrivateKey) setLibLoader(loader pkcs11LibLoader) {
170	y.libLoader = loader
171}
172
173// CryptoSigner returns a crypto.Signer tha wraps the YubiPrivateKey. Needed for
174// Certificate generation only
175func (y *YubiPrivateKey) CryptoSigner() crypto.Signer {
176	return &yubikeySigner{YubiPrivateKey: *y}
177}
178
179// Private is not implemented in hardware  keys
180func (y *YubiPrivateKey) Private() []byte {
181	// We cannot return the private material from a Yubikey
182	// TODO(david): We probably want to return an error here
183	return nil
184}
185
186// SignatureAlgorithm returns which algorithm this key uses to sign - currently
187// hardcoded to ECDSA
188func (y YubiPrivateKey) SignatureAlgorithm() data.SigAlgorithm {
189	return data.ECDSASignature
190}
191
192// Sign is a required method of the crypto.Signer interface and the data.PrivateKey
193// interface
194func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
195	ctx, session, err := SetupHSMEnv(pkcs11Lib, y.libLoader)
196	if err != nil {
197		return nil, err
198	}
199	defer cleanup(ctx, session)
200
201	v := signed.Verifiers[data.ECDSASignature]
202	for i := 0; i < sigAttempts; i++ {
203		sig, err := sign(ctx, session, y.slot, y.passRetriever, msg)
204		if err != nil {
205			return nil, fmt.Errorf("failed to sign using Yubikey: %v", err)
206		}
207		if err := v.Verify(&y.ECDSAPublicKey, sig, msg); err == nil {
208			return sig, nil
209		}
210	}
211	return nil, errors.New("failed to generate signature on Yubikey")
212}
213
214// If a byte array is less than the number of bytes specified by
215// ecdsaPrivateKeySize, left-zero-pad the byte array until
216// it is the required size.
217func ensurePrivateKeySize(payload []byte) []byte {
218	final := payload
219	if len(payload) < ecdsaPrivateKeySize {
220		final = make([]byte, ecdsaPrivateKeySize)
221		copy(final[ecdsaPrivateKeySize-len(payload):], payload)
222	}
223	return final
224}
225
226// addECDSAKey adds a key to the yubikey
227func addECDSAKey(
228	ctx IPKCS11Ctx,
229	session pkcs11.SessionHandle,
230	privKey data.PrivateKey,
231	pkcs11KeyID []byte,
232	passRetriever notary.PassRetriever,
233	role data.RoleName,
234) error {
235	logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID())
236
237	err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
238	if err != nil {
239		return err
240	}
241	defer ctx.Logout(session)
242
243	// Create an ecdsa.PrivateKey out of the private key bytes
244	ecdsaPrivKey, err := x509.ParseECPrivateKey(privKey.Private())
245	if err != nil {
246		return err
247	}
248
249	ecdsaPrivKeyD := ensurePrivateKeySize(ecdsaPrivKey.D.Bytes())
250
251	// Hard-coded policy: the generated certificate expires in 10 years.
252	startTime := time.Now()
253	template, err := utils.NewCertificate(role.String(), startTime, startTime.AddDate(10, 0, 0))
254	if err != nil {
255		return fmt.Errorf("failed to create the certificate template: %v", err)
256	}
257
258	certBytes, err := x509.CreateCertificate(rand.Reader, template, template, ecdsaPrivKey.Public(), ecdsaPrivKey)
259	if err != nil {
260		return fmt.Errorf("failed to create the certificate: %v", err)
261	}
262
263	certTemplate := []*pkcs11.Attribute{
264		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
265		pkcs11.NewAttribute(pkcs11.CKA_VALUE, certBytes),
266		pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
267	}
268
269	privateKeyTemplate := []*pkcs11.Attribute{
270		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
271		pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_ECDSA),
272		pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
273		pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07}),
274		pkcs11.NewAttribute(pkcs11.CKA_VALUE, ecdsaPrivKeyD),
275		pkcs11.NewAttribute(pkcs11.CKA_VENDOR_DEFINED, yubikeyKeymode),
276	}
277
278	_, err = ctx.CreateObject(session, certTemplate)
279	if err != nil {
280		return fmt.Errorf("error importing: %v", err)
281	}
282
283	_, err = ctx.CreateObject(session, privateKeyTemplate)
284	if err != nil {
285		return fmt.Errorf("error importing: %v", err)
286	}
287
288	return nil
289}
290
291func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte) (*data.ECDSAPublicKey, data.RoleName, error) {
292	findTemplate := []*pkcs11.Attribute{
293		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
294		pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
295		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
296	}
297
298	attrTemplate := []*pkcs11.Attribute{
299		pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, []byte{0}),
300		pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{0}),
301		pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0}),
302	}
303
304	if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
305		logrus.Debugf("Failed to init: %s", err.Error())
306		return nil, "", err
307	}
308	obj, _, err := ctx.FindObjects(session, 1)
309	if err != nil {
310		logrus.Debugf("Failed to find objects: %v", err)
311		return nil, "", err
312	}
313	if err := ctx.FindObjectsFinal(session); err != nil {
314		logrus.Debugf("Failed to finalize: %s", err.Error())
315		return nil, "", err
316	}
317	if len(obj) != 1 {
318		logrus.Debugf("should have found one object")
319		return nil, "", errors.New("no matching keys found inside of yubikey")
320	}
321
322	// Retrieve the public-key material to be able to create a new ECSAKey
323	attr, err := ctx.GetAttributeValue(session, obj[0], attrTemplate)
324	if err != nil {
325		logrus.Debugf("Failed to get Attribute for: %v", obj[0])
326		return nil, "", err
327	}
328
329	// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
330	var rawPubKey []byte
331	for _, a := range attr {
332		if a.Type == pkcs11.CKA_EC_POINT {
333			rawPubKey = a.Value
334		}
335
336	}
337
338	ecdsaPubKey := ecdsa.PublicKey{Curve: elliptic.P256(), X: new(big.Int).SetBytes(rawPubKey[3:35]), Y: new(big.Int).SetBytes(rawPubKey[35:])}
339	pubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPubKey)
340	if err != nil {
341		logrus.Debugf("Failed to Marshal public key")
342		return nil, "", err
343	}
344
345	return data.NewECDSAPublicKey(pubBytes), data.CanonicalRootRole, nil
346}
347
348// sign returns a signature for a given signature request
349func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever notary.PassRetriever, payload []byte) ([]byte, error) {
350	err := login(ctx, session, passRetriever, pkcs11.CKU_USER, UserPin)
351	if err != nil {
352		return nil, fmt.Errorf("error logging in: %v", err)
353	}
354	defer ctx.Logout(session)
355
356	// Define the ECDSA Private key template
357	class := pkcs11.CKO_PRIVATE_KEY
358	privateKeyTemplate := []*pkcs11.Attribute{
359		pkcs11.NewAttribute(pkcs11.CKA_CLASS, class),
360		pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_ECDSA),
361		pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
362	}
363
364	if err := ctx.FindObjectsInit(session, privateKeyTemplate); err != nil {
365		logrus.Debugf("Failed to init find objects: %s", err.Error())
366		return nil, err
367	}
368	obj, _, err := ctx.FindObjects(session, 1)
369	if err != nil {
370		logrus.Debugf("Failed to find objects: %v", err)
371		return nil, err
372	}
373	if err = ctx.FindObjectsFinal(session); err != nil {
374		logrus.Debugf("Failed to finalize find objects: %s", err.Error())
375		return nil, err
376	}
377	if len(obj) != 1 {
378		return nil, errors.New("length of objects found not 1")
379	}
380
381	var sig []byte
382	err = ctx.SignInit(
383		session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_ECDSA, nil)}, obj[0])
384	if err != nil {
385		return nil, err
386	}
387
388	// Get the SHA256 of the payload
389	digest := sha256.Sum256(payload)
390
391	if (yubikeyKeymode & KeymodeTouch) > 0 {
392		touchToSignUI()
393		defer touchDoneCallback()
394	}
395	// a call to Sign, whether or not Sign fails, will clear the SignInit
396	sig, err = ctx.Sign(session, digest[:])
397	if err != nil {
398		logrus.Debugf("Error while signing: %s", err)
399		return nil, err
400	}
401
402	if sig == nil {
403		return nil, errors.New("Failed to create signature")
404	}
405	return sig[:], nil
406}
407
408func yubiRemoveKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever notary.PassRetriever, keyID string) error {
409	err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
410	if err != nil {
411		return err
412	}
413	defer ctx.Logout(session)
414
415	template := []*pkcs11.Attribute{
416		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
417		pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
418		//pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
419		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
420	}
421
422	if err := ctx.FindObjectsInit(session, template); err != nil {
423		logrus.Debugf("Failed to init find objects: %s", err.Error())
424		return err
425	}
426	obj, b, err := ctx.FindObjects(session, 1)
427	if err != nil {
428		logrus.Debugf("Failed to find objects: %s %v", err.Error(), b)
429		return err
430	}
431	if err := ctx.FindObjectsFinal(session); err != nil {
432		logrus.Debugf("Failed to finalize find objects: %s", err.Error())
433		return err
434	}
435	if len(obj) != 1 {
436		logrus.Debugf("should have found exactly one object")
437		return err
438	}
439
440	// Delete the certificate
441	err = ctx.DestroyObject(session, obj[0])
442	if err != nil {
443		logrus.Debugf("Failed to delete cert")
444		return err
445	}
446	return nil
447}
448
449func yubiListKeys(ctx IPKCS11Ctx, session pkcs11.SessionHandle) (keys map[string]yubiSlot, err error) {
450	keys = make(map[string]yubiSlot)
451
452	attrTemplate := []*pkcs11.Attribute{
453		pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}),
454		pkcs11.NewAttribute(pkcs11.CKA_VALUE, []byte{0}),
455	}
456
457	objs, err := listObjects(ctx, session)
458	if err != nil {
459		return nil, err
460	}
461
462	if len(objs) == 0 {
463		return nil, errors.New("no keys found in yubikey")
464	}
465	logrus.Debugf("Found %d objects matching list filters", len(objs))
466	for _, obj := range objs {
467		var (
468			cert *x509.Certificate
469			slot []byte
470		)
471		// Retrieve the public-key material to be able to create a new ECDSA
472		attr, err := ctx.GetAttributeValue(session, obj, attrTemplate)
473		if err != nil {
474			logrus.Debugf("Failed to get Attribute for: %v", obj)
475			continue
476		}
477
478		// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
479		for _, a := range attr {
480			if a.Type == pkcs11.CKA_ID {
481				slot = a.Value
482			}
483			if a.Type == pkcs11.CKA_VALUE {
484				cert, err = x509.ParseCertificate(a.Value)
485				if err != nil {
486					continue
487				}
488				if !data.ValidRole(data.RoleName(cert.Subject.CommonName)) {
489					continue
490				}
491			}
492		}
493
494		// we found nothing
495		if cert == nil {
496			continue
497		}
498
499		var ecdsaPubKey *ecdsa.PublicKey
500		switch cert.PublicKeyAlgorithm {
501		case x509.ECDSA:
502			ecdsaPubKey = cert.PublicKey.(*ecdsa.PublicKey)
503		default:
504			logrus.Infof("Unsupported x509 PublicKeyAlgorithm: %d", cert.PublicKeyAlgorithm)
505			continue
506		}
507
508		pubBytes, err := x509.MarshalPKIXPublicKey(ecdsaPubKey)
509		if err != nil {
510			logrus.Debugf("Failed to Marshal public key")
511			continue
512		}
513
514		keys[data.NewECDSAPublicKey(pubBytes).ID()] = yubiSlot{
515			role:   data.RoleName(cert.Subject.CommonName),
516			slotID: slot,
517		}
518	}
519	return
520}
521
522func listObjects(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]pkcs11.ObjectHandle, error) {
523	findTemplate := []*pkcs11.Attribute{
524		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
525		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
526	}
527
528	if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
529		logrus.Debugf("Failed to init: %s", err.Error())
530		return nil, err
531	}
532
533	objs, b, err := ctx.FindObjects(session, numSlots)
534	for err == nil {
535		var o []pkcs11.ObjectHandle
536		o, b, err = ctx.FindObjects(session, numSlots)
537		if err != nil {
538			continue
539		}
540		if len(o) == 0 {
541			break
542		}
543		objs = append(objs, o...)
544	}
545	if err != nil {
546		logrus.Debugf("Failed to find: %s %v", err.Error(), b)
547		if len(objs) == 0 {
548			return nil, err
549		}
550	}
551	if err := ctx.FindObjectsFinal(session); err != nil {
552		logrus.Debugf("Failed to finalize: %s", err.Error())
553		return nil, err
554	}
555	return objs, nil
556}
557
558func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, error) {
559	findTemplate := []*pkcs11.Attribute{
560		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
561	}
562	attrTemplate := []*pkcs11.Attribute{
563		pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}),
564	}
565
566	if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
567		logrus.Debugf("Failed to init: %s", err.Error())
568		return nil, err
569	}
570	objs, b, err := ctx.FindObjects(session, numSlots)
571	// if there are more objects than `numSlots`, get all of them until
572	// there are no more to get
573	for err == nil {
574		var o []pkcs11.ObjectHandle
575		o, b, err = ctx.FindObjects(session, numSlots)
576		if err != nil {
577			continue
578		}
579		if len(o) == 0 {
580			break
581		}
582		objs = append(objs, o...)
583	}
584	taken := make(map[int]bool)
585	if err != nil {
586		logrus.Debugf("Failed to find: %s %v", err.Error(), b)
587		return nil, err
588	}
589	if err = ctx.FindObjectsFinal(session); err != nil {
590		logrus.Debugf("Failed to finalize: %s\n", err.Error())
591		return nil, err
592	}
593	for _, obj := range objs {
594		// Retrieve the slot ID
595		attr, err := ctx.GetAttributeValue(session, obj, attrTemplate)
596		if err != nil {
597			continue
598		}
599
600		// Iterate through attributes. If an ID attr was found, mark it as taken
601		for _, a := range attr {
602			if a.Type == pkcs11.CKA_ID {
603				if len(a.Value) < 1 {
604					continue
605				}
606				// a byte will always be capable of representing all slot IDs
607				// for the Yubikeys
608				slotNum := int(a.Value[0])
609				if slotNum >= numSlots {
610					// defensive
611					continue
612				}
613				taken[slotNum] = true
614			}
615		}
616	}
617	// iterate the token locations in our preferred order and use the first
618	// available one. Otherwise exit the loop and return an error.
619	for _, loc := range slotIDs {
620		if !taken[loc] {
621			return []byte{byte(loc)}, nil
622		}
623	}
624	return nil, errors.New("yubikey has no available slots")
625}
626
627// YubiStore is a KeyStore for private keys inside a Yubikey
628type YubiStore struct {
629	passRetriever notary.PassRetriever
630	keys          map[string]yubiSlot
631	backupStore   trustmanager.KeyStore
632	libLoader     pkcs11LibLoader
633}
634
635// NewYubiStore returns a YubiStore, given a backup key store to write any
636// generated keys to (usually a KeyFileStore)
637func NewYubiStore(backupStore trustmanager.KeyStore, passphraseRetriever notary.PassRetriever) (
638	*YubiStore, error) {
639
640	s := &YubiStore{
641		passRetriever: passphraseRetriever,
642		keys:          make(map[string]yubiSlot),
643		backupStore:   backupStore,
644		libLoader:     defaultLoader,
645	}
646	s.ListKeys() // populate keys field
647	return s, nil
648}
649
650// Name returns a user friendly name for the location this store
651// keeps its data
652func (s YubiStore) Name() string {
653	return "yubikey"
654}
655
656func (s *YubiStore) setLibLoader(loader pkcs11LibLoader) {
657	s.libLoader = loader
658}
659
660// ListKeys returns a list of keys in the yubikey store
661func (s *YubiStore) ListKeys() map[string]trustmanager.KeyInfo {
662	if len(s.keys) > 0 {
663		return buildKeyMap(s.keys)
664	}
665	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
666	if err != nil {
667		logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
668		return nil
669	}
670	defer cleanup(ctx, session)
671
672	keys, err := yubiListKeys(ctx, session)
673	if err != nil {
674		logrus.Debugf("Failed to list key from the yubikey: %s", err.Error())
675		return nil
676	}
677	s.keys = keys
678
679	return buildKeyMap(keys)
680}
681
682// AddKey puts a key inside the Yubikey, as well as writing it to the backup store
683func (s *YubiStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error {
684	added, err := s.addKey(privKey.ID(), keyInfo.Role, privKey)
685	if err != nil {
686		return err
687	}
688	if added && s.backupStore != nil {
689		err = s.backupStore.AddKey(keyInfo, privKey)
690		if err != nil {
691			defer s.RemoveKey(privKey.ID())
692			return ErrBackupFailed{err: err.Error()}
693		}
694	}
695	return nil
696}
697
698// Only add if we haven't seen the key already.  Return whether the key was
699// added.
700func (s *YubiStore) addKey(keyID string, role data.RoleName, privKey data.PrivateKey) (
701	bool, error) {
702
703	// We only allow adding root keys for now
704	if role != data.CanonicalRootRole {
705		return false, fmt.Errorf(
706			"yubikey only supports storing root keys, got %s for key: %s", role, keyID)
707	}
708
709	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
710	if err != nil {
711		logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
712		return false, err
713	}
714	defer cleanup(ctx, session)
715
716	if k, ok := s.keys[keyID]; ok {
717		if k.role == role {
718			// already have the key and it's associated with the correct role
719			return false, nil
720		}
721	}
722
723	slot, err := getNextEmptySlot(ctx, session)
724	if err != nil {
725		logrus.Debugf("Failed to get an empty yubikey slot: %s", err.Error())
726		return false, err
727	}
728	logrus.Debugf("Attempting to store key using yubikey slot %v", slot)
729
730	err = addECDSAKey(
731		ctx, session, privKey, slot, s.passRetriever, role)
732	if err == nil {
733		s.keys[privKey.ID()] = yubiSlot{
734			role:   role,
735			slotID: slot,
736		}
737		return true, nil
738	}
739	logrus.Debugf("Failed to add key to yubikey: %v", err)
740
741	return false, err
742}
743
744// GetKey retrieves a key from the Yubikey only (it does not look inside the
745// backup store)
746func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, data.RoleName, error) {
747	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
748	if err != nil {
749		logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
750		if _, ok := err.(errHSMNotPresent); ok {
751			err = trustmanager.ErrKeyNotFound{KeyID: keyID}
752		}
753		return nil, "", err
754	}
755	defer cleanup(ctx, session)
756
757	key, ok := s.keys[keyID]
758	if !ok {
759		return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID}
760	}
761
762	pubKey, alias, err := getECDSAKey(ctx, session, key.slotID)
763	if err != nil {
764		logrus.Debugf("Failed to get key from slot %s: %s", key.slotID, err.Error())
765		return nil, "", err
766	}
767	// Check to see if we're returning the intended keyID
768	if pubKey.ID() != keyID {
769		return nil, "", fmt.Errorf("expected root key: %s, but found: %s", keyID, pubKey.ID())
770	}
771	privKey := NewYubiPrivateKey(key.slotID, *pubKey, s.passRetriever)
772	if privKey == nil {
773		return nil, "", errors.New("could not initialize new YubiPrivateKey")
774	}
775
776	return privKey, alias, err
777}
778
779// RemoveKey deletes a key from the Yubikey only (it does not remove it from the
780// backup store)
781func (s *YubiStore) RemoveKey(keyID string) error {
782	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
783	if err != nil {
784		logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
785		return nil
786	}
787	defer cleanup(ctx, session)
788
789	key, ok := s.keys[keyID]
790	if !ok {
791		return errors.New("Key not present in yubikey")
792	}
793	err = yubiRemoveKey(ctx, session, key.slotID, s.passRetriever, keyID)
794	if err == nil {
795		delete(s.keys, keyID)
796	} else {
797		logrus.Debugf("Failed to remove from the yubikey KeyID %s: %v", keyID, err)
798	}
799
800	return err
801}
802
803// GetKeyInfo is not yet implemented
804func (s *YubiStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
805	return trustmanager.KeyInfo{}, fmt.Errorf("Not yet implemented")
806}
807
808func cleanup(ctx IPKCS11Ctx, session pkcs11.SessionHandle) {
809	err := ctx.CloseSession(session)
810	if err != nil {
811		logrus.Debugf("Error closing session: %s", err.Error())
812	}
813	finalizeAndDestroy(ctx)
814}
815
816func finalizeAndDestroy(ctx IPKCS11Ctx) {
817	err := ctx.Finalize()
818	if err != nil {
819		logrus.Debugf("Error finalizing: %s", err.Error())
820	}
821	ctx.Destroy()
822}
823
824// SetupHSMEnv is a method that depends on the existences
825func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) (
826	IPKCS11Ctx, pkcs11.SessionHandle, error) {
827
828	if libraryPath == "" {
829		return nil, 0, errHSMNotPresent{err: "no library found"}
830	}
831	p := libLoader(libraryPath)
832
833	if p == nil {
834		return nil, 0, fmt.Errorf("failed to load library %s", libraryPath)
835	}
836
837	if err := p.Initialize(); err != nil {
838		defer finalizeAndDestroy(p)
839		return nil, 0, fmt.Errorf("found library %s, but initialize error %s", libraryPath, err.Error())
840	}
841
842	slots, err := p.GetSlotList(true)
843	if err != nil {
844		defer finalizeAndDestroy(p)
845		return nil, 0, fmt.Errorf(
846			"loaded library %s, but failed to list HSM slots %s", libraryPath, err)
847	}
848	// Check to see if we got any slots from the HSM.
849	if len(slots) < 1 {
850		defer finalizeAndDestroy(p)
851		return nil, 0, fmt.Errorf(
852			"loaded library %s, but no HSM slots found", libraryPath)
853	}
854
855	// CKF_SERIAL_SESSION: TRUE if cryptographic functions are performed in serial with the application; FALSE if the functions may be performed in parallel with the application.
856	// CKF_RW_SESSION: TRUE if the session is read/write; FALSE if the session is read-only
857	session, err := p.OpenSession(slots[0], pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
858	if err != nil {
859		defer cleanup(p, session)
860		return nil, 0, fmt.Errorf(
861			"loaded library %s, but failed to start session with HSM %s",
862			libraryPath, err)
863	}
864
865	logrus.Debugf("Initialized PKCS11 library %s and started HSM session", libraryPath)
866	return p, session, nil
867}
868
869// IsAccessible returns true if a Yubikey can be accessed
870func IsAccessible() bool {
871	if pkcs11Lib == "" {
872		return false
873	}
874	ctx, session, err := SetupHSMEnv(pkcs11Lib, defaultLoader)
875	if err != nil {
876		return false
877	}
878	defer cleanup(ctx, session)
879	return true
880}
881
882func login(ctx IPKCS11Ctx, session pkcs11.SessionHandle, passRetriever notary.PassRetriever, userFlag uint, defaultPassw string) error {
883	// try default password
884	err := ctx.Login(session, userFlag, defaultPassw)
885	if err == nil {
886		return nil
887	}
888
889	// default failed, ask user for password
890	for attempts := 0; ; attempts++ {
891		var (
892			giveup bool
893			err    error
894			user   string
895		)
896		if userFlag == pkcs11.CKU_SO {
897			user = "SO Pin"
898		} else {
899			user = "User Pin"
900		}
901		passwd, giveup, err := passRetriever(user, "yubikey", false, attempts)
902		// Check if the passphrase retriever got an error or if it is telling us to give up
903		if giveup || err != nil {
904			return trustmanager.ErrPasswordInvalid{}
905		}
906		if attempts > 2 {
907			return trustmanager.ErrAttemptsExceeded{}
908		}
909
910		// attempt to login. Loop if failed
911		err = ctx.Login(session, userFlag, passwd)
912		if err == nil {
913			return nil
914		}
915	}
916}
917
918func buildKeyMap(keys map[string]yubiSlot) map[string]trustmanager.KeyInfo {
919	res := make(map[string]trustmanager.KeyInfo)
920	for k, v := range keys {
921		res[k] = trustmanager.KeyInfo{Role: v.role, Gun: ""}
922	}
923	return res
924}
925