diff --git a/cmd/notary/integration_test.go b/cmd/notary/integration_test.go index ad3e1c509c..0cf30fee4b 100644 --- a/cmd/notary/integration_test.go +++ b/cmd/notary/integration_test.go @@ -23,6 +23,7 @@ import ( "github.com/docker/notary/server" "github.com/docker/notary/server/storage" "github.com/docker/notary/trustmanager" + "github.com/docker/notary/tuf/data" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "golang.org/x/net/context" @@ -50,10 +51,7 @@ func setupServer() *httptest.Server { ctx := context.WithValue( context.Background(), "metaStore", storage.NewMemStorage()) - // Do not pass one of the const KeyAlgorithms here as the value! Passing a - // string is in itself good test that we are handling it correctly as we - // will be receiving a string from the configuration. - ctx = context.WithValue(ctx, "keyAlgorithm", "ecdsa") + ctx = context.WithValue(ctx, "keyAlgorithm", data.ECDSAKey) // Eat the logs instead of spewing them out var b bytes.Buffer @@ -181,19 +179,24 @@ func GetKeys(t *testing.T, tempDir string) ([]string, []string) { func assertNumKeys(t *testing.T, tempDir string, numRoot, numSigning int, rootOnDisk bool) ([]string, []string) { + uniqueKeys := make(map[string]struct{}) root, signing := GetKeys(t, tempDir) - assert.Len(t, root, numRoot) assert.Len(t, signing, numSigning) - for _, rootKeyID := range root { + for i, rootKeyLine := range root { + keyID := strings.Split(rootKeyLine, "-")[0] + keyID = strings.TrimSpace(keyID) + root[i] = keyID + uniqueKeys[keyID] = struct{}{} _, err := os.Stat(filepath.Join( - tempDir, "private", "root_keys", rootKeyID+"_root.key")) + tempDir, "private", "root_keys", keyID+"_root.key")) // os.IsExist checks to see if the error is because a file already // exist, and hence doesn't actually the right funciton to use here assert.Equal(t, rootOnDisk, !os.IsNotExist(err)) // this function is declared is in the build-tagged setup files - verifyRootKeyOnHardware(t, rootKeyID) + verifyRootKeyOnHardware(t, keyID) } + assert.Len(t, uniqueKeys, numRoot) return root, signing } @@ -245,7 +248,7 @@ func TestClientKeyGenerationRotation(t *testing.T) { assertNumKeys(t, tempDir, 0, 0, true) // generate root key produces a single root key and no other keys - _, err = runCommand(t, tempDir, "key", "generate", "ecdsa") + _, err = runCommand(t, tempDir, "key", "generate", data.ECDSAKey) assert.NoError(t, err) assertNumKeys(t, tempDir, 1, 0, true) @@ -345,7 +348,7 @@ func TestClientKeyImportExportRootAndSigning(t *testing.T) { _, err = runCommand(t, dirs[1], "key", "import", zipfile) assert.NoError(t, err) - assertNumKeys(t, dirs[1], 1, 4, true) // all keys should be there + assertNumKeys(t, dirs[1], 1, 4, !rootOnHardware()) // all keys should be there // can list and publish to both repos using imported keys for _, gun := range []string{"gun1", "gun2"} { @@ -383,7 +386,7 @@ func exportRoot(t *testing.T, exportTo string) string { defer os.RemoveAll(tempDir) // generate root key produces a single root key and no other keys - _, err = runCommand(t, tempDir, "key", "generate", "ecdsa") + _, err = runCommand(t, tempDir, "key", "generate", data.ECDSAKey) assert.NoError(t, err) oldRoot, _ := assertNumKeys(t, tempDir, 1, 0, true) @@ -505,7 +508,6 @@ func TestClientCertInteraction(t *testing.T) { _, err = runCommand(t, tempDir, "cert", "remove", certID, "-y", "-g", "") assert.NoError(t, err) assertNumCerts(t, tempDir, 0) - } func TestMain(m *testing.M) { diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index 6104edd987..985d33e427 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -7,7 +7,11 @@ import ( "sort" "strings" + "github.com/docker/notary" notaryclient "github.com/docker/notary/client" + "github.com/docker/notary/cryptoservice" + "github.com/docker/notary/passphrase" + "github.com/docker/notary/trustmanager" "github.com/docker/notary/tuf/data" "github.com/spf13/cobra" @@ -93,16 +97,20 @@ func keysList(cmd *cobra.Command, args []string) { parseConfig() - cs := getCryptoService(cmd, trustDir, retriever, true) + stores := getKeyStores(cmd, trustDir, retriever, true) - // Get a map of all the keys/roles - keysMap := cs.ListAllKeys() + keys := make(map[trustmanager.KeyStore]map[string]string) + for _, store := range stores { + keys[store] = store.ListKeys() + } cmd.Println("") cmd.Println("# Root keys: ") - for k, v := range keysMap { - if v == "root" { - cmd.Println(k) + for store, keysMap := range keys { + for k, v := range keysMap { + if v == "root" { + cmd.Println(k, "-", store.Name()) + } } } @@ -110,17 +118,20 @@ func keysList(cmd *cobra.Command, args []string) { cmd.Println("# Signing keys: ") // Get a list of all the keys - var sortedKeys []string - for k := range keysMap { - sortedKeys = append(sortedKeys, k) - } - // Sort the list of all the keys - sort.Strings(sortedKeys) + for store, keysMap := range keys { + var sortedKeys []string + for k := range keysMap { + sortedKeys = append(sortedKeys, k) + } - // Print a sorted list of the key/role - for _, k := range sortedKeys { - if keysMap[k] != "root" { - printKey(cmd, k, keysMap[k]) + // Sort the list of all the keys + sort.Strings(sortedKeys) + + // Print a sorted list of the key/role + for _, k := range sortedKeys { + if keysMap[k] != "root" { + printKey(cmd, k, keysMap[k], store.Name()) + } } } } @@ -143,7 +154,10 @@ func keysGenerateRootKey(cmd *cobra.Command, args []string) { parseConfig() - cs := getCryptoService(cmd, trustDir, retriever, true) + cs := cryptoservice.NewCryptoService( + "", + getKeyStores(cmd, trustDir, retriever, true)..., + ) pubKey, err := cs.Create(data.CanonicalRootRole, algorithm) if err != nil { @@ -164,7 +178,10 @@ func keysExport(cmd *cobra.Command, args []string) { parseConfig() - cs := getCryptoService(cmd, trustDir, retriever, false) + cs := cryptoservice.NewCryptoService( + "", + getKeyStores(cmd, trustDir, retriever, true)..., + ) exportFile, err := os.Create(exportFilename) if err != nil { @@ -204,7 +221,10 @@ func keysExportRoot(cmd *cobra.Command, args []string) { parseConfig() - cs := getCryptoService(cmd, trustDir, retriever, false) + cs := cryptoservice.NewCryptoService( + "", + getKeyStores(cmd, trustDir, retriever, true)..., + ) exportFile, err := os.Create(exportFilename) if err != nil { @@ -236,7 +256,10 @@ func keysImport(cmd *cobra.Command, args []string) { parseConfig() - cs := getCryptoService(cmd, trustDir, retriever, false) + cs := cryptoservice.NewCryptoService( + "", + getKeyStores(cmd, trustDir, retriever, true)..., + ) zipReader, err := zip.OpenReader(importFilename) if err != nil { @@ -262,7 +285,10 @@ func keysImportRoot(cmd *cobra.Command, args []string) { parseConfig() - cs := getCryptoService(cmd, trustDir, retriever, true) + cs := cryptoservice.NewCryptoService( + "", + getKeyStores(cmd, trustDir, retriever, true)..., + ) importFile, err := os.Open(importFilename) if err != nil { @@ -277,10 +303,10 @@ func keysImportRoot(cmd *cobra.Command, args []string) { } } -func printKey(cmd *cobra.Command, keyPath, alias string) { +func printKey(cmd *cobra.Command, keyPath, alias, loc string) { keyID := filepath.Base(keyPath) gun := filepath.Dir(keyPath) - cmd.Printf("%s - %s - %s\n", gun, alias, keyID) + cmd.Printf("%s - %s - %s - %s\n", gun, alias, keyID, loc) } func keysRotate(cmd *cobra.Command, args []string) { @@ -299,3 +325,26 @@ func keysRotate(cmd *cobra.Command, args []string) { fatalf(err.Error()) } } + +func getKeyStores(cmd *cobra.Command, directory string, + ret passphrase.Retriever, withHardware bool) []trustmanager.KeyStore { + + keysPath := filepath.Join(directory, notary.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, ret) + if err != nil { + fatalf("Failed to create private key store in directory: %s", keysPath) + } + + ks := []trustmanager.KeyStore{fileKeyStore} + + if withHardware { + yubiStore, err := getYubiKeyStore(fileKeyStore, ret) + if err == nil && yubiStore != nil { + // Note that the order is important, since we want to prioritize + // the yubikey store + ks = []trustmanager.KeyStore{yubiStore, fileKeyStore} + } + } + + return ks +} diff --git a/cmd/notary/keys_nonpkcs11.go b/cmd/notary/keys_nonpkcs11.go index 31179d02e6..bcb75b0a06 100644 --- a/cmd/notary/keys_nonpkcs11.go +++ b/cmd/notary/keys_nonpkcs11.go @@ -3,22 +3,12 @@ package main import ( - "path/filepath" + "errors" - "github.com/docker/notary" - "github.com/docker/notary/cryptoservice" "github.com/docker/notary/passphrase" "github.com/docker/notary/trustmanager" - "github.com/spf13/cobra" ) -func getCryptoService(cmd *cobra.Command, directory string, - ret passphrase.Retriever, _ bool) *cryptoservice.CryptoService { - - keysPath := filepath.Join(directory, notary.PrivDir) - fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, ret) - if err != nil { - fatalf("Failed to create private key store in directory: %s", keysPath) - } - return cryptoservice.NewCryptoService("", fileKeyStore) +func getYubiKeyStore(fileKeyStore trustmanager.KeyStore, ret passphrase.Retriever) (trustmanager.KeyStore, error) { + return nil, errors.New("Not built with hardware support") } diff --git a/cmd/notary/keys_pkcs11.go b/cmd/notary/keys_pkcs11.go index e1df821d12..87af9dad65 100644 --- a/cmd/notary/keys_pkcs11.go +++ b/cmd/notary/keys_pkcs11.go @@ -3,38 +3,10 @@ package main import ( - "path/filepath" - - "github.com/docker/notary" - "github.com/docker/notary/cryptoservice" "github.com/docker/notary/passphrase" "github.com/docker/notary/trustmanager" - "github.com/spf13/cobra" ) -// Build a CryptoService, optionally including a hardware keystore. Returns -// the CryptoService and whether or not a hardware keystore was included. -func getCryptoService(cmd *cobra.Command, directory string, - ret passphrase.Retriever, withHardware bool) *cryptoservice.CryptoService { - - keysPath := filepath.Join(directory, notary.PrivDir) - fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, ret) - if err != nil { - fatalf("Failed to create private key store in directory: %s", keysPath) - } - - ks := []trustmanager.KeyStore{fileKeyStore} - - if withHardware { - yubiStore, err := trustmanager.NewYubiKeyStore(fileKeyStore, ret) - if err != nil { - cmd.Println("No YubiKey detected - using local filesystem only.") - } else { - // Note that the order is important, since we want to prioritize - // the yubikey store - ks = []trustmanager.KeyStore{yubiStore, fileKeyStore} - } - } - - return cryptoservice.NewCryptoService("", ks...) +func getYubiKeyStore(fileKeyStore trustmanager.KeyStore, ret passphrase.Retriever) (trustmanager.KeyStore, error) { + return trustmanager.NewYubiKeyStore(fileKeyStore, ret) } diff --git a/cryptoservice/import_export.go b/cryptoservice/import_export.go index 7ee3ded5ad..66405b22e3 100644 --- a/cryptoservice/import_export.go +++ b/cryptoservice/import_export.go @@ -197,11 +197,20 @@ func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error { if keyName[len(keyName)-5:] == "_root" { keyName = "root" } + // try to import the key to all key stores. As long as one of them + // succeeds, consider it a success + var tmpErr error for _, ks := range cs.keyStores { if err := ks.ImportKey(pemBytes, keyName); err != nil { - return err + tmpErr = err + } else { + tmpErr = nil + break } } + if tmpErr != nil { + return tmpErr + } } return nil diff --git a/signer/keydbstore/keydbstore.go b/signer/keydbstore/keydbstore.go index c3e89a190c..2f9813932f 100644 --- a/signer/keydbstore/keydbstore.go +++ b/signer/keydbstore/keydbstore.go @@ -58,6 +58,11 @@ func NewKeyDBStore(passphraseRetriever passphrase.Retriever, defaultPassAlias, d cachedKeys: cachedKeys}, nil } +// Name returns a user friendly name for the storage location +func (s *KeyDBStore) Name() string { + return "database" +} + // AddKey stores the contents of a private key. Both name and alias are ignored, // we always use Key IDs as name, and don't support aliases func (s *KeyDBStore) AddKey(name, alias string, privKey data.PrivateKey) error { diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index 18b8969b00..b8148c58ca 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -1,6 +1,7 @@ package trustmanager import ( + "fmt" "path/filepath" "strings" "sync" @@ -44,6 +45,12 @@ func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) ( cachedKeys: cachedKeys}, nil } +// Name returns a user friendly name for the location this store +// keeps its data +func (s *KeyFileStore) Name() string { + return fmt.Sprintf("file (%s)", s.SimpleFileStore.BaseDir()) +} + // AddKey stores the contents of a PEM-encoded private key as a PEM block func (s *KeyFileStore) AddKey(name, alias string, privKey data.PrivateKey) error { s.Lock() @@ -96,6 +103,12 @@ func NewKeyMemoryStore(passphraseRetriever passphrase.Retriever) *KeyMemoryStore cachedKeys: cachedKeys} } +// Name returns a user friendly name for the location this store +// keeps its data +func (s *KeyMemoryStore) Name() string { + return "memory" +} + // AddKey stores the contents of a PEM-encoded private key as a PEM block func (s *KeyMemoryStore) AddKey(name, alias string, privKey data.PrivateKey) error { s.Lock() diff --git a/trustmanager/keystore.go b/trustmanager/keystore.go index 4a06d9962e..88a0e3d774 100644 --- a/trustmanager/keystore.go +++ b/trustmanager/keystore.go @@ -46,6 +46,7 @@ type KeyStore interface { RemoveKey(name string) error ExportKey(name string) ([]byte, error) ImportKey(pemBytes []byte, alias string) error + Name() string } type cachedKey struct { diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index e462671a04..51877ae45b 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -398,12 +398,12 @@ func ED25519ToPrivateKey(privKeyBytes []byte) (data.PrivateKey, error) { } func blockType(k data.PrivateKey) (string, error) { - switch k.(type) { - case *data.RSAPrivateKey: + switch k.Algorithm() { + case data.RSAKey, data.RSAx509Key: return "RSA PRIVATE KEY", nil - case *data.ECDSAPrivateKey: + case data.ECDSAKey, data.ECDSAx509Key: return "EC PRIVATE KEY", nil - case *data.ED25519PrivateKey: + case data.ED25519Key: return "ED25519 PRIVATE KEY", nil default: return "", fmt.Errorf("algorithm %s not supported", k.Algorithm()) diff --git a/trustmanager/yubikeystore.go b/trustmanager/yubikeystore.go index 6945944c8c..52f106a718 100644 --- a/trustmanager/yubikeystore.go +++ b/trustmanager/yubikeystore.go @@ -580,6 +580,12 @@ func NewYubiKeyStore(backupStore KeyStore, passphraseRetriever passphrase.Retrie return s, nil } +// Name returns a user friendly name for the location this store +// keeps its data +func (s YubiKeyStore) Name() string { + return "yubikey" +} + func (s *YubiKeyStore) ListKeys() map[string]string { if len(s.keys) > 0 { return buildKeyMap(s.keys) @@ -701,13 +707,16 @@ func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) { } // ImportKey imports a root key into a Yubikey -func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyID string) error { - logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyID) +func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error { + logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyPath) privKey, _, err := GetPasswdDecryptBytes( s.passRetriever, pemBytes, "", "imported root") if err != nil { return err } + if keyPath != data.CanonicalRootRole { + return fmt.Errorf("yubikey only supports storing root keys") + } return s.addKey(privKey.ID(), "root", privKey, false) } diff --git a/trustmanager/yubikeystore_test.go b/trustmanager/yubikeystore_test.go index b769404cc4..6bbd9b8bc3 100644 --- a/trustmanager/yubikeystore_test.go +++ b/trustmanager/yubikeystore_test.go @@ -129,7 +129,7 @@ func TestImportKey(t *testing.T) { pemBytes, err := EncryptPrivateKey(privKey, "passphrase") assert.NoError(t, err) - err = store.ImportKey(pemBytes, privKey.ID()) + err = store.ImportKey(pemBytes, "root") assert.NoError(t, err) // key is not in backup store