From 0fd1fa6ada02500da2dc3d5f3c50cb48680cb15b Mon Sep 17 00:00:00 2001
From: David Lawrence <david.lawrence@docker.com>
Date: Thu, 5 Nov 2015 13:48:03 -0800
Subject: [PATCH] arbitrary slots working Signed-off-by: David Lawrence
 <david.lawrence@docker.com> (github: endophage)

---
 cryptoservice/crypto_service.go             |   2 +-
 signer/api/ecdsa_hardware_crypto_service.go | 111 +++++++++++++++++---
 trustmanager/keyfilestore.go                |   4 +-
 tuf/signed/ed25519.go                       |   2 +-
 4 files changed, 102 insertions(+), 17 deletions(-)

diff --git a/cryptoservice/crypto_service.go b/cryptoservice/crypto_service.go
index 8b07a77d91..4065df0f45 100644
--- a/cryptoservice/crypto_service.go
+++ b/cryptoservice/crypto_service.go
@@ -164,7 +164,7 @@ func (cs *CryptoService) ListKeys(role string) []string {
 	return res
 }
 
-// ListKeys returns a list of key IDs valid for the given role
+// ListAllKeys returns a map of key IDs to role
 func (cs *CryptoService) ListAllKeys() map[string]string {
 	res := make(map[string]string)
 	for _, ks := range cs.keyStores {
diff --git a/signer/api/ecdsa_hardware_crypto_service.go b/signer/api/ecdsa_hardware_crypto_service.go
index 9cf1d66035..1042f24d20 100644
--- a/signer/api/ecdsa_hardware_crypto_service.go
+++ b/signer/api/ecdsa_hardware_crypto_service.go
@@ -39,16 +39,18 @@ type yubiSlot struct {
 type YubiPrivateKey struct {
 	data.ECDSAPublicKey
 	passRetriever passphrase.Retriever
+	slot          []byte
 }
 
 type YubikeySigner struct {
 	YubiPrivateKey
 }
 
-func NewYubiPrivateKey(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,
 	}
 }
 
@@ -85,7 +87,7 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts
 	}
 	defer cleanup(ctx, session)
 
-	sig, err := sign(ctx, session, YUBIKEY_ROOT_KEY_ID, y.passRetriever, msg)
+	sig, err := sign(ctx, session, y.slot, y.passRetriever, msg)
 	if err != nil {
 		return nil, fmt.Errorf("failed to sign using Yubikey: %v", err)
 	}
@@ -94,7 +96,7 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts
 }
 
 // addECDSAKey adds a key to the yubikey
-func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.PrivateKey, pkcs11KeyID []byte, passRetriever passphrase.Retriever) error {
+func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.PrivateKey, pkcs11KeyID []byte, passRetriever passphrase.Retriever, role string) error {
 	logrus.Debugf("Got into add key with key: %s\n", privKey.ID())
 
 	// TODO(diogo): Figure out CKU_SO with yubikey
@@ -113,7 +115,7 @@ func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.Pri
 	ecdsaPrivKeyD := ecdsaPrivKey.D.Bytes()
 	logrus.Debugf("Getting D bytes: %v\n", ecdsaPrivKeyD)
 
-	template, err := trustmanager.NewCertificate(data.CanonicalRootRole)
+	template, err := trustmanager.NewCertificate(role)
 	if err != nil {
 		return fmt.Errorf("failed to create the certificate template: %v", err)
 	}
@@ -395,16 +397,80 @@ func listKeys(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) (keys map[string]yu
 	return
 }
 
+func getNextEmptySlot(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) ([]byte, error) {
+	findTemplate := []*pkcs11.Attribute{
+		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
+	}
+	attrTemplate := []*pkcs11.Attribute{
+		pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}),
+	}
+
+	if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
+		logrus.Debugf("Failed to init: %s\n", err.Error())
+		return nil, err
+	}
+	objs, b, err := ctx.FindObjects(session, numSlots)
+	for err == nil {
+		var o []pkcs11.ObjectHandle
+		o, b, err = ctx.FindObjects(session, numSlots)
+		if err != nil {
+			continue
+		}
+		if len(o) == 0 {
+			break
+		}
+		objs = append(objs, o...)
+	}
+	taken := make([]bool, numSlots)
+	if err != nil {
+		logrus.Debugf("Failed to find: %s %v\n", err.Error(), b)
+		return nil, err
+	}
+	for _, obj := range objs {
+		// Retrieve the public-key material to be able to create a new HSMRSAKey
+		attr, err := ctx.GetAttributeValue(session, obj, attrTemplate)
+		if err != nil {
+			logrus.Debugf("Failed to get Attribute for: %v\n", obj)
+			continue
+		}
+
+		// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
+		for _, a := range attr {
+			if a.Type == pkcs11.CKA_ID {
+				if len(a.Value) < 1 {
+					continue
+				}
+				// max 50 slots so a single byte will always represent
+				// all possible slots positions
+				slotNum := int(a.Value[0])
+				if slotNum >= len(taken) {
+					// defensive
+					continue
+				}
+				taken[slotNum] = true
+			}
+		}
+	}
+	for i := 0; i < numSlots; i++ {
+		if !taken[i] {
+			return []byte{byte(i)}, nil
+		}
+	}
+	return nil, errors.New("Yubikey has no available slots.")
+}
+
 type YubiKeyStore struct {
 	passRetriever passphrase.Retriever
 	keys          map[string]yubiSlot
 }
 
 func NewYubiKeyStore(passphraseRetriever passphrase.Retriever) *YubiKeyStore {
-	return &YubiKeyStore{
+	s := &YubiKeyStore{
 		passRetriever: passphraseRetriever,
 		keys:          make(map[string]yubiSlot),
 	}
+	s.ListKeys() // populate keys field
+	return s
 }
 
 func (s *YubiKeyStore) ListKeys() map[string]string {
@@ -424,10 +490,10 @@ func (s *YubiKeyStore) ListKeys() map[string]string {
 	return buildKeyMap(keys)
 }
 
-func (s *YubiKeyStore) AddKey(keyID, alias string, privKey data.PrivateKey) error {
+func (s *YubiKeyStore) AddKey(keyID, role string, privKey data.PrivateKey) error {
 	// We only allow adding root keys for now
-	if alias != data.CanonicalRootRole {
-		return fmt.Errorf("yubikey only supports storing root keys, got %s for key: %s\n", alias, keyID)
+	if role != data.CanonicalRootRole {
+		return fmt.Errorf("yubikey only supports storing root keys, got %s for key: %s\n", role, keyID)
 	}
 
 	ctx, session, err := SetupHSMEnv(pkcs11Lib)
@@ -436,8 +502,18 @@ func (s *YubiKeyStore) AddKey(keyID, alias string, privKey data.PrivateKey) erro
 	}
 	defer cleanup(ctx, session)
 
-	//return addECDSAKey(ctx, session, privKey, YUBIKEY_ROOT_KEY_ID, s.passRetriever)
-	return addECDSAKey(ctx, session, privKey, []byte{3}, s.passRetriever)
+	if k, ok := s.keys[keyID]; ok {
+		if k.role == role {
+
+		}
+	}
+
+	slot, err := getNextEmptySlot(ctx, session)
+	if err != nil {
+		return err
+	}
+	logrus.Debugf("Using yubikey slot %v", slot)
+	return addECDSAKey(ctx, session, privKey, slot, s.passRetriever, role)
 }
 
 func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
@@ -447,7 +523,12 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
 	}
 	defer cleanup(ctx, session)
 
-	pubKey, alias, err := getECDSAKey(ctx, session, YUBIKEY_ROOT_KEY_ID)
+	key, ok := s.keys[keyID]
+	if !ok {
+		return nil, "", errors.New("no matching keys found inside of yubikey")
+	}
+
+	pubKey, alias, err := getECDSAKey(ctx, session, key.slotID)
 	if err != nil {
 		return nil, "", err
 	}
@@ -455,7 +536,7 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
 	if pubKey.ID() != keyID {
 		return nil, "", fmt.Errorf("expected root key: %s, but found: %s\n", keyID, pubKey.ID())
 	}
-	privKey := NewYubiPrivateKey(*pubKey, s.passRetriever)
+	privKey := NewYubiPrivateKey(key.slotID, *pubKey, s.passRetriever)
 	if privKey == nil {
 		return nil, "", errors.New("could not initialize new YubiPrivateKey")
 	}
@@ -469,7 +550,11 @@ func (s *YubiKeyStore) RemoveKey(keyID string) error {
 		return nil
 	}
 	defer cleanup(ctx, session)
-	return removeKey(ctx, session, YUBIKEY_ROOT_KEY_ID, s.passRetriever, keyID)
+	key, ok := s.keys[keyID]
+	if !ok {
+		return errors.New("Key not present in yubikey")
+	}
+	return removeKey(ctx, session, key.slotID, s.passRetriever, keyID)
 }
 
 func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) {
diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go
index ecc9af9709..e364669e46 100644
--- a/trustmanager/keyfilestore.go
+++ b/trustmanager/keyfilestore.go
@@ -268,8 +268,8 @@ func getRawKey(s LimitedFileStore, name string) ([]byte, string, error) {
 	return keyBytes, keyAlias, nil
 }
 
-// Get the password to decript the given pem bytes.  Return the password,
-// because it is useful for importing
+// GetPasswdDecryptBytes gets the password to decript the given pem bytes.
+// Returns the password and private key
 func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
 	var (
 		passwd  string
diff --git a/tuf/signed/ed25519.go b/tuf/signed/ed25519.go
index 1b9536b163..4aabb2403c 100644
--- a/tuf/signed/ed25519.go
+++ b/tuf/signed/ed25519.go
@@ -49,7 +49,7 @@ func (e *Ed25519) ListKeys(role string) []string {
 	return keyIDs
 }
 
-// ListKeys returns the list of keys IDs for the role
+// ListAllKeys returns the map of keys IDs to role
 func (e *Ed25519) ListAllKeys() map[string]string {
 	keys := make(map[string]string)
 	for id, edKey := range e.keys {