From 75b63b84cdcf8bd87db4db431865b361c9bf52ff Mon Sep 17 00:00:00 2001 From: Ying Li Date: Tue, 27 Oct 2015 16:19:14 -0700 Subject: [PATCH] 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 --- keystoremanager/import_export.go | 116 +++++++---------- keystoremanager/import_export_test.go | 28 +++-- signer/keydbstore.go | 11 ++ trustmanager/keyfilestore.go | 175 +++++++++++++++++++------- trustmanager/keyfilestore_test.go | 127 ++++++++++++++++++- trustmanager/keystore.go | 2 + 6 files changed, 333 insertions(+), 126 deletions(-) diff --git a/keystoremanager/import_export.go b/keystoremanager/import_export.go index 05624ad1d4..649a15fb52 100644 --- a/keystoremanager/import_export.go +++ b/keystoremanager/import_export.go @@ -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 } diff --git a/keystoremanager/import_export_test.go b/keystoremanager/import_export_test.go index 71a85cce1e..afceec0957 100644 --- a/keystoremanager/import_export_test.go +++ b/keystoremanager/import_export_test.go @@ -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()) diff --git a/signer/keydbstore.go b/signer/keydbstore.go index a7fa5aa1ca..6ec5f4093c 100644 --- a/signer/keydbstore.go +++ b/signer/keydbstore.go @@ -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{} diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index 1ed7fcf324..6bd30ecfa9 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -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) +} diff --git a/trustmanager/keyfilestore_test.go b/trustmanager/keyfilestore_test.go index ff92aef41e..ed1edb4206 100644 --- a/trustmanager/keyfilestore_test.go +++ b/trustmanager/keyfilestore_test.go @@ -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()) +} diff --git a/trustmanager/keystore.go b/trustmanager/keystore.go index ba5fb1a1a4..0eaf526e50 100644 --- a/trustmanager/keystore.go +++ b/trustmanager/keystore.go @@ -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 {