2017-04-17 18:08:24 -04:00
|
|
|
package trustmanager
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
2017-09-11 17:07:00 -04:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-10-30 12:21:41 -04:00
|
|
|
"github.com/theupdateframework/notary"
|
|
|
|
store "github.com/theupdateframework/notary/storage"
|
|
|
|
"github.com/theupdateframework/notary/tuf/data"
|
|
|
|
"github.com/theupdateframework/notary/tuf/utils"
|
2017-04-17 18:08:24 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
type keyInfoMap map[string]KeyInfo
|
|
|
|
|
2017-08-24 18:40:24 -04:00
|
|
|
type cachedKey struct {
|
|
|
|
role data.RoleName
|
|
|
|
key data.PrivateKey
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GenericKeyStore is a wrapper for Storage instances that provides
|
|
|
|
// translation between the []byte form and Public/PrivateKey objects
|
|
|
|
type GenericKeyStore struct {
|
|
|
|
store Storage
|
|
|
|
sync.Mutex
|
|
|
|
notary.PassRetriever
|
|
|
|
cachedKeys map[string]*cachedKey
|
|
|
|
keyInfoMap
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
|
|
|
|
// hold the keys.
|
|
|
|
func NewKeyFileStore(baseDir string, p notary.PassRetriever) (*GenericKeyStore, error) {
|
|
|
|
fileStore, err := store.NewPrivateKeyFileStorage(baseDir, notary.KeyExtension)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return NewGenericKeyStore(fileStore, p), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory
|
|
|
|
func NewKeyMemoryStore(p notary.PassRetriever) *GenericKeyStore {
|
|
|
|
memStore := store.NewMemoryStore(nil)
|
|
|
|
return NewGenericKeyStore(memStore, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewGenericKeyStore creates a GenericKeyStore wrapping the provided
|
|
|
|
// Storage instance, using the PassRetriever to enc/decrypt keys
|
|
|
|
func NewGenericKeyStore(s Storage, p notary.PassRetriever) *GenericKeyStore {
|
|
|
|
ks := GenericKeyStore{
|
|
|
|
store: s,
|
|
|
|
PassRetriever: p,
|
|
|
|
cachedKeys: make(map[string]*cachedKey),
|
|
|
|
keyInfoMap: make(keyInfoMap),
|
|
|
|
}
|
|
|
|
ks.loadKeyInfo()
|
|
|
|
return &ks
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateKeyInfoMap(s Storage) map[string]KeyInfo {
|
|
|
|
keyInfoMap := make(map[string]KeyInfo)
|
|
|
|
for _, keyPath := range s.ListFiles() {
|
|
|
|
d, err := s.Get(keyPath)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Error(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
keyID, keyInfo, err := KeyInfoFromPEM(d, keyPath)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Error(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
keyInfoMap[keyID] = keyInfo
|
|
|
|
}
|
|
|
|
return keyInfoMap
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *GenericKeyStore) loadKeyInfo() {
|
|
|
|
s.keyInfoMap = generateKeyInfoMap(s.store)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetKeyInfo returns the corresponding gun and role key info for a keyID
|
|
|
|
func (s *GenericKeyStore) GetKeyInfo(keyID string) (KeyInfo, error) {
|
|
|
|
if info, ok := s.keyInfoMap[keyID]; ok {
|
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
return KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddKey stores the contents of a PEM-encoded private key as a PEM block
|
|
|
|
func (s *GenericKeyStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error {
|
|
|
|
var (
|
|
|
|
chosenPassphrase string
|
|
|
|
giveup bool
|
|
|
|
err error
|
|
|
|
pemPrivKey []byte
|
|
|
|
)
|
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
|
|
|
if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
|
|
|
|
keyInfo.Gun = ""
|
|
|
|
}
|
2017-08-24 18:40:24 -04:00
|
|
|
keyID := privKey.ID()
|
2017-04-17 18:08:24 -04:00
|
|
|
for attempts := 0; ; attempts++ {
|
2017-08-24 18:40:24 -04:00
|
|
|
chosenPassphrase, giveup, err = s.PassRetriever(keyID, keyInfo.Role.String(), true, attempts)
|
2017-04-17 18:08:24 -04:00
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if giveup || attempts > 10 {
|
|
|
|
return ErrAttemptsExceeded{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 18:40:24 -04:00
|
|
|
pemPrivKey, err = utils.ConvertPrivateKeyToPKCS8(privKey, keyInfo.Role, keyInfo.Gun, chosenPassphrase)
|
2017-04-17 18:08:24 -04:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-08-24 18:40:24 -04:00
|
|
|
s.cachedKeys[keyID] = &cachedKey{role: keyInfo.Role, key: privKey}
|
|
|
|
err = s.store.Set(keyID, pemPrivKey)
|
2017-04-17 18:08:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.keyInfoMap[privKey.ID()] = keyInfo
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetKey returns the PrivateKey given a KeyID
|
2017-08-24 18:40:24 -04:00
|
|
|
func (s *GenericKeyStore) GetKey(keyID string) (data.PrivateKey, data.RoleName, error) {
|
2017-04-17 18:08:24 -04:00
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
2017-08-24 18:40:24 -04:00
|
|
|
|
|
|
|
cachedKeyEntry, ok := s.cachedKeys[keyID]
|
2017-04-17 18:08:24 -04:00
|
|
|
if ok {
|
2017-08-24 18:40:24 -04:00
|
|
|
return cachedKeyEntry.key, cachedKeyEntry.role, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
role, err := getKeyRole(s.store, keyID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
|
2017-08-24 18:40:24 -04:00
|
|
|
keyBytes, err := s.store.Get(keyID)
|
2017-04-17 18:08:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// See if the key is encrypted. If its encrypted we'll fail to parse the private key
|
|
|
|
privKey, err := utils.ParsePEMPrivateKey(keyBytes, "")
|
|
|
|
if err != nil {
|
2017-08-24 18:40:24 -04:00
|
|
|
privKey, _, err = GetPasswdDecryptBytes(s.PassRetriever, keyBytes, keyID, string(role))
|
2017-04-17 18:08:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
}
|
2017-08-24 18:40:24 -04:00
|
|
|
s.cachedKeys[keyID] = &cachedKey{role: role, key: privKey}
|
|
|
|
return privKey, role, nil
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
|
|
|
|
func (s *GenericKeyStore) ListKeys() map[string]KeyInfo {
|
|
|
|
return copyKeyInfoMap(s.keyInfoMap)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveKey removes the key from the keyfilestore
|
|
|
|
func (s *GenericKeyStore) RemoveKey(keyID string) error {
|
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
|
|
|
delete(s.cachedKeys, keyID)
|
|
|
|
|
2017-08-24 18:40:24 -04:00
|
|
|
err := s.store.Remove(keyID)
|
2017-04-17 18:08:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-08-24 18:40:24 -04:00
|
|
|
delete(s.keyInfoMap, keyID)
|
2017-04-17 18:08:24 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name returns a user friendly name for the location this store
|
|
|
|
// keeps its data
|
|
|
|
func (s *GenericKeyStore) Name() string {
|
|
|
|
return s.store.Location()
|
|
|
|
}
|
|
|
|
|
|
|
|
// copyKeyInfoMap returns a deep copy of the passed-in keyInfoMap
|
|
|
|
func copyKeyInfoMap(keyInfoMap map[string]KeyInfo) map[string]KeyInfo {
|
|
|
|
copyMap := make(map[string]KeyInfo)
|
|
|
|
for keyID, keyInfo := range keyInfoMap {
|
|
|
|
copyMap[keyID] = KeyInfo{Role: keyInfo.Role, Gun: keyInfo.Gun}
|
|
|
|
}
|
|
|
|
return copyMap
|
|
|
|
}
|
|
|
|
|
|
|
|
// KeyInfoFromPEM attempts to get a keyID and KeyInfo from the filename and PEM bytes of a key
|
|
|
|
func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
|
2017-08-24 18:40:24 -04:00
|
|
|
var keyID string
|
|
|
|
keyID = filepath.Base(filename)
|
|
|
|
role, gun, err := utils.ExtractPrivateKeyAttributes(pemBytes)
|
|
|
|
if err != nil {
|
|
|
|
return "", KeyInfo{}, err
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
return keyID, KeyInfo{Gun: gun, Role: role}, nil
|
|
|
|
}
|
|
|
|
|
2017-08-24 18:40:24 -04:00
|
|
|
// getKeyRole finds the role for the given keyID. It attempts to look
|
|
|
|
// both in the newer format PEM headers, and also in the legacy filename
|
|
|
|
// format. It returns: the role, and an error
|
|
|
|
func getKeyRole(s Storage, keyID string) (data.RoleName, error) {
|
2017-04-17 18:08:24 -04:00
|
|
|
name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID)))
|
|
|
|
|
|
|
|
for _, file := range s.ListFiles() {
|
|
|
|
filename := filepath.Base(file)
|
|
|
|
if strings.HasPrefix(filename, name) {
|
|
|
|
d, err := s.Get(file)
|
|
|
|
if err != nil {
|
2017-08-24 18:40:24 -04:00
|
|
|
return "", err
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
|
2017-08-24 18:40:24 -04:00
|
|
|
role, _, err := utils.ExtractPrivateKeyAttributes(d)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return role, nil
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
}
|
2017-08-24 18:40:24 -04:00
|
|
|
return "", ErrKeyNotFound{KeyID: keyID}
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetPasswdDecryptBytes gets the password to decrypt the given pem bytes.
|
|
|
|
// Returns the password and private key
|
|
|
|
func GetPasswdDecryptBytes(passphraseRetriever notary.PassRetriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
|
|
|
|
var (
|
|
|
|
passwd string
|
|
|
|
privKey data.PrivateKey
|
|
|
|
)
|
|
|
|
for attempts := 0; ; attempts++ {
|
|
|
|
var (
|
|
|
|
giveup bool
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
if attempts > 10 {
|
|
|
|
return nil, "", ErrAttemptsExceeded{}
|
|
|
|
}
|
|
|
|
passwd, giveup, err = passphraseRetriever(name, alias, false, attempts)
|
|
|
|
// Check if the passphrase retriever got an error or if it is telling us to give up
|
|
|
|
if giveup || err != nil {
|
|
|
|
return nil, "", ErrPasswordInvalid{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to convert PEM encoded bytes back to a PrivateKey using the passphrase
|
|
|
|
privKey, err = utils.ParsePEMPrivateKey(pemBytes, passwd)
|
|
|
|
if err == nil {
|
|
|
|
// We managed to parse the PrivateKey. We've succeeded!
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return privKey, passwd, nil
|
|
|
|
}
|