Repository: docker/notary
Pull request #536 (2016-02-03 01:18:01)

Non-root key import and export CLI
Allows for importing and exporting non-root keys via notary CLI.  This also works for importing delegation keys:

`notary key import <file> --role <ROLE> --gun <GUN>`

The default role is root, so that the behavior of `notary key import <file>` remains unchanged.

export is the same but now also can export non-root keys by ID:

`notary key export <keyID> <file>`

I also made "private", "tuf_keys", and "root_keys" constants in `const.go`

Partially addresses #483 (`passwd` can be a separate PR, along with changes to passphrase retriever)

diogomonica: Behavior that I think we should support:

## If a PEM key gets imported and has a role inside of it: 
- if it's root, import it to `root_keys`
- if it's delegation, import to `tuf_keys`
- if it's targets or snapshots and `--gun` is set, import it to the right directory
- if it's targets or snapshots and `--gun` is not set, fail

## if a PEM key gets imported and has no role inside of it:
- if `--role` is not set, fail
- if `--role` is set to a delegation, import it to `tuf_keys` and rewrite the PEM to include the role
- if `--role` is set to a target or snapshot, and `--gun` is set, import it to the right directory and rewrite the PEM to include the role
- if `--role` is set to a target or snapshot, and `--gun` is not set, fail
cyli: @diogomonica :+1: on behavior.  Maybe nice error messages on `--role` not being set like "could not infer role, and no role was specified" and "specified role does not match role in PEM headers" or some such too.  It might make it easier to just have a simpler explanation in the description.
riyazdf: @cyli those are great suggestions for error messages, I'll work on including those along with @diogomonica's behavior ideas :+1: 
GordonTheTurtle: Please sign your commits following these rules:
https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work
The easiest way to do this is to amend the last commit:
~~~console
$ git clone -b "nonroot-import" git@github.com:docker/notary.git somewhere
$ cd somewhere
$ git rebase -i HEAD~7
editor opens
change each 'pick' to 'edit'
save the file and quit
$ git commit --amend -s --no-edit
$ git rebase --continue # and repeat the amend for each commit
$ git push -f
~~~

Ammending updates the existing PR. You **DO NOT** need to open a new one.

riyazdf: Removing WIP: added the aforementioned `--role` and `--gun` checks with the suggested error messages, and tests for importing and exporting
diogomonica: @riyazdf do you wan to merge this and then work on the refactor?
riyazdf: @diogomonica I'm fine with merging this before the refactor
diogomonica: Rebase needed.

