docs/trustmanager/keyfilestore.go

236 lines
6.7 KiB
Go

package trustmanager
import (
"path/filepath"
"strings"
"sync"
"github.com/docker/notary/pkg/passphrase"
"github.com/endophage/gotuf/data"
)
// KeyFileStore persists and manages private keys on disk
type KeyFileStore struct {
sync.Mutex
SimpleFileStore
passphrase.Retriever
cachedKeys map[string]*cachedKey
}
// KeyMemoryStore manages private keys in memory
type KeyMemoryStore struct {
sync.Mutex
MemoryFileStore
passphrase.Retriever
cachedKeys map[string]*cachedKey
}
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
// hold the keys.
func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyFileStore, error) {
fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension)
if err != nil {
return nil, err
}
cachedKeys := make(map[string]*cachedKey)
return &KeyFileStore{SimpleFileStore: *fileStore,
Retriever: passphraseRetriever,
cachedKeys: cachedKeys}, nil
}
// AddKey stores the contents of a PEM-encoded private key as a PEM block
func (s *KeyFileStore) AddKey(name, alias string, privKey data.PrivateKey) error {
s.Lock()
defer s.Unlock()
return addKey(s, s.Retriever, s.cachedKeys, name, alias, privKey)
}
// GetKey returns the PrivateKey given a KeyID
func (s *KeyFileStore) GetKey(name string) (data.PrivateKey, string, error) {
s.Lock()
defer s.Unlock()
return getKey(s, s.Retriever, s.cachedKeys, name)
}
// ListKeys returns a list of unique PublicKeys present on the KeyFileStore.
// There might be symlinks associating Certificate IDs to Public Keys, so this
// method only returns the IDs that aren't symlinks
func (s *KeyFileStore) ListKeys() map[string]string {
return listKeys(s)
}
// RemoveKey removes the key from the keyfilestore
func (s *KeyFileStore) RemoveKey(name string) error {
s.Lock()
defer s.Unlock()
return removeKey(s, s.cachedKeys, name)
}
// NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory
func NewKeyMemoryStore(passphraseRetriever passphrase.Retriever) *KeyMemoryStore {
memStore := NewMemoryFileStore()
cachedKeys := make(map[string]*cachedKey)
return &KeyMemoryStore{MemoryFileStore: *memStore,
Retriever: passphraseRetriever,
cachedKeys: cachedKeys}
}
// AddKey stores the contents of a PEM-encoded private key as a PEM block
func (s *KeyMemoryStore) AddKey(name, alias string, privKey data.PrivateKey) error {
s.Lock()
defer s.Unlock()
return addKey(s, s.Retriever, s.cachedKeys, name, alias, privKey)
}
// GetKey returns the PrivateKey given a KeyID
func (s *KeyMemoryStore) GetKey(name string) (data.PrivateKey, string, error) {
s.Lock()
defer s.Unlock()
return getKey(s, s.Retriever, s.cachedKeys, name)
}
// ListKeys returns a list of unique PublicKeys present on the KeyFileStore.
// There might be symlinks associating Certificate IDs to Public Keys, so this
// method only returns the IDs that aren't symlinks
func (s *KeyMemoryStore) ListKeys() map[string]string {
return listKeys(s)
}
// RemoveKey removes the key from the keystore
func (s *KeyMemoryStore) RemoveKey(name string) error {
s.Lock()
defer s.Unlock()
return removeKey(s, s.cachedKeys, name)
}
func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name, alias string, privKey data.PrivateKey) error {
pemPrivKey, err := KeyToPEM(privKey)
if err != nil {
return err
}
attempts := 0
chosenPassphrase := ""
giveup := false
for {
chosenPassphrase, giveup, err = passphraseRetriever(name, alias, true, attempts)
if err != nil {
attempts++
continue
}
if giveup {
return ErrAttemptsExceeded{}
}
if attempts > 10 {
return ErrAttemptsExceeded{}
}
break
}
if chosenPassphrase != "" {
pemPrivKey, err = EncryptPrivateKey(privKey, chosenPassphrase)
if err != nil {
return err
}
}
cachedKeys[name] = &cachedKey{alias: alias, key: privKey}
return s.Add(name+"_"+alias, pemPrivKey)
}
func getKeyAlias(s LimitedFileStore, keyID string) (string, error) {
files := s.ListFiles(true)
name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID)))
for _, file := range files {
filename := filepath.Base(file)
if strings.HasPrefix(filename, name) {
aliasPlusDotKey := strings.TrimPrefix(filename, name+"_")
retVal := strings.TrimSuffix(aliasPlusDotKey, "."+keyExtension)
return retVal, nil
}
}
return "", &ErrKeyNotFound{KeyID: keyID}
}
// GetKey returns the PrivateKey given a KeyID
func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name string) (data.PrivateKey, string, error) {
cachedKeyEntry, ok := cachedKeys[name]
if ok {
return cachedKeyEntry.key, cachedKeyEntry.alias, nil
}
keyAlias, err := getKeyAlias(s, name)
if err != nil {
return nil, "", err
}
keyBytes, err := s.Get(name + "_" + keyAlias)
if err != nil {
return nil, "", err
}
var retErr error
// See if the key is encrypted. If its encrypted we'll fail to parse the private key
privKey, err := ParsePEMPrivateKey(keyBytes, "")
if err != nil {
// We need to decrypt the key, lets get a passphrase
for attempts := 0; ; attempts++ {
passphrase, giveup, err := passphraseRetriever(name, string(keyAlias), 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{}
}
if attempts > 10 {
return nil, "", ErrAttemptsExceeded{}
}
// Try to convert PEM encoded bytes back to a PrivateKey using the passphrase
privKey, err = ParsePEMPrivateKey(keyBytes, passphrase)
if err != nil {
retErr = ErrPasswordInvalid{}
} else {
// We managed to parse the PrivateKey. We've succeeded!
retErr = nil
break
}
}
}
if retErr != nil {
return nil, "", retErr
}
cachedKeys[name] = &cachedKey{alias: keyAlias, key: privKey}
return privKey, keyAlias, nil
}
// ListKeys returns a map of unique PublicKeys present on the KeyFileStore and
// their corresponding aliases.
// There might be symlinks associating Certificate IDs to Public Keys, so this
// method only returns the IDs that aren't symlinks
func listKeys(s LimitedFileStore) map[string]string {
keyIDMap := make(map[string]string)
for _, f := range s.ListFiles(false) {
keyIDFull := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f)))
keyID := keyIDFull[:strings.LastIndex(keyIDFull, "_")]
keyAlias := keyIDFull[strings.LastIndex(keyIDFull, "_")+1:]
keyIDMap[keyID] = keyAlias
}
return keyIDMap
}
// RemoveKey removes the key from the keyfilestore
func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string) error {
keyAlias, err := getKeyAlias(s, name)
if err != nil {
return err
}
delete(cachedKeys, name)
return s.Remove(name + "_" + keyAlias)
}