diff --git a/cmd/notary-signer/main.go b/cmd/notary-signer/main.go index da0e3c719e..e2721da8ac 100644 --- a/cmd/notary-signer/main.go +++ b/cmd/notary-signer/main.go @@ -41,7 +41,7 @@ func init() { flag.BoolVar(&debug, "debug", false, "show the version and exit") } -func passphraseRetriever(keyName string, createNew bool, attempts int) (passphrase string, giveup bool, err error) { +func passphraseRetriever(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) { //TODO(mccauley) Read from config once we have locked keys in notary-signer return "", false, nil; diff --git a/cmd/notary/main.go b/cmd/notary/main.go index 08cec19bf4..a9bd2bf896 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -89,7 +89,7 @@ func init() { //TODO(mccauley): Appears unused? Remove it? Or is it here for early failure? privKeyStore, err = trustmanager.NewKeyFileStore(finalPrivDir, - func (string, bool, int) (string, bool, error) { return "", false, nil}) + func (string, string, bool, int) (string, bool, error) { return "", false, nil}) if err != nil { fatalf("could not create KeyFileStore: %v", err) } diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index c676417364..fd65cb73e0 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -16,11 +16,18 @@ import ( notaryclient "github.com/docker/notary/client" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/docker/notary/trustmanager" ) // FIXME: This should not be hardcoded const hardcodedBaseURL = "https://notary-server:4443" +var retriever trustmanager.PassphraseRetriever + +func init() { + retriever = getNotaryPassphraseRetriever() +} + var remoteTrustServer string var cmdTufList = &cobra.Command{ @@ -83,7 +90,7 @@ func tufAdd(cmd *cobra.Command, args []string) { targetPath := args[2] repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getInsecureTransport(), passphraseRetriever) + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -108,7 +115,7 @@ func tufInit(cmd *cobra.Command, args []string) { gun := args[0] nRepo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getInsecureTransport(), passphraseRetriever) + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -144,7 +151,7 @@ func tufList(cmd *cobra.Command, args []string) { } gun := args[0] repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getInsecureTransport(), passphraseRetriever) + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -170,7 +177,7 @@ func tufLookup(cmd *cobra.Command, args []string) { targetName := args[1] repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getInsecureTransport(), passphraseRetriever) + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -195,12 +202,12 @@ func tufPublish(cmd *cobra.Command, args []string) { fmt.Println("Pushing changes to ", gun, ".") repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getInsecureTransport(), passphraseRetriever) + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } - err = repo.Publish(passphraseRetriever) + err = repo.Publish(retriever) if err != nil { fatalf(err.Error()) } @@ -241,7 +248,7 @@ func verify(cmd *cobra.Command, args []string) { gun := args[0] targetName := args[1] repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getInsecureTransport(), passphraseRetriever) + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -265,51 +272,82 @@ func verify(cmd *cobra.Command, args []string) { return } -func passphraseRetriever(keyName string, createNew bool, numAttempts int) (string, bool, error) { - fmt.Printf("Retrieving passphrase for key %s: ", keyName) +func getNotaryPassphraseRetriever() (trustmanager.PassphraseRetriever) { + userEnteredTargetsSnapshotsPass := false + targetsSnapshotsPass := "" - if (numAttempts > 3 && !createNew) { - return "", true, errors.New("Too many attempts") + return func(keyID string, alias string, createNew bool, numAttempts int) (string, bool, error) { + fmt.Printf("userEnteredTargetsSnapshotsPass: %s\n", userEnteredTargetsSnapshotsPass) + fmt.Printf("targetsSnapshotsPass: %s\n", targetsSnapshotsPass) + fmt.Printf("keyID: %s\n", keyID) + fmt.Printf("alias: %s\n", alias) + fmt.Printf("numAttempts: %s\n", numAttempts) + + if numAttempts == 0 && userEnteredTargetsSnapshotsPass && (alias == "snapshot" || alias == "targets") { + fmt.Println("return cached value") + + return targetsSnapshotsPass, false, nil; + } + if (numAttempts > 3 && !createNew) { + return "", true, errors.New("Too many attempts") + } + + state, err := term.SaveState(0) + if err != nil { + return "", false, err + } + term.DisableEcho(0, state) + defer term.RestoreTerminal(0, state) + + stdin := bufio.NewReader(os.Stdin) + + if createNew { + fmt.Printf("Enter passphrase for new %s key with id %s: ", alias, keyID) + }else { + fmt.Printf("Enter key passphrase for %s key with id %s: ", alias, keyID) + } + + passphrase, err := stdin.ReadBytes('\n') + fmt.Println() + if err != nil { + return "", false, err + } + passphrase = passphrase[0 : len(passphrase)-1] + + if !createNew { + retPass := string(passphrase) + if alias == "snapshot" || alias == "targets" { + userEnteredTargetsSnapshotsPass = true + targetsSnapshotsPass = retPass + } + return string(passphrase), false, nil; + } + + if len(passphrase) < 8 { + fmt.Println("Please use a password manager to generate and store a good random passphrase.") + return "", false, errors.New("Passphrase too short") + } + + fmt.Printf("Repeat passphrase for new %s key with id %s:: ", alias, keyID) + confirmation, err := stdin.ReadBytes('\n') + fmt.Println() + if err != nil { + return "", false, err + } + confirmation = confirmation[0 : len(confirmation)-1] + + if !bytes.Equal(passphrase, confirmation) { + return "", false, errors.New("The entered passphrases do not match") + } + retPass := string(passphrase) + + if alias == "snapshots" || alias == "targets" { + userEnteredTargetsSnapshotsPass = true + targetsSnapshotsPass = retPass + } + + return retPass, false, nil } - - state, err := term.SaveState(0) - if err != nil { - return "", false, err - } - term.DisableEcho(0, state) - defer term.RestoreTerminal(0, state) - - stdin := bufio.NewReader(os.Stdin) - - fmt.Printf("Enter %s key passphrase: ", keyName) - passphrase, err := stdin.ReadBytes('\n') - fmt.Println() - if err != nil { - return "", false, err - } - passphrase = passphrase[0 : len(passphrase)-1] - - if !createNew { - return string(passphrase), false, nil; - } - - if len(passphrase) < 8 { - fmt.Println("Please use a password manager to generate and store a good random passphrase.") - return "", false, errors.New("Passphrase too short") - } - - fmt.Printf("Repeat %s key passphrase: ", keyName) - confirmation, err := stdin.ReadBytes('\n') - fmt.Println() - if err != nil { - return "", false, err - } - confirmation = confirmation[0 : len(confirmation)-1] - - if !bytes.Equal(passphrase, confirmation) { - return "", false, errors.New("The entered passphrases do not match") - } - return string(passphrase), false, nil } func getInsecureTransport() *http.Transport { diff --git a/cryptoservice/crypto_service.go b/cryptoservice/crypto_service.go index 899af7cecf..567e84e0ff 100644 --- a/cryptoservice/crypto_service.go +++ b/cryptoservice/crypto_service.go @@ -58,8 +58,8 @@ func (ccs *CryptoService) Create(role string, algorithm data.KeyAlgorithm) (data } logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role, privKey.ID()) - // Store the private key into our keystore with the name being: /GUN/ID.key - err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey) + // Store the private key into our keystore with the name being: /GUN/ID.key with an alias of role + err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), role, privKey) if err != nil { return nil, fmt.Errorf("failed to add key to filestore: %v", err) } diff --git a/keystoremanager/import_export.go b/keystoremanager/import_export.go index 13c3110438..6a056cc47d 100644 --- a/keystoremanager/import_export.go +++ b/keystoremanager/import_export.go @@ -34,6 +34,10 @@ var ( ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN") ) +const ( + aliasSuffix = ".alias" +) + // 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 { @@ -89,29 +93,17 @@ func moveKeys(oldKeyStore, newKeyStore *trustmanager.KeyFileStore) error { relKeyPath := strings.TrimPrefix(fullKeyPath, oldKeyStore.BaseDir()) relKeyPath = strings.TrimPrefix(relKeyPath, string(filepath.Separator)) - pemBytes, err := oldKeyStore.Get(relKeyPath) + pemBytes, err := oldKeyStore.GetKey(relKeyPath) if err != nil { return err } - block, _ := pem.Decode(pemBytes) - if block == nil { - return ErrNoValidPrivateKey + alias, err := oldKeyStore.GetKeyAlias(relKeyPath) + if err != nil { + return err } - if !x509.IsEncryptedPEMBlock(block) { - // Key is not encrypted. Parse it, and add it - // to the temporary store as an encrypted key. - privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") - if err != nil { - return err - } - err = newKeyStore.AddKey(relKeyPath, privKey) - } else { - // Encrypted key - pass it through without - // decrypting - err = newKeyStore.Add(relKeyPath, pemBytes) - } + err = newKeyStore.AddKey(relKeyPath, alias, pemBytes) if err != nil { return err @@ -207,11 +199,13 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader, passphrase string // store in an inconsistent state newRootKeys := make(map[string][]byte) newNonRootKeys := make(map[string]data.PrivateKey) + newNonRootKeyAliases := make(map[string]string) // Note that using / as a separator is okay here - the zip package // guarantees that the separator will be / rootKeysPrefix := privDir + "/" + rootKeysSubdir + "/" nonRootKeysPrefix := privDir + "/" + nonRootKeysSubdir + "/" + aliasSuffix := ".alias" // Iterate through the files in the archive. Don't add the keys for _, f := range zipReader.File { @@ -222,7 +216,7 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader, passphrase string return err } - pemBytes, err := ioutil.ReadAll(rc) + fileBytes, err := ioutil.ReadAll(rc) if err != nil { return nil } @@ -231,25 +225,29 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader, passphrase string // 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(pemBytes); err != nil { + if err = checkRootKeyIsEncrypted(fileBytes); err != nil { rc.Close() return err } // Root keys are preserved without decrypting keyName := strings.TrimPrefix(fNameTrimmed, rootKeysPrefix) - newRootKeys[keyName] = pemBytes + newRootKeys[keyName] = fileBytes } else if strings.HasPrefix(fNameTrimmed, nonRootKeysPrefix) { // Non-root keys need to be decrypted - key, err := trustmanager.ParsePEMPrivateKey(pemBytes, passphrase) + key, err := trustmanager.ParsePEMPrivateKey(fileBytes, passphrase) if err != nil { rc.Close() return err } keyName := strings.TrimPrefix(fNameTrimmed, nonRootKeysPrefix) newNonRootKeys[keyName] = key + } else if strings.HasSuffix(fNameTrimmed, aliasSuffix) { + // Aliases need to be recorded so they can be reintroduced in the new zip + fileName := strings.TrimSuffix(fNameTrimmed, aliasSuffix) + newNonRootKeyAliases[fileName] = string(fileBytes) } else { // This path inside the zip archive doesn't look like a - // root key or a non-root key. To avoid adding a file + // 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) @@ -261,13 +259,17 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader, passphrase string } for keyName, pemBytes := range newRootKeys { + if err := km.rootKeyStore.Add(keyName + "." + aliasSuffix, []byte("root")); err != nil { + return err + } if err := km.rootKeyStore.Add(keyName, pemBytes); err != nil { return err } } for keyName, privKey := range newNonRootKeys { - if err := km.nonRootKeyStore.AddKey(keyName, privKey); err != nil { + alias := newNonRootKeyAliases[keyName] + if err := km.nonRootKeyStore.AddKey(keyName, alias, privKey); err != nil { return err } } @@ -292,6 +294,11 @@ func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun stri return err } + alias, err := oldKeyStore.GetKeyAlias(relKeyPath) + if err != nil { + return err + } + block, _ := pem.Decode(pemBytes) if block == nil { return ErrNoValidPrivateKey @@ -307,7 +314,7 @@ func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun stri if err != nil { return err } - err = newKeyStore.AddKey(relKeyPath, privKey) + err = newKeyStore.AddKey(relKeyPath, alias, privKey) if err != nil { return err } diff --git a/keystoremanager/keystoremanager.go b/keystoremanager/keystoremanager.go index 0bd9fb6649..349d32fa9b 100644 --- a/keystoremanager/keystoremanager.go +++ b/keystoremanager/keystoremanager.go @@ -163,7 +163,7 @@ func (km *KeyStoreManager) GenRootKey(algorithm string) (string, error) { } // Changing the root - km.rootKeyStore.AddKey(privKey.ID(), privKey) + km.rootKeyStore.AddKey(privKey.ID(), "root", privKey) return privKey.ID(), nil } diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index 41c865d6d9..f376d6d271 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -9,14 +9,16 @@ import ( const ( keyExtension = "key" + aliasExtension = "alias" ) // KeyStore is a generic interface for private key storage type KeyStore interface { LimitedFileStore - AddKey(name string, privKey data.PrivateKey) error + AddKey(name, alias string, privKey data.PrivateKey) error GetKey(name string) (data.PrivateKey, error) + GetKeyAlias(name string) (string, error) ListKeys() []string RemoveKey(name string) error } @@ -25,7 +27,7 @@ type KeyStore interface { // for a given named key. If it should be treated as new passphrase (e.g. with // confirmation), createNew will be true. Attempts is passed in so that implementers // decide how many chances to give to a human, for example. -type PassphraseRetriever func(keyName string, createNew bool, attempts int) (passphrase string, giveup bool, err error) +type PassphraseRetriever func(keyId, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) // KeyFileStore persists and manages private keys on disk type KeyFileStore struct { @@ -51,8 +53,8 @@ func NewKeyFileStore(baseDir string, passphraseRetriever PassphraseRetriever) (* } // AddKey stores the contents of a PEM-encoded private key as a PEM block -func (s *KeyFileStore) AddKey(name string, privKey data.PrivateKey) error { - return addKey(s, s.PassphraseRetriever, name, privKey) +func (s *KeyFileStore) AddKey(name, alias string, privKey data.PrivateKey) error { + return addKey(s, s.PassphraseRetriever, name, alias, privKey) } // GetKey returns the PrivateKey given a KeyID @@ -60,6 +62,12 @@ func (s *KeyFileStore) GetKey(name string) (data.PrivateKey, error) { return getKey(s, s.PassphraseRetriever, name) } +// GetKeyAlias returns the PrivateKey given a KeyID +func (s *KeyFileStore) GetKeyAlias(name string) (string, error) { + return getKeyAlias(s, name) +} + + // ListKeys returns a list of unique PublicKeys present on the KeyFileStore. // There might be symlinks associating Certificate IDs to Public Keys, so this // method only returns the IDs that aren't symlinks @@ -69,7 +77,7 @@ func (s *KeyFileStore) ListKeys() []string { // RemoveKey removes the key from the keyfilestore func (s *KeyFileStore) RemoveKey(name string) error { - return remove(s, name) + return removeKey(s, name) } // NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory @@ -80,8 +88,8 @@ func NewKeyMemoryStore(passphraseRetriever PassphraseRetriever) *KeyMemoryStore } // AddKey stores the contents of a PEM-encoded private key as a PEM block -func (s *KeyMemoryStore) AddKey(name string, privKey data.PrivateKey) error { - return addKey(s, s.PassphraseRetriever, name, privKey) +func (s *KeyMemoryStore) AddKey(name, alias string, privKey data.PrivateKey) error { + return addKey(s, s.PassphraseRetriever, name, alias, privKey) } // GetKey returns the PrivateKey given a KeyID @@ -89,6 +97,12 @@ func (s *KeyMemoryStore) GetKey(name string) (data.PrivateKey, error) { return getKey(s, s.PassphraseRetriever, name) } +// GetKeyAlias returns the PrivateKey given a KeyID +func (s *KeyMemoryStore) GetKeyAlias(name string) (string, error) { + return getKeyAlias(s, name) +} + + // ListKeys returns a list of unique PublicKeys present on the KeyFileStore. // There might be symlinks associating Certificate IDs to Public Keys, so this // method only returns the IDs that aren't symlinks @@ -98,11 +112,11 @@ func (s *KeyMemoryStore) ListKeys() []string { // RemoveKey removes the key from the keystore func (s *KeyMemoryStore) RemoveKey(name string) error { - return remove(s, name) + return removeKey(s, name) } -func addKey(s LimitedFileStore, passphraseRetriever PassphraseRetriever, name string, privKey data.PrivateKey) error { +func addKey(s LimitedFileStore, passphraseRetriever PassphraseRetriever, name, alias string, privKey data.PrivateKey) error { pemPrivKey, err := KeyToPEM(privKey) if err != nil { return err @@ -111,8 +125,8 @@ func addKey(s LimitedFileStore, passphraseRetriever PassphraseRetriever, name st attempts := 0 passphrase := "" giveup := false - for (true) { - passphrase, giveup, err = passphraseRetriever(name, true, attempts) + for { + passphrase, giveup, err = passphraseRetriever(name, alias, true, attempts) if err != nil { attempts++ continue @@ -124,17 +138,29 @@ func addKey(s LimitedFileStore, passphraseRetriever PassphraseRetriever, name st } if passphrase != "" { - encryptedPrivKey, err := EncryptPrivateKey(privKey, passphrase) + pemPrivKey, err = EncryptPrivateKey(privKey, passphrase) if err != nil { return err } - - return s.Add(name, encryptedPrivKey) } + err = s.Add(name + "." + aliasExtension, []byte(alias)) + if err != nil { + return err + } return s.Add(name, pemPrivKey) } + +func getKeyAlias(s LimitedFileStore, name string) (string, error) { + keyAlias, err := s.Get(name + "." + aliasExtension) + if err != nil { + return "", err + } + + return string(keyAlias), nil +} + // GetKey returns the PrivateKey given a KeyID func getKey(s LimitedFileStore, passphraseRetriever PassphraseRetriever, name string) (data.PrivateKey, error) { keyBytes, err := s.Get(name) @@ -142,13 +168,19 @@ func getKey(s LimitedFileStore, passphraseRetriever PassphraseRetriever, name st return nil, err } + keyAlias, err := getKeyAlias(s, name) + if err != nil { + return nil, err + } + + // 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 attempts := 0 - for (true) { - passphrase, giveup, err := passphraseRetriever(name, false, attempts) + for { + 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, err @@ -179,6 +211,6 @@ func listKeys(s LimitedFileStore) []string { } // RemoveKey removes the key from the keyfilestore -func remove(s LimitedFileStore, name string) error { +func removeKey(s LimitedFileStore, name string) error { return s.Remove(name) }