mirror of https://github.com/docker/docs.git
Add import/export to KeyStore interface so that the import_export code
makes use of this rather than mangle files manually to import/export root keys. (Regular keys it just zips up the whole directory.) Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
parent
566bd3ce67
commit
75b63b84cd
|
|
@ -11,7 +11,6 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary/pkg/passphrase"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
)
|
||||
|
|
@ -35,19 +34,24 @@ var (
|
|||
// ExportRootKey exports the specified root key to an io.Writer in PEM format.
|
||||
// The key's existing encryption is preserved.
|
||||
func (km *KeyStoreManager) ExportRootKey(dest io.Writer, keyID string) error {
|
||||
pemBytes, err := km.rootKeyStore.Get(keyID + "_root")
|
||||
pemBytes, err := km.KeyStore.ExportKey(keyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dest.Write(pemBytes)
|
||||
return err
|
||||
nBytes, err := dest.Write(pemBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nBytes != len(pemBytes) {
|
||||
return errors.New("Unable to finish writing exported key.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportRootKeyReencrypt exports the specified root key to an io.Writer in
|
||||
// PEM format. The key is reencrypted with a new passphrase.
|
||||
func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
|
||||
privateKey, alias, err := km.rootKeyStore.GetKey(keyID)
|
||||
privateKey, alias, err := km.KeyStore.GetKey(keyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -56,25 +60,29 @@ func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string,
|
|||
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
privRootKeysSubdir := filepath.Join(privDir, rootKeysSubdir)
|
||||
tempRootKeysPath := filepath.Join(tempBaseDir, privRootKeysSubdir)
|
||||
tempRootKeyStore, err := trustmanager.NewKeyFileStore(tempRootKeysPath, newPassphraseRetriever)
|
||||
tempKeysPath := filepath.Join(tempBaseDir, privDir)
|
||||
tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, newPassphraseRetriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tempRootKeyStore.AddKey(keyID, alias, privateKey)
|
||||
err = tempKeyStore.AddKey(keyID, alias, privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pemBytes, err := tempRootKeyStore.Get(keyID + "_" + alias)
|
||||
pemBytes, err := tempKeyStore.ExportKey(keyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dest.Write(pemBytes)
|
||||
return err
|
||||
nBytes, err := dest.Write(pemBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nBytes != len(pemBytes) {
|
||||
return errors.New("Unable to finish writing exported key.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkRootKeyIsEncrypted makes sure the root key is encrypted. We have
|
||||
|
|
@ -106,7 +114,7 @@ func (km *KeyStoreManager) ImportRootKey(source io.Reader, keyID string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err = km.rootKeyStore.Add(keyID+"_root", pemBytes); err != nil {
|
||||
if err = km.KeyStore.ImportKey(pemBytes, "root"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -170,35 +178,20 @@ func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, newPassphraseRetriever
|
|||
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
privNonRootKeysSubdir := filepath.Join(privDir, nonRootKeysSubdir)
|
||||
privRootKeysSubdir := filepath.Join(privDir, rootKeysSubdir)
|
||||
|
||||
// Create temporary keystores to use as a staging area
|
||||
tempNonRootKeysPath := filepath.Join(tempBaseDir, privNonRootKeysSubdir)
|
||||
tempNonRootKeyStore, err := trustmanager.NewKeyFileStore(tempNonRootKeysPath, newPassphraseRetriever)
|
||||
// Create temporary keystore to use as a staging area
|
||||
tempKeysPath := filepath.Join(tempBaseDir, privDir)
|
||||
tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, newPassphraseRetriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tempRootKeysPath := filepath.Join(tempBaseDir, privRootKeysSubdir)
|
||||
tempRootKeyStore, err := trustmanager.NewKeyFileStore(tempRootKeysPath, newPassphraseRetriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := moveKeys(km.rootKeyStore, tempRootKeyStore); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := moveKeys(km.nonRootKeyStore, tempNonRootKeyStore); err != nil {
|
||||
if err := moveKeys(km.KeyStore, tempKeyStore); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zipWriter := zip.NewWriter(dest)
|
||||
|
||||
if err := addKeysToArchive(zipWriter, tempRootKeyStore, privRootKeysSubdir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addKeysToArchive(zipWriter, tempNonRootKeyStore, privNonRootKeysSubdir); err != nil {
|
||||
if err := addKeysToArchive(zipWriter, tempKeyStore, privDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -214,13 +207,7 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader) error {
|
|||
// Temporarily store the keys in maps, so we can bail early if there's
|
||||
// an error (for example, wrong passphrase), without leaving the key
|
||||
// store in an inconsistent state
|
||||
newRootKeys := make(map[string][]byte)
|
||||
newNonRootKeys := make(map[string][]byte)
|
||||
|
||||
// Note that using / as a separator is okay here - the zip package
|
||||
// guarantees that the separator will be /
|
||||
rootKeysPrefix := privDir + "/" + rootKeysSubdir + "/"
|
||||
nonRootKeysPrefix := privDir + "/" + nonRootKeysSubdir + "/"
|
||||
newKeys := make(map[string][]byte)
|
||||
|
||||
// Iterate through the files in the archive. Don't add the keys
|
||||
for _, f := range zipReader.File {
|
||||
|
|
@ -236,29 +223,22 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Is this in the root_keys directory?
|
||||
// Note that using / as a separator is okay here - the zip
|
||||
// package guarantees that the separator will be /
|
||||
if strings.HasPrefix(fNameTrimmed, rootKeysPrefix) {
|
||||
if err = checkRootKeyIsEncrypted(fileBytes); err != nil {
|
||||
rc.Close()
|
||||
return err
|
||||
if strings.HasPrefix(fNameTrimmed, privDir) {
|
||||
if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" {
|
||||
if err = checkRootKeyIsEncrypted(fileBytes); err != nil {
|
||||
rc.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Root keys are preserved without decrypting
|
||||
keyName := strings.TrimPrefix(fNameTrimmed, rootKeysPrefix)
|
||||
newRootKeys[keyName] = fileBytes
|
||||
|
||||
} else if strings.HasPrefix(fNameTrimmed, nonRootKeysPrefix) {
|
||||
// Nonroot keys are preserved without decrypting
|
||||
keyName := strings.TrimPrefix(fNameTrimmed, nonRootKeysPrefix)
|
||||
newNonRootKeys[keyName] = fileBytes
|
||||
|
||||
keyName := strings.TrimPrefix(fNameTrimmed, privDir)
|
||||
newKeys[keyName] = fileBytes
|
||||
} else {
|
||||
// This path inside the zip archive doesn't look like a
|
||||
// root key, non-root key, or alias. To avoid adding a file
|
||||
// to the filestore that we won't be able to use, skip
|
||||
// this file in the import.
|
||||
logrus.Warnf("skipping import of key with a path that doesn't begin with %s or %s: %s", rootKeysPrefix, nonRootKeysPrefix, f.Name)
|
||||
rc.Close()
|
||||
continue
|
||||
}
|
||||
|
|
@ -266,14 +246,8 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader) error {
|
|||
rc.Close()
|
||||
}
|
||||
|
||||
for keyName, pemBytes := range newRootKeys {
|
||||
if err := km.rootKeyStore.Add(keyName, pemBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for keyName, pemBytes := range newNonRootKeys {
|
||||
if err := km.nonRootKeyStore.Add(keyName, pemBytes); err != nil {
|
||||
for keyName, pemBytes := range newKeys {
|
||||
if err := km.KeyStore.Add(keyName, pemBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -309,26 +283,24 @@ func (km *KeyStoreManager) ExportKeysByGUN(dest io.Writer, gun string, passphras
|
|||
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
privNonRootKeysSubdir := filepath.Join(privDir, nonRootKeysSubdir)
|
||||
|
||||
// Create temporary keystore to use as a staging area
|
||||
tempNonRootKeysPath := filepath.Join(tempBaseDir, privNonRootKeysSubdir)
|
||||
tempNonRootKeyStore, err := trustmanager.NewKeyFileStore(tempNonRootKeysPath, passphraseRetriever)
|
||||
tempKeysPath := filepath.Join(tempBaseDir, privDir)
|
||||
tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, passphraseRetriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := moveKeysByGUN(km.nonRootKeyStore, tempNonRootKeyStore, gun); err != nil {
|
||||
if err := moveKeysByGUN(km.KeyStore, tempKeyStore, gun); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zipWriter := zip.NewWriter(dest)
|
||||
|
||||
if len(tempNonRootKeyStore.ListKeys()) == 0 {
|
||||
if len(tempKeyStore.ListKeys()) == 0 {
|
||||
return ErrNoKeysFoundForGUN
|
||||
}
|
||||
|
||||
if err := addKeysToArchive(zipWriter, tempNonRootKeyStore, privNonRootKeysSubdir); err != nil {
|
||||
if err := addKeysToArchive(zipWriter, tempKeyStore, privDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,11 +83,14 @@ func TestImportExportZip(t *testing.T) {
|
|||
|
||||
// Add non-root keys to the map. These should use the new passphrase
|
||||
// because the passwords were chosen by the newPassphraseRetriever.
|
||||
privKeyMap := repo.KeyStoreManager.NonRootKeyStore().ListKeys()
|
||||
privKeyMap := repo.KeyStoreManager.KeyStore.ListKeys()
|
||||
for privKeyName := range privKeyMap {
|
||||
_, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(privKeyName)
|
||||
_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName)
|
||||
assert.NoError(t, err, "privKey %s has no alias", privKeyName)
|
||||
|
||||
if alias == "root" {
|
||||
continue
|
||||
}
|
||||
relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key")
|
||||
passphraseByFile[relKeyPath] = exportPassphrase
|
||||
}
|
||||
|
|
@ -156,9 +159,12 @@ func TestImportExportZip(t *testing.T) {
|
|||
// Look for keys in private. The filenames should match the key IDs
|
||||
// in the repo's private key store.
|
||||
for privKeyName := range privKeyMap {
|
||||
_, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(privKeyName)
|
||||
_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName)
|
||||
assert.NoError(t, err, "privKey %s has no alias", privKeyName)
|
||||
|
||||
if alias == "root" {
|
||||
continue
|
||||
}
|
||||
relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key")
|
||||
privKeyFileName := filepath.Join(tempBaseDir2, relKeyPath)
|
||||
_, err = os.Stat(privKeyFileName)
|
||||
|
|
@ -219,12 +225,15 @@ func TestImportExportGUN(t *testing.T) {
|
|||
|
||||
// Add keys non-root keys to the map. These should use the new passphrase
|
||||
// because they were formerly unencrypted.
|
||||
privKeyMap := repo.KeyStoreManager.NonRootKeyStore().ListKeys()
|
||||
privKeyMap := repo.KeyStoreManager.KeyStore.ListKeys()
|
||||
for privKeyName := range privKeyMap {
|
||||
_, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(privKeyName)
|
||||
_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName)
|
||||
if err != nil {
|
||||
t.Fatalf("privKey %s has no alias", privKeyName)
|
||||
}
|
||||
if alias == "root" {
|
||||
continue
|
||||
}
|
||||
relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key")
|
||||
|
||||
passphraseByFile[relKeyPath] = exportPassphrase
|
||||
|
|
@ -290,10 +299,13 @@ func TestImportExportGUN(t *testing.T) {
|
|||
// Look for keys in private. The filenames should match the key IDs
|
||||
// in the repo's private key store.
|
||||
for privKeyName := range privKeyMap {
|
||||
_, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(privKeyName)
|
||||
_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName)
|
||||
if err != nil {
|
||||
t.Fatalf("privKey %s has no alias", privKeyName)
|
||||
}
|
||||
if alias == "root" {
|
||||
continue
|
||||
}
|
||||
relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key")
|
||||
privKeyFileName := filepath.Join(tempBaseDir2, relKeyPath)
|
||||
_, err = os.Stat(privKeyFileName)
|
||||
|
|
@ -381,7 +393,7 @@ func TestImportExportRootKey(t *testing.T) {
|
|||
assert.EqualError(t, err, keystoremanager.ErrNoValidPrivateKey.Error())
|
||||
|
||||
// Should be able to unlock the root key with the old password
|
||||
key, alias, err := repo2.KeyStoreManager.RootKeyStore().GetKey(rootKeyID)
|
||||
key, alias, err := repo2.KeyStoreManager.KeyStore.GetKey(rootKeyID)
|
||||
assert.NoError(t, err, "could not unlock root key")
|
||||
assert.Equal(t, "root", alias)
|
||||
assert.Equal(t, rootKeyID, key.ID())
|
||||
|
|
@ -452,7 +464,7 @@ func TestImportExportRootKeyReencrypt(t *testing.T) {
|
|||
assert.NoError(t, err, "missing root key")
|
||||
|
||||
// Should be able to unlock the root key with the new password
|
||||
key, alias, err := repo2.KeyStoreManager.RootKeyStore().GetKey(rootKeyID)
|
||||
key, alias, err := repo2.KeyStoreManager.KeyStore.GetKey(rootKeyID)
|
||||
assert.NoError(t, err, "could not unlock root key")
|
||||
assert.Equal(t, "root", alias)
|
||||
assert.Equal(t, rootKeyID, key.ID())
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package signer
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
|
|
@ -196,6 +197,16 @@ func (s *KeyDBStore) RotateKeyPassphrase(name, newPassphraseAlias string) error
|
|||
return nil
|
||||
}
|
||||
|
||||
// ExportKey is currently unimplemented and will always return an error
|
||||
func (s *KeyDBStore) ExportKey(name string) ([]byte, error) {
|
||||
return nil, errors.New("Exporting from a KeyDBStore is not supported.")
|
||||
}
|
||||
|
||||
// ImportKey is currently unimplemented and will always return an error
|
||||
func (s *KeyDBStore) ImportKey(pemBytes []byte, alias string) error {
|
||||
return errors.New("Importing into a KeyDBStore is not supported")
|
||||
}
|
||||
|
||||
// HealthCheck verifies that DB exists and is query-able
|
||||
func (s *KeyDBStore) HealthCheck() error {
|
||||
dbPrivateKey := GormPrivateKey{}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,22 @@ func (s *KeyFileStore) RemoveKey(name string) error {
|
|||
return removeKey(s, s.cachedKeys, name)
|
||||
}
|
||||
|
||||
// ExportKey exportes the encrypted bytes from the keystore and writes it to
|
||||
// dest.
|
||||
func (s *KeyFileStore) ExportKey(name string) ([]byte, error) {
|
||||
keyBytes, _, err := getRawKey(s, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keyBytes, nil
|
||||
}
|
||||
|
||||
// ImportKey imports the private key in the encrypted bytes into the keystore
|
||||
// with the given key ID and alias.
|
||||
func (s *KeyFileStore) ImportKey(pemBytes []byte, alias string) error {
|
||||
return importKey(s, s.Retriever, s.cachedKeys, alias, pemBytes)
|
||||
}
|
||||
|
||||
// NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory
|
||||
func NewKeyMemoryStore(passphraseRetriever passphrase.Retriever) *KeyMemoryStore {
|
||||
memStore := NewMemoryFileStore()
|
||||
|
|
@ -106,19 +122,33 @@ func (s *KeyMemoryStore) RemoveKey(name string) error {
|
|||
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)
|
||||
// ExportKey exportes the encrypted bytes from the keystore and writes it to
|
||||
// dest.
|
||||
func (s *KeyMemoryStore) ExportKey(name string) ([]byte, error) {
|
||||
keyBytes, _, err := getRawKey(s, name)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
return keyBytes, nil
|
||||
}
|
||||
|
||||
attempts := 0
|
||||
chosenPassphrase := ""
|
||||
giveup := false
|
||||
for {
|
||||
// ImportKey imports the private key in the encrypted bytes into the keystore
|
||||
// with the given key ID and alias.
|
||||
func (s *KeyMemoryStore) ImportKey(pemBytes []byte, alias string) error {
|
||||
return importKey(s, s.Retriever, s.cachedKeys, alias, pemBytes)
|
||||
}
|
||||
|
||||
func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name, alias string, privKey data.PrivateKey) error {
|
||||
|
||||
var (
|
||||
chosenPassphrase string
|
||||
giveup bool
|
||||
err error
|
||||
)
|
||||
|
||||
for attempts := 0; ; attempts++ {
|
||||
chosenPassphrase, giveup, err = passphraseRetriever(name, alias, true, attempts)
|
||||
if err != nil {
|
||||
attempts++
|
||||
continue
|
||||
}
|
||||
if giveup {
|
||||
|
|
@ -130,15 +160,7 @@ func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached
|
|||
break
|
||||
}
|
||||
|
||||
if chosenPassphrase != "" {
|
||||
pemPrivKey, err = EncryptPrivateKey(privKey, chosenPassphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cachedKeys[name] = &cachedKey{alias: alias, key: privKey}
|
||||
return s.Add(filepath.Join(getSubdir(alias), name+"_"+alias), pemPrivKey)
|
||||
return encryptAndAddKey(s, chosenPassphrase, cachedKeys, name, alias, privKey)
|
||||
}
|
||||
|
||||
func getKeyAlias(s LimitedFileStore, keyID string) (string, error) {
|
||||
|
|
@ -165,14 +187,8 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached
|
|||
if ok {
|
||||
return cachedKeyEntry.key, cachedKeyEntry.alias, nil
|
||||
}
|
||||
keyAlias, err := getKeyAlias(s, name)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
filename := name + "_" + keyAlias
|
||||
var keyBytes []byte
|
||||
keyBytes, err = s.Get(filepath.Join(getSubdir(keyAlias), filename))
|
||||
keyBytes, keyAlias, err := getRawKey(s, name)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
|
@ -181,27 +197,7 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached
|
|||
// 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
|
||||
}
|
||||
}
|
||||
privKey, _, retErr = getPasswdDecryptBytes(s, passphraseRetriever, keyBytes, name, string(keyAlias))
|
||||
}
|
||||
if retErr != nil {
|
||||
return nil, "", retErr
|
||||
|
|
@ -247,9 +243,98 @@ func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string
|
|||
return nil
|
||||
}
|
||||
|
||||
// Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys
|
||||
func getSubdir(alias string) string {
|
||||
if alias == "root" {
|
||||
return rootKeysSubdir
|
||||
}
|
||||
return nonRootKeysSubdir
|
||||
}
|
||||
|
||||
// Given a key ID, gets the bytes and alias belonging to that key if the key
|
||||
// exists
|
||||
func getRawKey(s LimitedFileStore, name string) ([]byte, string, error) {
|
||||
keyAlias, err := getKeyAlias(s, name)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
filename := name + "_" + keyAlias
|
||||
var keyBytes []byte
|
||||
keyBytes, err = s.Get(filepath.Join(getSubdir(keyAlias), filename))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return keyBytes, keyAlias, nil
|
||||
}
|
||||
|
||||
// Get the password to decript the given pem bytes. Return the password,
|
||||
// because it is useful for importing
|
||||
func getPasswdDecryptBytes(s LimitedFileStore, passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
|
||||
var (
|
||||
passwd string
|
||||
retErr error
|
||||
privKey data.PrivateKey
|
||||
)
|
||||
for attempts := 0; ; attempts++ {
|
||||
var (
|
||||
giveup bool
|
||||
err error
|
||||
)
|
||||
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{}
|
||||
}
|
||||
if attempts > 10 {
|
||||
return nil, "", ErrAttemptsExceeded{}
|
||||
}
|
||||
|
||||
// Try to convert PEM encoded bytes back to a PrivateKey using the passphrase
|
||||
privKey, err = ParsePEMPrivateKey(pemBytes, passwd)
|
||||
if err != nil {
|
||||
retErr = ErrPasswordInvalid{}
|
||||
} else {
|
||||
// We managed to parse the PrivateKey. We've succeeded!
|
||||
retErr = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
if retErr != nil {
|
||||
return nil, "", retErr
|
||||
}
|
||||
return privKey, passwd, nil
|
||||
}
|
||||
|
||||
func encryptAndAddKey(s LimitedFileStore, passwd string, cachedKeys map[string]*cachedKey, name, alias string, privKey data.PrivateKey) error {
|
||||
|
||||
var (
|
||||
pemPrivKey []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if passwd != "" {
|
||||
pemPrivKey, err = EncryptPrivateKey(privKey, passwd)
|
||||
} else {
|
||||
pemPrivKey, err = KeyToPEM(privKey)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cachedKeys[name] = &cachedKey{alias: alias, key: privKey}
|
||||
return s.Add(filepath.Join(getSubdir(alias), name+"_"+alias), pemPrivKey)
|
||||
}
|
||||
|
||||
func importKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, alias string, pemBytes []byte) error {
|
||||
|
||||
privKey, passphrase, err := getPasswdDecryptBytes(s, passphraseRetriever, pemBytes, "imported", alias)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return encryptAndAddKey(
|
||||
s, passphrase, cachedKeys, privKey.ID(), alias, privKey)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,15 +9,18 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/docker/notary/pkg/passphrase"
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const cannedPassphrase = "passphrase"
|
||||
|
||||
var passphraseRetriever = func(keyID string, alias string, createNew bool, numAttempts int) (string, bool, error) {
|
||||
if numAttempts > 5 {
|
||||
giveup := true
|
||||
return "", giveup, errors.New("passPhraseRetriever failed after too many requests")
|
||||
}
|
||||
return "passphrase", false, nil
|
||||
return cannedPassphrase, false, nil
|
||||
}
|
||||
|
||||
func TestAddKey(t *testing.T) {
|
||||
|
|
@ -345,3 +348,125 @@ func TestKeysAreCached(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, 2, numTimesCalled, "numTimesCalled should be 2 -- no additional call to passphraseRetriever")
|
||||
}
|
||||
|
||||
// Exporting a key is successful (it is a valid key)
|
||||
func TestKeyFileStoreExportSuccess(t *testing.T) {
|
||||
// Generate a new Private Key
|
||||
privKey, err := GenerateECDSAKey(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
// Create our FileStore and add the key
|
||||
store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever)
|
||||
assert.NoError(t, err)
|
||||
err = store.AddKey(privKey.ID(), "root", privKey)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assertExportKeySuccess(t, store, privKey)
|
||||
}
|
||||
|
||||
// Exporting a key that doesn't exist fails (it is a valid key)
|
||||
func TestKeyFileStoreExportNonExistantFailure(t *testing.T) {
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
// Create empty FileStore
|
||||
store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = store.ExportKey("12345")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// Exporting a key is successful (it is a valid key)
|
||||
func TestKeyMemoryStoreExportSuccess(t *testing.T) {
|
||||
// Generate a new Private Key
|
||||
privKey, err := GenerateECDSAKey(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create our MemoryStore and add key to it
|
||||
store := NewKeyMemoryStore(passphraseRetriever)
|
||||
assert.NoError(t, err)
|
||||
err = store.AddKey(privKey.ID(), "root", privKey)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assertExportKeySuccess(t, store, privKey)
|
||||
}
|
||||
|
||||
// Exporting a key that doesn't exist fails (it is a valid key)
|
||||
func TestKeyMemoryStoreExportNonExistantFailure(t *testing.T) {
|
||||
store := NewKeyMemoryStore(passphraseRetriever)
|
||||
_, err := store.ExportKey("12345")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// Importing a key is successful
|
||||
func TestKeyFileStoreImportSuccess(t *testing.T) {
|
||||
// Generate a new Private Key
|
||||
privKey, err := GenerateECDSAKey(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
// Create our FileStore
|
||||
store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assertImportKeySuccess(t, store, privKey)
|
||||
}
|
||||
|
||||
// Importing a key is successful
|
||||
func TestKeyMemoryStoreImportSuccess(t *testing.T) {
|
||||
// Generate a new Private Key
|
||||
privKey, err := GenerateECDSAKey(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create our MemoryStore
|
||||
store := NewKeyMemoryStore(passphraseRetriever)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assertImportKeySuccess(t, store, privKey)
|
||||
}
|
||||
|
||||
// Given a keystore and expected key that is in the store, export the key
|
||||
// and assert that the exported key is the same and encrypted with the right
|
||||
// password.
|
||||
func assertExportKeySuccess(
|
||||
t *testing.T, s KeyStore, expectedKey data.PrivateKey) {
|
||||
|
||||
pemBytes, err := s.ExportKey(expectedKey.ID())
|
||||
assert.NoError(t, err)
|
||||
|
||||
reparsedKey, err := ParsePEMPrivateKey(pemBytes, cannedPassphrase)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedKey.Private(), reparsedKey.Private())
|
||||
assert.Equal(t, expectedKey.Public(), reparsedKey.Public())
|
||||
}
|
||||
|
||||
// Given a keystore and expected key, generate an encrypted PEM of the key
|
||||
// and assert that the then imported key is the same and encrypted with the
|
||||
// right password.
|
||||
func assertImportKeySuccess(
|
||||
t *testing.T, s KeyStore, expectedKey data.PrivateKey) {
|
||||
|
||||
pemBytes, err := EncryptPrivateKey(expectedKey, cannedPassphrase)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = s.ImportKey(pemBytes, "root")
|
||||
assert.NoError(t, err)
|
||||
|
||||
reimportedKey, reimportedAlias, err := s.GetKey(expectedKey.ID())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "root", reimportedAlias)
|
||||
assert.Equal(t, expectedKey.Private(), reimportedKey.Private())
|
||||
assert.Equal(t, expectedKey.Public(), reimportedKey.Public())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ type KeyStore interface {
|
|||
GetKey(name string) (data.PrivateKey, string, error)
|
||||
ListKeys() map[string]string
|
||||
RemoveKey(name string) error
|
||||
ExportKey(name string) ([]byte, error)
|
||||
ImportKey(pemBytes []byte, alias string) error
|
||||
}
|
||||
|
||||
type cachedKey struct {
|
||||
|
|
|
|||
Loading…
Reference in New Issue