keystore aliasing, take 1

Signed-off-by: Nathan McCauley <nathan.mccauley@docker.com>
This commit is contained in:
Nathan McCauley 2015-07-18 02:01:55 -07:00
parent 7530774101
commit 5df1eb21f3
7 changed files with 173 additions and 96 deletions

View File

@ -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;

View File

@ -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)
}

View File

@ -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,9 +272,22 @@ 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 := ""
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")
}
@ -281,7 +301,12 @@ func passphraseRetriever(keyName string, createNew bool, numAttempts int) (strin
stdin := bufio.NewReader(os.Stdin)
fmt.Printf("Enter %s key passphrase: ", keyName)
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 {
@ -290,6 +315,11 @@ func passphraseRetriever(keyName string, createNew bool, numAttempts int) (strin
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;
}
@ -298,7 +328,7 @@ func passphraseRetriever(keyName string, createNew bool, numAttempts int) (strin
return "", false, errors.New("Passphrase too short")
}
fmt.Printf("Repeat %s key passphrase: ", keyName)
fmt.Printf("Repeat passphrase for new %s key with id %s:: ", alias, keyID)
confirmation, err := stdin.ReadBytes('\n')
fmt.Println()
if err != nil {
@ -309,7 +339,15 @@ func passphraseRetriever(keyName string, createNew bool, numAttempts int) (strin
if !bytes.Equal(passphrase, confirmation) {
return "", false, errors.New("The entered passphrases do not match")
}
return string(passphrase), false, nil
retPass := string(passphrase)
if alias == "snapshots" || alias == "targets" {
userEnteredTargetsSnapshotsPass = true
targetsSnapshotsPass = retPass
}
return retPass, false, nil
}
}
func getInsecureTransport() *http.Transport {

View File

@ -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)
}

View File

@ -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
}
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, "")
alias, err := oldKeyStore.GetKeyAlias(relKeyPath)
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
}

View File

@ -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
}

View File

@ -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)
}