Patch:
diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go
index 353ec750..0e991fb2 100644
--- a/cmd/notary/keys.go
+++ b/cmd/notary/keys.go
@@ -20,6 +20,7 @@ import (
 	"github.com/docker/notary/tuf/data"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
+	"io/ioutil"
 )
 
 var cmdKeyTemplate = usageTemplate{
@@ -52,10 +53,10 @@ var cmdKeysBackupTemplate = usageTemplate{
 	Long:  "Backs up all of your accessible of keys. The keys are reencrypted with a new passphrase. The output is a ZIP file.  If the --gun option is passed, only signing keys and no root keys will be backed up.  Does not work on keys that are only in hardware (e.g. Yubikeys).",
 }
 
-var cmdKeyExportRootTemplate = usageTemplate{
+var cmdKeyExportTemplate = usageTemplate{
 	Use:   "export [ keyID ] [ pemfilename ]",
-	Short: "Export a root key on disk to a PEM file.",
-	Long:  "Exports a single root key on disk, without reencrypting. The output is a PEM file. Does not work on keys that are only in hardware (e.g. Yubikeys).",
+	Short: "Export a private key on disk to a PEM file.",
+	Long:  "Exports a single private key on disk, without reencrypting. The output is a PEM file. Does not work on keys that are only in hardware (e.g. Yubikeys).",
 }
 
 var cmdKeysRestoreTemplate = usageTemplate{
@@ -64,10 +65,10 @@ var cmdKeysRestoreTemplate = usageTemplate{
 	Long:  "Restores one or more keys from a ZIP file. If hardware key storage (e.g. a Yubikey) is available, root keys will be imported into the hardware, but not backed up to disk in the same location as the other, non-root keys.",
 }
 
-var cmdKeyImportRootTemplate = usageTemplate{
+var cmdKeyImportTemplate = usageTemplate{
 	Use:   "import [ pemfilename ]",
-	Short: "Imports a root key from a PEM file.",
-	Long:  "Imports a single root key from a PEM file. If a hardware key storage (e.g. Yubikey) is available, the root key will be imported into the hardware but not backed up on disk again.",
+	Short: "Imports a key from a PEM file.",
+	Long:  "Imports a single key from a PEM file. If a hardware key storage (e.g. Yubikey) is available, the root key will be imported into the hardware but not backed up on disk again.",
 }
 
 var cmdKeyRemoveTemplate = usageTemplate{
@@ -88,10 +89,12 @@ type keyCommander struct {
 	getRetriever func() passphrase.Retriever
 
 	// these are for command line parsing - no need to set
-	keysExportRootChangePassphrase bool
-	keysExportGUN                  string
-	rotateKeyRole                  string
-	rotateKeyServerManaged         bool
+	keysExportChangePassphrase bool
+	keysExportGUN              string
+	keysImportGUN              string
+	keysImportRole             string
+	rotateKeyRole              string
+	rotateKeyServerManaged     bool
 }
 
 func (k *keyCommander) GetCommand() *cobra.Command {
@@ -99,7 +102,13 @@ func (k *keyCommander) GetCommand() *cobra.Command {
 	cmd.AddCommand(cmdKeyListTemplate.ToCommand(k.keysList))
 	cmd.AddCommand(cmdKeyGenerateRootKeyTemplate.ToCommand(k.keysGenerateRootKey))
 	cmd.AddCommand(cmdKeysRestoreTemplate.ToCommand(k.keysRestore))
-	cmd.AddCommand(cmdKeyImportRootTemplate.ToCommand(k.keysImportRoot))
+	cmdKeysImport := cmdKeyImportTemplate.ToCommand(k.keysImport)
+	cmdKeysImport.Flags().StringVarP(
+		&k.keysImportGUN, "gun", "g", "", "Globally Unique Name to import key to")
+	cmdKeysImport.Flags().StringVarP(
+		&k.keysImportRole, "role", "r", "", "Role to import key to (if not in PEM headers)")
+	cmd.AddCommand(cmdKeysImport)
+
 	cmd.AddCommand(cmdKeyRemoveTemplate.ToCommand(k.keyRemove))
 	cmd.AddCommand(cmdKeyPasswdTemplate.ToCommand(k.keyPassphraseChange))
 
@@ -108,11 +117,11 @@ func (k *keyCommander) GetCommand() *cobra.Command {
 		&k.keysExportGUN, "gun", "g", "", "Globally Unique Name to export keys for")
 	cmd.AddCommand(cmdKeysBackup)
 
-	cmdKeyExportRoot := cmdKeyExportRootTemplate.ToCommand(k.keysExportRoot)
-	cmdKeyExportRoot.Flags().BoolVarP(
-		&k.keysExportRootChangePassphrase, "change-passphrase", "p", false,
+	cmdKeyExport := cmdKeyExportTemplate.ToCommand(k.keysExport)
+	cmdKeyExport.Flags().BoolVarP(
+		&k.keysExportChangePassphrase, "change-passphrase", "p", false,
 		"Set a new passphrase for the key being exported")
-	cmd.AddCommand(cmdKeyExportRoot)
+	cmd.AddCommand(cmdKeyExport)
 
 	cmdRotateKey := cmdRotateKeyTemplate.ToCommand(k.keysRotate)
 	cmdRotateKey.Flags().BoolVarP(&k.rotateKeyServerManaged, "server-managed", "r",
@@ -236,8 +245,8 @@ func (k *keyCommander) keysBackup(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
-// keysExportRoot exports a root key by ID to a PEM file
-func (k *keyCommander) keysExportRoot(cmd *cobra.Command, args []string) error {
+// keysExport exports a key by ID to a PEM file
+func (k *keyCommander) keysExport(cmd *cobra.Command, args []string) error {
 	if len(args) < 2 {
 		cmd.Usage()
 		return fmt.Errorf("Must specify key ID and output filename for export")
@@ -247,7 +256,7 @@ func (k *keyCommander) keysExportRoot(cmd *cobra.Command, args []string) error {
 	exportFilename := args[1]
 
 	if len(keyID) != notary.Sha256HexSize {
-		return fmt.Errorf("Please specify a valid root key ID")
+		return fmt.Errorf("Please specify a valid key ID")
 	}
 
 	config, err := k.configGetter()
@@ -258,24 +267,44 @@ func (k *keyCommander) keysExportRoot(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return err
 	}
-	cs := cryptoservice.NewCryptoService("", ks...)
+
+	// Search for this key in all of our keystores, determine whether this key has a GUN
+	keyGun := ""
+	keyRole := ""
+	for _, store := range ks {
+		for keypath, role := range store.ListKeys() {
+			if filepath.Base(keypath) == keyID {
+				keyRole = role
+				if role == data.CanonicalRootRole {
+					continue
+				}
+				dirPath := filepath.Dir(keypath)
+				if dirPath != "." { // no gun
+					keyGun = dirPath
+				}
+				break
+			}
+		}
+	}
+
+	cs := cryptoservice.NewCryptoService(keyGun, ks...)
 
 	exportFile, err := os.Create(exportFilename)
 	if err != nil {
 		return fmt.Errorf("Error creating output file: %v", err)
 	}
-	if k.keysExportRootChangePassphrase {
+	if k.keysExportChangePassphrase {
 		// Must use a different passphrase retriever to avoid caching the
 		// unlocking passphrase and reusing that.
 		exportRetriever := k.getRetriever()
-		err = cs.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever)
+		err = cs.ExportKeyReencrypt(exportFile, keyID, exportRetriever)
 	} else {
-		err = cs.ExportRootKey(exportFile, keyID)
+		err = cs.ExportKey(exportFile, keyID, keyRole)
 	}
 	exportFile.Close()
 	if err != nil {
 		os.Remove(exportFilename)
-		return fmt.Errorf("Error exporting root key: %v", err)
+		return fmt.Errorf("Error exporting %s key: %v", keyRole, err)
 	}
 	return nil
 }
@@ -313,8 +342,8 @@ func (k *keyCommander) keysRestore(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
-// keysImportRoot imports a root key from a PEM file
-func (k *keyCommander) keysImportRoot(cmd *cobra.Command, args []string) error {
+// keysImport imports a private key from a PEM file for a role
+func (k *keyCommander) keysImport(cmd *cobra.Command, args []string) error {
 	if len(args) != 1 {
 		cmd.Usage()
 		return fmt.Errorf("Must specify input filename for import")
@@ -328,7 +357,6 @@ func (k *keyCommander) keysImportRoot(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return err
 	}
-	cs := cryptoservice.NewCryptoService("", ks...)
 
 	importFilename := args[0]
 
@@ -338,7 +366,38 @@ func (k *keyCommander) keysImportRoot(cmd *cobra.Command, args []string) error {
 	}
 	defer importFile.Close()
 
-	err = cs.ImportRootKey(importFile)
+	pemBytes, err := ioutil.ReadAll(importFile)
+	if err != nil {
+		return fmt.Errorf("Error reading input file: %v", err)
+	}
+
+	pemRole := trustmanager.ReadRoleFromPEM(pemBytes)
+
+	// If the PEM key doesn't have a role in it, we must have --role set
+	if pemRole == "" && k.keysImportRole == "" {
+		return fmt.Errorf("Could not infer role, and no role was specified for key")
+	}
+
+	// If both  PEM role and a --role are provided and they don't match, error
+	if pemRole != "" && k.keysImportRole != "" && pemRole != k.keysImportRole {
+		return fmt.Errorf("Specified role %s does not match role %s in PEM headers", k.keysImportRole, pemRole)
+	}
+
+	// Determine which role to add to between PEM headers and --role flag:
+	var importRole string
+	if k.keysImportRole != "" {
+		importRole = k.keysImportRole
+	} else {
+		importRole = pemRole
+	}
+
+	// If we're importing to targets or snapshot, we need a GUN
+	if (importRole == data.CanonicalTargetsRole || importRole == data.CanonicalSnapshotRole) && k.keysImportGUN == "" {
+		return fmt.Errorf("Must specify GUN for %s key", importRole)
+	}
+
+	cs := cryptoservice.NewCryptoService(k.keysImportGUN, ks...)
+	err = cs.ImportRoleKey(pemBytes, importRole, k.getRetriever())
 
 	if err != nil {
 		return fmt.Errorf("Error importing root key: %v", err)
diff --git a/const.go b/const.go
index 3841bc8c..bb1d01c8 100644
--- a/const.go
+++ b/const.go
@@ -18,4 +18,10 @@ const (
 	Sha256HexSize = 64
 	// TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored
 	TrustedCertsDir = "trusted_certificates"
+	// PrivDir is the directory, under the notary repo base directory, where private keys are stored
+	PrivDir = "private"
+	// RootKeysSubdir is the subdirectory under PrivDir where root private keys are stored
+	RootKeysSubdir = "root_keys"
+	// NonRootKeysSubdir is the subdirectory under PrivDir where non-root private keys are stored
+	NonRootKeysSubdir = "tuf_keys"
 )
diff --git a/cryptoservice/import_export.go b/cryptoservice/import_export.go
index f1d454e1..c4b19944 100644
--- a/cryptoservice/import_export.go
+++ b/cryptoservice/import_export.go
@@ -11,8 +11,10 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/docker/notary"
 	"github.com/docker/notary/passphrase"
 	"github.com/docker/notary/trustmanager"
+	"github.com/docker/notary/tuf/data"
 )
 
 const zipMadeByUNIX = 3 << 8
@@ -31,14 +33,17 @@ var (
 	ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN")
 )
 
-// ExportRootKey exports the specified root key to an io.Writer in PEM format.
+// ExportKey exports the specified private key to an io.Writer in PEM format.
 // The key's existing encryption is preserved.
-func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error {
+func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
 	var (
 		pemBytes []byte
 		err      error
 	)
 
+	if role != data.CanonicalRootRole {
+		keyID = filepath.Join(cs.gun, keyID)
+	}
 	for _, ks := range cs.keyStores {
 		pemBytes, err = ks.ExportKey(keyID)
 		if err != nil {
@@ -59,9 +64,9 @@ func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error {
 	return nil
 }
 
-// ExportRootKeyReencrypt exports the specified root key to an io.Writer in
+// ExportKeyReencrypt exports the specified private key to an io.Writer in
 // PEM format. The key is reencrypted with a new passphrase.
-func (cs *CryptoService) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
+func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
 	privateKey, role, err := cs.GetPrivateKey(keyID)
 	if err != nil {
 		return err
@@ -103,14 +108,41 @@ func (cs *CryptoService) ImportRootKey(source io.Reader) error {
 	if err != nil {
 		return err
 	}
+	return cs.ImportRoleKey(pemBytes, data.CanonicalRootRole, nil)
+}
 
-	if err = checkRootKeyIsEncrypted(pemBytes); err != nil {
-		return err
+// ImportRoleKey imports a private key in PEM format key from a byte array
+// It prompts for the key's passphrase to verify the data and to determine
+// the key ID.
+func (cs *CryptoService) ImportRoleKey(pemBytes []byte, role string, newPassphraseRetriever passphrase.Retriever) error {
+	var alias string
+	var err error
+	if role == data.CanonicalRootRole {
+		alias = role
+		if err = checkRootKeyIsEncrypted(pemBytes); err != nil {
+			return err
+		}
+	} else {
+		// Parse the private key to get the key ID so that we can import it to the correct location
+		privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
+		if err != nil {
+			privKey, _, err = trustmanager.GetPasswdDecryptBytes(newPassphraseRetriever, pemBytes, role, string(role))
+			if err != nil {
+				return err
+			}
+		}
+		// Since we're importing a non-root role, we need to pass the path as an alias
+		alias = filepath.Join(notary.NonRootKeysSubdir, cs.gun, privKey.ID())
+		// We also need to ensure that the role is properly set in the PEM headers
+		pemBytes, err = trustmanager.KeyToPEM(privKey, role)
+		if err != nil {
+			return err
+		}
 	}
 
 	for _, ks := range cs.keyStores {
 		// don't redeclare err, we want the value carried out of the loop
-		if err = ks.ImportKey(pemBytes, "root"); err == nil {
+		if err = ks.ImportKey(pemBytes, alias); err == nil {
 			return nil //bail on the first keystore we import to
 		}
 	}
diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go
index 0f9d8213..0a6b8ff4 100644
--- a/trustmanager/keyfilestore.go
+++ b/trustmanager/keyfilestore.go
@@ -8,16 +8,11 @@ import (
 	"sync"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/notary"
 	"github.com/docker/notary/passphrase"
 	"github.com/docker/notary/tuf/data"
 )
 
-const (
-	rootKeysSubdir    = "root_keys"
-	nonRootKeysSubdir = "tuf_keys"
-	privDir           = "private"
-)
-
 // KeyFileStore persists and manages private keys on disk
 type KeyFileStore struct {
 	sync.Mutex
@@ -37,7 +32,7 @@ type KeyMemoryStore struct {
 // NewKeyFileStore returns a new KeyFileStore creating a private directory to
 // hold the keys.
 func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyFileStore, error) {
-	baseDir = filepath.Join(baseDir, privDir)
+	baseDir = filepath.Join(baseDir, notary.PrivDir)
 	fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension)
 	if err != nil {
 		return nil, err
@@ -242,10 +237,10 @@ func listKeys(s LimitedFileStore) map[string]string {
 	for _, f := range s.ListFiles() {
 		// Remove the prefix of the directory from the filename
 		var keyIDFull string
-		if strings.HasPrefix(f, rootKeysSubdir+"/") {
-			keyIDFull = strings.TrimPrefix(f, rootKeysSubdir+"/")
+		if strings.HasPrefix(f, notary.RootKeysSubdir+"/") {
+			keyIDFull = strings.TrimPrefix(f, notary.RootKeysSubdir+"/")
 		} else {
-			keyIDFull = strings.TrimPrefix(f, nonRootKeysSubdir+"/")
+			keyIDFull = strings.TrimPrefix(f, notary.NonRootKeysSubdir+"/")
 		}
 
 		keyIDFull = strings.TrimSpace(keyIDFull)
@@ -302,9 +297,9 @@ func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string
 // Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys
 func getSubdir(alias string) string {
 	if alias == "root" {
-		return rootKeysSubdir
+		return notary.RootKeysSubdir
 	}
-	return nonRootKeysSubdir
+	return notary.NonRootKeysSubdir
 }
 
 // Given a key ID, gets the bytes and alias belonging to that key if the key
@@ -327,7 +322,7 @@ func getRawKey(s LimitedFileStore, name string) ([]byte, string, error) {
 	return keyBytes, role, nil
 }
 
-// GetPasswdDecryptBytes gets the password to decript the given pem bytes.
+// GetPasswdDecryptBytes gets the password to decrypt 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 (
diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go
index d44dc5d0..601b15b8 100644
--- a/trustmanager/x509utils.go
+++ b/trustmanager/x509utils.go
@@ -514,6 +514,19 @@ func EncryptPrivateKey(key data.PrivateKey, role, passphrase string) ([]byte, er
 	return pem.EncodeToMemory(encryptedPEMBlock), nil
 }
 
+// ReadRoleFromPEM returns the value from the role PEM header, if it exists
+func ReadRoleFromPEM(pemBytes []byte) string {
+	pemBlock, _ := pem.Decode(pemBytes)
+	if pemBlock.Headers == nil {
+		return ""
+	}
+	role, ok := pemBlock.Headers["role"]
+	if !ok {
+		return ""
+	}
+	return role
+}
+
 // CertToKey transforms a single input certificate into its corresponding
 // PublicKey
 func CertToKey(cert *x509.Certificate) data.PublicKey {


Test patch:
diff --git a/cmd/notary/integration_nonpkcs11_test.go b/cmd/notary/integration_nonpkcs11_test.go
index 83d33c20..4ce10b6c 100644
--- a/cmd/notary/integration_nonpkcs11_test.go
+++ b/cmd/notary/integration_nonpkcs11_test.go
@@ -12,7 +12,7 @@ import (
 func init() {
 	NewNotaryCommand = func() *cobra.Command {
 		commander := &notaryCommander{
-			getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
+			getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever(testPassphrase) },
 		}
 		return commander.GetCommand()
 	}
diff --git a/cmd/notary/integration_test.go b/cmd/notary/integration_test.go
index bb1f6e3e..261c33d5 100644
--- a/cmd/notary/integration_test.go
+++ b/cmd/notary/integration_test.go
@@ -20,6 +20,7 @@ import (
 
 	"github.com/Sirupsen/logrus"
 	ctxu "github.com/docker/distribution/context"
+	"github.com/docker/notary"
 	"github.com/docker/notary/cryptoservice"
 	"github.com/docker/notary/passphrase"
 	"github.com/docker/notary/server"
@@ -674,6 +675,47 @@ func TestClientDelegationsPublishing(t *testing.T) {
 	output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases")
 	assert.NoError(t, err)
 	assert.Contains(t, output, "targets/releases")
+
+	// remove the target for this role only
+	_, err = runCommand(t, tempDir, "remove", "gun", target, "--roles", "targets/releases")
+	assert.NoError(t, err)
+
+	// publish repo
+	_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
+	assert.NoError(t, err)
+
+	// Now remove this key, and make a new file to import the delegation's key from
+	assert.NoError(t, os.Remove(filepath.Join(keyDir, canonicalKeyID+".key")))
+	tempPrivFile, err := ioutil.TempFile("/tmp", "privfile")
+	assert.NoError(t, err)
+	defer os.Remove(tempPrivFile.Name())
+
+	// Write the private key to a file so we can import it
+	_, err = tempPrivFile.Write(privKeyBytesNoRole)
+	assert.NoError(t, err)
+	tempPrivFile.Close()
+
+	// Import the private key, associating it with our delegation role
+	_, err = runCommand(t, tempDir, "key", "import", tempPrivFile.Name(), "--role", "targets/releases")
+	assert.NoError(t, err)
+
+	// add a target using the delegation -- will only add to targets/releases
+	_, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases")
+	assert.NoError(t, err)
+
+	// list targets for targets/releases - we should see no targets until we publish
+	output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases")
+	assert.NoError(t, err)
+	assert.Contains(t, output, "No targets")
+
+	// publish repo
+	_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
+	assert.NoError(t, err)
+
+	// list targets for targets/releases - we should see our target!
+	output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases")
+	assert.NoError(t, err)
+	assert.Contains(t, output, "targets/releases")
 }
 
 // Splits a string into lines, and returns any lines that are not empty (
@@ -1028,6 +1070,104 @@ func TestClientKeyImportExportRootOnly(t *testing.T) {
 		t, tempDir, server.URL, "gun", target, tempFile.Name())
 }
 
+// Helper method to get the subdirectory for TUF keys
+func getKeySubdir(role, gun string) string {
+	subdir := notary.PrivDir
+	switch role {
+	case data.CanonicalRootRole:
+		return filepath.Join(subdir, notary.RootKeysSubdir)
+	case data.CanonicalTargetsRole:
+		return filepath.Join(subdir, notary.NonRootKeysSubdir, gun)
+	case data.CanonicalSnapshotRole:
+		return filepath.Join(subdir, notary.NonRootKeysSubdir, gun)
+	default:
+		return filepath.Join(subdir, notary.NonRootKeysSubdir)
+	}
+}
+
+// Tests importing and exporting keys for all different roles and GUNs
+func TestClientKeyImportExportAllRoles(t *testing.T) {
+	// -- setup --
+	setUp(t)
+
+	tempDir := tempDirWithConfig(t, "{}")
+	defer os.RemoveAll(tempDir)
+
+	server := setupServer()
+	defer server.Close()
+
+	// -- tests --
+	_, err := runCommand(t, tempDir, "-s", server.URL, "init", "gun")
+	assert.NoError(t, err)
+
+	testRoles := append(data.BaseRoles, "targets/releases")
+	// Test importing and exporting keys to all base roles and delegation role
+	for _, role := range testRoles {
+		// Do this while importing keys that have the PEM header role set or have --role set on import
+		for _, setKeyRole := range []bool{true, false} {
+			// Make a new key for this role
+			privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
+			assert.NoError(t, err)
+
+			// Make a tempfile for importing
+			tempFile, err := ioutil.TempFile("", "pemfile")
+			assert.NoError(t, err)
+
+			// Specify the role in the PEM header
+			pemBytes, err := trustmanager.EncryptPrivateKey(privKey, role, testPassphrase)
+			assert.NoError(t, err)
+			ioutil.WriteFile(tempFile.Name(), pemBytes, 0644)
+
+			// If we need to set the key role with the --role flag, do so on import
+			if setKeyRole {
+				// If it's targets/snapshot we must specify the GUN
+				if role == data.CanonicalTargetsRole || role == data.CanonicalSnapshotRole {
+					_, err = runCommand(t, tempDir, "key", "import", tempFile.Name(), "--gun", "gun", "--role", role)
+				} else {
+					_, err = runCommand(t, tempDir, "key", "import", tempFile.Name(), "--role", role)
+				}
+			} else {
+				// If it's targets/snapshot we must specify the GUN
+				if role == data.CanonicalTargetsRole || role == data.CanonicalSnapshotRole {
+					_, err = runCommand(t, tempDir, "key", "import", tempFile.Name(), "--gun", "gun")
+				} else {
+					_, err = runCommand(t, tempDir, "key", "import", tempFile.Name())
+				}
+			}
+			assert.NoError(t, err)
+
+			// Test that we imported correctly
+			keySubdir := getKeySubdir(role, "gun")
+			_, err = os.Stat(filepath.Join(tempDir, keySubdir, privKey.ID()+".key"))
+			assert.Nil(t, err)
+
+			// Remove the input file so we can test exporting
+			assert.NoError(t, os.Remove(tempFile.Name()))
+
+			// Make a tempfile for exporting to
+			tempFile, err = ioutil.TempFile("", "pemfile")
+			assert.NoError(t, err)
+
+			// Ensure exporting this key by ID gets the same key
+			_, err = runCommand(t, tempDir, "key", "export", privKey.ID(), tempFile.Name())
+			assert.NoError(t, err)
+			// Compare the bytes of the exported file and the root key file in the repo
+			exportedBytes, err := ioutil.ReadFile(tempFile.Name())
+			assert.NoError(t, err)
+			repoBytes, err := ioutil.ReadFile(filepath.Join(tempDir, keySubdir, privKey.ID()+".key"))
+			assert.NoError(t, err)
+			assert.Equal(t, repoBytes, exportedBytes)
+
+			// Ensure exporting this key and changing the passphrase works
+			_, err = runCommand(t, tempDir, "key", "export", privKey.ID(), tempFile.Name(), "-p")
+			assert.NoError(t, err)
+
+			// Remove the export file for cleanup
+			assert.NoError(t, os.Remove(tempFile.Name()))
+		}
+	}
+}
+
 func assertNumCerts(t *testing.T, tempDir string, expectedNum int) []string {
 	output, err := runCommand(t, tempDir, "cert", "list")
 	assert.NoError(t, err)
diff --git a/cmd/notary/keys_test.go b/cmd/notary/keys_test.go
index 95bf0979..bbb7bfb6 100644
--- a/cmd/notary/keys_test.go
+++ b/cmd/notary/keys_test.go
@@ -433,3 +433,102 @@ func TestChangeKeyPassphraseNonexistentID(t *testing.T) {
 	assert.Error(t, err)
 	assert.Contains(t, err.Error(), "could not retrieve local key for key ID provided")
 }
+
+func TestKeyImportMismatchingRoles(t *testing.T) {
+	k := &keyCommander{
+		configGetter:   func() (*viper.Viper, error) { return viper.New(), nil },
+		getRetriever:   func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
+		keysImportRole: "targets",
+	}
+	tempFileName := generateTempTestKeyFile(t, "snapshot")
+	defer os.Remove(tempFileName)
+
+	err := k.keysImport(&cobra.Command{}, []string{tempFileName})
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "does not match role")
+}
+
+func TestKeyImportNoGUNForTargetsPEM(t *testing.T) {
+	k := &keyCommander{
+		configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
+		getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
+	}
+	tempFileName := generateTempTestKeyFile(t, "targets")
+	defer os.Remove(tempFileName)
+
+	err := k.keysImport(&cobra.Command{}, []string{tempFileName})
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "Must specify GUN")
+}
+
+func TestKeyImportNoGUNForSnapshotPEM(t *testing.T) {
+	k := &keyCommander{
+		configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
+		getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
+	}
+	tempFileName := generateTempTestKeyFile(t, "snapshot")
+	defer os.Remove(tempFileName)
+
+	err := k.keysImport(&cobra.Command{}, []string{tempFileName})
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "Must specify GUN")
+}
+
+func TestKeyImportNoGUNForTargetsFlag(t *testing.T) {
+	k := &keyCommander{
+		configGetter:   func() (*viper.Viper, error) { return viper.New(), nil },
+		getRetriever:   func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
+		keysImportRole: "targets",
+	}
+	tempFileName := generateTempTestKeyFile(t, "")
+	defer os.Remove(tempFileName)
+
+	err := k.keysImport(&cobra.Command{}, []string{tempFileName})
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "Must specify GUN")
+}
+
+func TestKeyImportNoGUNForSnapshotFlag(t *testing.T) {
+	k := &keyCommander{
+		configGetter:   func() (*viper.Viper, error) { return viper.New(), nil },
+		getRetriever:   func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
+		keysImportRole: "snapshot",
+	}
+	tempFileName := generateTempTestKeyFile(t, "")
+	defer os.Remove(tempFileName)
+
+	err := k.keysImport(&cobra.Command{}, []string{tempFileName})
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "Must specify GUN")
+}
+
+func TestKeyImportNoRole(t *testing.T) {
+	k := &keyCommander{
+		configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
+		getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
+	}
+	tempFileName := generateTempTestKeyFile(t, "")
+	defer os.Remove(tempFileName)
+
+	err := k.keysImport(&cobra.Command{}, []string{tempFileName})
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "Could not infer role, and no role was specified for key")
+}
+
+func generateTempTestKeyFile(t *testing.T, role string) string {
+	privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
+	if err != nil {
+		return ""
+	}
+	keyBytes, err := trustmanager.KeyToPEM(privKey, role)
+	assert.NoError(t, err)
+
+	tempPrivFile, err := ioutil.TempFile("/tmp", "privfile")
+	assert.NoError(t, err)
+
+	// Write the private key to a file so we can import it
+	_, err = tempPrivFile.Write(keyBytes)
+	assert.NoError(t, err)
+	tempPrivFile.Close()
+	return tempPrivFile.Name()
+}
diff --git a/cryptoservice/import_export_test.go b/cryptoservice/import_export_test.go
index 5b482162..0262de16 100644
--- a/cryptoservice/import_export_test.go
+++ b/cryptoservice/import_export_test.go
@@ -77,7 +77,7 @@ func TestImportExportZip(t *testing.T) {
 		_, alias, err := cs.GetPrivateKey(privKeyName)
 		assert.NoError(t, err, "privKey %s has no alias", privKeyName)
 
-		if alias == "root" {
+		if alias == data.CanonicalRootRole {
 			continue
 		}
 		relKeyPath := filepath.Join("tuf_keys", privKeyName+".key")
@@ -138,7 +138,7 @@ func TestImportExportZip(t *testing.T) {
 		_, alias, err := cs2.GetPrivateKey(privKeyName)
 		assert.NoError(t, err, "privKey %s has no alias", privKeyName)
 
-		if alias == "root" {
+		if alias == data.CanonicalRootRole {
 			continue
 		}
 		relKeyPath := filepath.Join("tuf_keys", privKeyName+".key")
@@ -196,7 +196,7 @@ func TestImportExportGUN(t *testing.T) {
 	for privKeyName := range privKeyMap {
 		_, alias, err := cs.GetPrivateKey(privKeyName)
 		assert.NoError(t, err, "privKey %s has no alias", privKeyName)
-		if alias == "root" {
+		if alias == data.CanonicalRootRole {
 			continue
 		}
 		relKeyPath := filepath.Join("tuf_keys", privKeyName+".key")
@@ -250,12 +250,12 @@ 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, role := range privKeyMap {
-		if role == "root" {
+		if role == data.CanonicalRootRole {
 			continue
 		}
 		_, alias, err := cs2.GetPrivateKey(privKeyName)
 		assert.NoError(t, err, "privKey %s has no alias", privKeyName)
-		if alias == "root" {
+		if alias == data.CanonicalRootRole {
 			continue
 		}
 		relKeyPath := filepath.Join("tuf_keys", privKeyName+".key")
@@ -284,7 +284,7 @@ func TestImportExportRootKey(t *testing.T) {
 	tempKeyFilePath := tempKeyFile.Name()
 	defer os.Remove(tempKeyFilePath)
 
-	err = cs.ExportRootKey(tempKeyFile, rootKeyID)
+	err = cs.ExportKey(tempKeyFile, rootKeyID, data.CanonicalRootRole)
 	assert.NoError(t, err)
 	tempKeyFile.Close()
 
@@ -329,7 +329,7 @@ func TestImportExportRootKey(t *testing.T) {
 	// Should be able to unlock the root key with the old password
 	key, alias, err := cs2.GetPrivateKey(rootKeyID)
 	assert.NoError(t, err, "could not unlock root key")
-	assert.Equal(t, "root", alias)
+	assert.Equal(t, data.CanonicalRootRole, alias)
 	assert.Equal(t, rootKeyID, key.ID())
 }
 
@@ -352,7 +352,7 @@ func TestImportExportRootKeyReencrypt(t *testing.T) {
 	tempKeyFilePath := tempKeyFile.Name()
 	defer os.Remove(tempKeyFilePath)
 
-	err = cs.ExportRootKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever)
+	err = cs.ExportKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever)
 	assert.NoError(t, err)
 	tempKeyFile.Close()
 
@@ -381,6 +381,116 @@ func TestImportExportRootKeyReencrypt(t *testing.T) {
 	// Should be able to unlock the root key with the new password
 	key, alias, err := cs2.GetPrivateKey(rootKeyID)
 	assert.NoError(t, err, "could not unlock root key")
-	assert.Equal(t, "root", alias)
+	assert.Equal(t, data.CanonicalRootRole, alias)
 	assert.Equal(t, rootKeyID, key.ID())
 }
+
+func TestImportExportNonRootKey(t *testing.T) {
+	gun := "docker.com/notary"
+
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("", "notary-test-")
+	defer os.RemoveAll(tempBaseDir)
+	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
+
+	fileStore, err := trustmanager.NewKeyFileStore(tempBaseDir, oldPassphraseRetriever)
+	cs := NewCryptoService(gun, fileStore)
+	pubKey, err := cs.Create(data.CanonicalTargetsRole, data.ECDSAKey)
+	assert.NoError(t, err)
+
+	targetsKeyID := pubKey.ID()
+
+	tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
+	tempKeyFilePath := tempKeyFile.Name()
+	defer os.Remove(tempKeyFilePath)
+
+	err = cs.ExportKey(tempKeyFile, targetsKeyID, data.CanonicalTargetsRole)
+	assert.NoError(t, err)
+	tempKeyFile.Close()
+
+	// Create new repo to test import
+	tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
+	defer os.RemoveAll(tempBaseDir2)
+	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
+
+	fileStore2, err := trustmanager.NewKeyFileStore(tempBaseDir2, oldPassphraseRetriever)
+	cs2 := NewCryptoService(gun, fileStore2)
+
+	keyReader, err := os.Open(tempKeyFilePath)
+	assert.NoError(t, err, "could not open key file")
+
+	pemBytes, err := ioutil.ReadAll(keyReader)
+	assert.NoError(t, err, "could not read key file")
+
+	err = cs2.ImportRoleKey(pemBytes, data.CanonicalTargetsRole, oldPassphraseRetriever)
+	assert.NoError(t, err)
+	keyReader.Close()
+
+	// Look for repo's targets key in repo2
+	// There should be a file named after the key ID of the targets key we
+	// imported.
+	targetsKeyFilename := targetsKeyID + ".key"
+	_, err = os.Stat(filepath.Join(tempBaseDir2, "private", "tuf_keys", "docker.com/notary", targetsKeyFilename))
+	assert.NoError(t, err, "missing targets key")
+
+	// Check that the key is the same
+	key, alias, err := cs2.GetPrivateKey(targetsKeyID)
+	assert.NoError(t, err, "could not unlock targets key")
+	assert.Equal(t, data.CanonicalTargetsRole, alias)
+	assert.Equal(t, targetsKeyID, key.ID())
+}
+
+func TestImportExportNonRootKeyReencrypt(t *testing.T) {
+	gun := "docker.com/notary"
+
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("", "notary-test-")
+	defer os.RemoveAll(tempBaseDir)
+	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
+
+	fileStore, err := trustmanager.NewKeyFileStore(tempBaseDir, oldPassphraseRetriever)
+	cs := NewCryptoService(gun, fileStore)
+	pubKey, err := cs.Create(data.CanonicalSnapshotRole, data.ECDSAKey)
+	assert.NoError(t, err)
+
+	snapshotKeyID := pubKey.ID()
+
+	tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
+	tempKeyFilePath := tempKeyFile.Name()
+	defer os.Remove(tempKeyFilePath)
+
+	err = cs.ExportKeyReencrypt(tempKeyFile, snapshotKeyID, newPassphraseRetriever)
+	assert.NoError(t, err)
+	tempKeyFile.Close()
+
+	// Create new repo to test import
+	tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
+	defer os.RemoveAll(tempBaseDir2)
+	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
+
+	fileStore2, err := trustmanager.NewKeyFileStore(tempBaseDir2, newPassphraseRetriever)
+	cs2 := NewCryptoService(gun, fileStore2)
+
+	keyReader, err := os.Open(tempKeyFilePath)
+	assert.NoError(t, err, "could not open key file")
+
+	pemBytes, err := ioutil.ReadAll(keyReader)
+	assert.NoError(t, err, "could not read key file")
+
+	err = cs2.ImportRoleKey(pemBytes, data.CanonicalSnapshotRole, newPassphraseRetriever)
+	assert.NoError(t, err)
+	keyReader.Close()
+
+	// Look for repo's snapshot key in repo2
+	// There should be a file named after the key ID of the snapshot key we
+	// imported.
+	snapshotKeyFilename := snapshotKeyID + ".key"
+	_, err = os.Stat(filepath.Join(tempBaseDir2, "private", "tuf_keys", "docker.com/notary", snapshotKeyFilename))
+	assert.NoError(t, err, "missing snapshot key")
+
+	// Should be able to unlock the root key with the new password
+	key, alias, err := cs2.GetPrivateKey(snapshotKeyID)
+	assert.NoError(t, err, "could not unlock snapshot key")
+	assert.Equal(t, data.CanonicalSnapshotRole, alias)
+	assert.Equal(t, snapshotKeyID, key.ID())
+}
diff --git a/trustmanager/keyfilestore_test.go b/trustmanager/keyfilestore_test.go
index e914c9b1..92255046 100644
--- a/trustmanager/keyfilestore_test.go
+++ b/trustmanager/keyfilestore_test.go
@@ -9,6 +9,7 @@ import (
 	"path/filepath"
 	"testing"
 
+	"github.com/docker/notary"
 	"github.com/docker/notary/passphrase"
 	"github.com/docker/notary/tuf/data"
 	"github.com/stretchr/testify/assert"
@@ -25,11 +26,11 @@ var passphraseRetriever = func(keyID string, alias string, createNew bool, numAt
 }
 
 func TestAddKey(t *testing.T) {
-	testAddKeyWithRole(t, data.CanonicalRootRole, rootKeysSubdir)
-	testAddKeyWithRole(t, data.CanonicalTargetsRole, nonRootKeysSubdir)
-	testAddKeyWithRole(t, data.CanonicalSnapshotRole, nonRootKeysSubdir)
-	testAddKeyWithRole(t, "targets/a/b/c", nonRootKeysSubdir)
-	testAddKeyWithRole(t, "invalidRole", nonRootKeysSubdir)
+	testAddKeyWithRole(t, data.CanonicalRootRole, notary.RootKeysSubdir)
+	testAddKeyWithRole(t, data.CanonicalTargetsRole, notary.NonRootKeysSubdir)
+	testAddKeyWithRole(t, data.CanonicalSnapshotRole, notary.NonRootKeysSubdir)
+	testAddKeyWithRole(t, "targets/a/b/c", notary.NonRootKeysSubdir)
+	testAddKeyWithRole(t, "invalidRole", notary.NonRootKeysSubdir)
 }
 
 func testAddKeyWithRole(t *testing.T, role, expectedSubdir string) {
@@ -42,7 +43,7 @@ func testAddKeyWithRole(t *testing.T, role, expectedSubdir string) {
 	defer os.RemoveAll(tempBaseDir)
 
 	// Since we're generating this manually we need to add the extension '.'
-	expectedFilePath := filepath.Join(tempBaseDir, privDir, expectedSubdir, testName+"."+testExt)
+	expectedFilePath := filepath.Join(tempBaseDir, notary.PrivDir, expectedSubdir, testName+"."+testExt)
 
 	// Create our store
 	store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever)
@@ -73,22 +74,22 @@ func TestGet(t *testing.T) {
 
 	// Root role needs to go in the rootKeySubdir to be read.
 	// All other roles can go in the nonRootKeysSubdir, possibly under a GUN
-	rootKeysSubdirWithGUN := filepath.Clean(filepath.Join(rootKeysSubdir, gun))
-	nonRootKeysSubdirWithGUN := filepath.Clean(filepath.Join(nonRootKeysSubdir, gun))
+	rootKeysSubdirWithGUN := filepath.Clean(filepath.Join(notary.RootKeysSubdir, gun))
+	nonRootKeysSubdirWithGUN := filepath.Clean(filepath.Join(notary.NonRootKeysSubdir, gun))
 
-	testGetKeyWithRole(t, "", data.CanonicalRootRole, rootKeysSubdir, true)
+	testGetKeyWithRole(t, "", data.CanonicalRootRole, notary.RootKeysSubdir, true)
 	testGetKeyWithRole(t, gun, data.CanonicalRootRole, rootKeysSubdirWithGUN, true)
 	for _, role := range nonRootRolesToTest {
-		testGetKeyWithRole(t, "", role, nonRootKeysSubdir, true)
+		testGetKeyWithRole(t, "", role, notary.NonRootKeysSubdir, true)
 		testGetKeyWithRole(t, gun, role, nonRootKeysSubdirWithGUN, true)
 	}
 
 	// Root cannot go in the nonRootKeysSubdir, or it won't be able to be read,
 	// and vice versa
-	testGetKeyWithRole(t, "", data.CanonicalRootRole, nonRootKeysSubdir, false)
+	testGetKeyWithRole(t, "", data.CanonicalRootRole, notary.NonRootKeysSubdir, false)
 	testGetKeyWithRole(t, gun, data.CanonicalRootRole, nonRootKeysSubdirWithGUN, false)
 	for _, role := range nonRootRolesToTest {
-		testGetKeyWithRole(t, "", role, rootKeysSubdir, false)
+		testGetKeyWithRole(t, "", role, notary.RootKeysSubdir, false)
 		testGetKeyWithRole(t, gun, role, rootKeysSubdirWithGUN, false)
 	}
 }
@@ -136,7 +137,7 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0=
 	defer os.RemoveAll(tempBaseDir)
 
 	// Since we're generating this manually we need to add the extension '.'
-	filePath := filepath.Join(tempBaseDir, privDir, expectedSubdir, testName+"."+testExt)
+	filePath := filepath.Join(tempBaseDir, notary.PrivDir, expectedSubdir, testName+"."+testExt)
 	os.MkdirAll(filepath.Dir(filePath), perms)
 	err = ioutil.WriteFile(filePath, testData, perms)
 	assert.NoError(t, err, "failed to write test file")
@@ -206,7 +207,7 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0=
 	defer os.RemoveAll(tempBaseDir)
 
 	// Since we're generating this manually we need to add the extension '.'
-	filePath := filepath.Join(tempBaseDir, privDir, rootKeysSubdir, testName+"_"+testAlias+"."+testExt)
+	filePath := filepath.Join(tempBaseDir, notary.PrivDir, notary.RootKeysSubdir, testName+"_"+testAlias+"."+testExt)
 
 	os.MkdirAll(filepath.Dir(filePath), perms)
 	err = ioutil.WriteFile(filePath, testData, perms)
@@ -258,7 +259,7 @@ func TestListKeys(t *testing.T) {
 	}
 
 	// Write an invalid filename to the directory
-	filePath := filepath.Join(tempBaseDir, privDir, rootKeysSubdir, "fakekeyname.key")
+	filePath := filepath.Join(tempBaseDir, notary.PrivDir, notary.RootKeysSubdir, "fakekeyname.key")
 	err = ioutil.WriteFile(filePath, []byte("data"), perms)
 	assert.NoError(t, err, "failed to write test file")
 
@@ -312,7 +313,7 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
 	assert.NoError(t, err, "failed to add key to store")
 
 	// Since we're generating this manually we need to add the extension '.'
-	expectedFilePath := filepath.Join(tempBaseDir, privDir, rootKeysSubdir, privKey.ID()+"."+testExt)
+	expectedFilePath := filepath.Join(tempBaseDir, notary.PrivDir, notary.RootKeysSubdir, privKey.ID()+"."+testExt)
 
 	// Get file description, open file
 	fp, err := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600)
@@ -409,11 +410,11 @@ func testGetDecryptedWithInvalidPassphrase(t *testing.T, store KeyStore, newStor
 }
 
 func TestRemoveKey(t *testing.T) {
-	testRemoveKeyWithRole(t, data.CanonicalRootRole, rootKeysSubdir)
-	testRemoveKeyWithRole(t, data.CanonicalTargetsRole, nonRootKeysSubdir)
-	testRemoveKeyWithRole(t, data.CanonicalSnapshotRole, nonRootKeysSubdir)
-	testRemoveKeyWithRole(t, "targets/a/b/c", nonRootKeysSubdir)
-	testRemoveKeyWithRole(t, "invalidRole", nonRootKeysSubdir)
+	testRemoveKeyWithRole(t, data.CanonicalRootRole, notary.RootKeysSubdir)
+	testRemoveKeyWithRole(t, data.CanonicalTargetsRole, notary.NonRootKeysSubdir)
+	testRemoveKeyWithRole(t, data.CanonicalSnapshotRole, notary.NonRootKeysSubdir)
+	testRemoveKeyWithRole(t, "targets/a/b/c", notary.NonRootKeysSubdir)
+	testRemoveKeyWithRole(t, "invalidRole", notary.NonRootKeysSubdir)
 }
 
 func testRemoveKeyWithRole(t *testing.T, role, expectedSubdir string) {
@@ -426,7 +427,7 @@ func testRemoveKeyWithRole(t *testing.T, role, expectedSubdir string) {
 	defer os.RemoveAll(tempBaseDir)
 
 	// Since we're generating this manually we need to add the extension '.'
-	expectedFilePath := filepath.Join(tempBaseDir, privDir, expectedSubdir, testName+"."+testExt)
+	expectedFilePath := filepath.Join(tempBaseDir, notary.PrivDir, expectedSubdir, testName+"."+testExt)
 
 	// Create our store
 	store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever)