Move passphrase logic to its own package

The logic to retrieve passphrase is generic and may be used by directly by clients.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
Derek McGowan 2015-07-20 13:02:05 -07:00
parent ca98668cfc
commit c35c1ea254
7 changed files with 129 additions and 111 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/docker/notary/client/changelist" "github.com/docker/notary/client/changelist"
"github.com/docker/notary/cryptoservice" "github.com/docker/notary/cryptoservice"
"github.com/docker/notary/keystoremanager" "github.com/docker/notary/keystoremanager"
"github.com/docker/notary/pkg/passphrase"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/endophage/gotuf" "github.com/endophage/gotuf"
tufclient "github.com/endophage/gotuf/client" tufclient "github.com/endophage/gotuf/client"
@ -84,7 +85,7 @@ func NewTarget(targetName string, targetPath string) (*Target, error) {
// It takes the base directory under where all the trust files will be stored // It takes the base directory under where all the trust files will be stored
// (usually ~/.docker/trust/). // (usually ~/.docker/trust/).
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
passphraseRetriever trustmanager.PassphraseRetriever) (*NotaryRepository, error) { passphraseRetriever passphrase.Retriever) (*NotaryRepository, error) {
keyStoreManager, err := keystoremanager.NewKeyStoreManager(baseDir, passphraseRetriever) keyStoreManager, err := keystoremanager.NewKeyStoreManager(baseDir, passphraseRetriever)
if err != nil { if err != nil {

View File

@ -1,31 +1,27 @@
package main package main
import ( import (
"bufio"
"crypto/sha256" "crypto/sha256"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/term"
notaryclient "github.com/docker/notary/client" notaryclient "github.com/docker/notary/client"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/pkg/passphrase"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"strings"
) )
// FIXME: This should not be hardcoded // FIXME: This should not be hardcoded
const hardcodedBaseURL = "https://notary-server:4443" const hardcodedBaseURL = "https://notary-server:4443"
var retriever trustmanager.PassphraseRetriever var retriever passphrase.Retriever
func init() { func init() {
retriever = getNotaryPassphraseRetriever() retriever = passphrase.PromptRetriever()
} }
var remoteTrustServer string var remoteTrustServer string
@ -272,93 +268,6 @@ func verify(cmd *cobra.Command, args []string) {
return return
} }
func getNotaryPassphraseRetriever() trustmanager.PassphraseRetriever {
userEnteredTargetsSnapshotsPass := false
targetsSnapshotsPass := ""
userEnteredRootsPass := false
rootsPass := ""
return func(keyID string, alias string, createNew bool, numAttempts int) (string, bool, error) {
// First, check if we have a password cached for this alias.
if numAttempts == 0 {
if userEnteredTargetsSnapshotsPass && (alias == "snapshot" || alias == "targets") {
return targetsSnapshotsPass, false, nil
}
if userEnteredRootsPass && (alias == "root") {
return rootsPass, 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
}
retPass := strings.TrimSpace(string(passphrase))
if !createNew {
if alias == "snapshot" || alias == "targets" {
userEnteredTargetsSnapshotsPass = true
targetsSnapshotsPass = retPass
}
if alias == "root" {
userEnteredRootsPass = true
rootsPass = retPass
}
return retPass, false, nil
}
if len(retPass) < 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
}
confirmationStr := strings.TrimSpace(string(confirmation))
if retPass != confirmationStr {
return "", false, errors.New("The entered passphrases do not match")
}
if alias == "snapshot" || alias == "targets" {
userEnteredTargetsSnapshotsPass = true
targetsSnapshotsPass = retPass
}
if alias == "root" {
userEnteredRootsPass = true
rootsPass = retPass
}
return retPass, false, nil
}
}
func getInsecureTransport() *http.Transport { func getInsecureTransport() *http.Transport {
return &http.Transport{ return &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{

View File

@ -12,6 +12,7 @@ import (
"strings" "strings"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/notary/pkg/passphrase"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
) )
@ -135,7 +136,7 @@ func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileSt
// ExportAllKeys exports all keys to an io.Writer in zip format. // ExportAllKeys exports all keys to an io.Writer in zip format.
// newPassphraseRetriever will be used to obtain passphrases to use to encrypt the existing keys. // newPassphraseRetriever will be used to obtain passphrases to use to encrypt the existing keys.
func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, newPassphraseRetriever trustmanager.PassphraseRetriever) error { func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, newPassphraseRetriever passphrase.Retriever) error {
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-") tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)
@ -280,7 +281,7 @@ func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun stri
// ExportKeysByGUN exports all keys associated with a specified GUN to an // ExportKeysByGUN exports all keys associated with a specified GUN to an
// io.Writer in zip format. passphraseRetriever is used to select new passphrases to use to // io.Writer in zip format. passphraseRetriever is used to select new passphrases to use to
// encrypt the keys. // encrypt the keys.
func (km *KeyStoreManager) ExportKeysByGUN(dest io.Writer, gun string, passphraseRetriever trustmanager.PassphraseRetriever) error { func (km *KeyStoreManager) ExportKeysByGUN(dest io.Writer, gun string, passphraseRetriever passphrase.Retriever) error {
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-") tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)

View File

@ -11,6 +11,7 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/notary/cryptoservice" "github.com/docker/notary/cryptoservice"
"github.com/docker/notary/pkg/passphrase"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/endophage/gotuf/data" "github.com/endophage/gotuf/data"
"github.com/endophage/gotuf/signed" "github.com/endophage/gotuf/signed"
@ -60,7 +61,7 @@ func (err ErrRootRotationFail) Error() string {
// NewKeyStoreManager returns an initialized KeyStoreManager, or an error // NewKeyStoreManager returns an initialized KeyStoreManager, or an error
// if it fails to create the KeyFileStores or load certificates // if it fails to create the KeyFileStores or load certificates
func NewKeyStoreManager(baseDir string, passphraseRetriever trustmanager.PassphraseRetriever) (*KeyStoreManager, error) { func NewKeyStoreManager(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyStoreManager, error) {
nonRootKeysPath := filepath.Join(baseDir, privDir, nonRootKeysSubdir) nonRootKeysPath := filepath.Join(baseDir, privDir, nonRootKeysSubdir)
nonRootKeyStore, err := trustmanager.NewKeyFileStore(nonRootKeysPath, passphraseRetriever) nonRootKeyStore, err := trustmanager.NewKeyFileStore(nonRootKeysPath, passphraseRetriever)
if err != nil { if err != nil {

View File

@ -0,0 +1,109 @@
// Package passphrase is a utility function for managing passphrase
// for TUF and Notary keys.
package passphrase
import (
"bufio"
"errors"
"fmt"
"os"
"strings"
"github.com/docker/docker/pkg/term"
)
// Retriever is a callback function that should retrieve a passphrase
// 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 Retriever func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error)
// PromptRetriever returns a new Retriever which will provide a terminal prompt
// to retrieve a passphrase. The passphrase will be cached such that subsequent
// prompts will produce the same passphrase.
func PromptRetriever() Retriever {
userEnteredTargetsSnapshotsPass := false
targetsSnapshotsPass := ""
userEnteredRootsPass := false
rootsPass := ""
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
// First, check if we have a password cached for this alias.
if numAttempts == 0 {
if userEnteredTargetsSnapshotsPass && (alias == "snapshot" || alias == "targets") {
return targetsSnapshotsPass, false, nil
}
if userEnteredRootsPass && (alias == "root") {
return rootsPass, 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, keyName)
} else {
fmt.Printf("Enter key passphrase for %s key with id %s: ", alias, keyName)
}
passphrase, err := stdin.ReadBytes('\n')
fmt.Println()
if err != nil {
return "", false, err
}
retPass := strings.TrimSpace(string(passphrase))
if !createNew {
if alias == "snapshot" || alias == "targets" {
userEnteredTargetsSnapshotsPass = true
targetsSnapshotsPass = retPass
}
if alias == "root" {
userEnteredRootsPass = true
rootsPass = retPass
}
return retPass, false, nil
}
if len(retPass) < 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, keyName)
confirmation, err := stdin.ReadBytes('\n')
fmt.Println()
if err != nil {
return "", false, err
}
confirmationStr := strings.TrimSpace(string(confirmation))
if retPass != confirmationStr {
return "", false, errors.New("The entered passphrases do not match")
}
if alias == "snapshot" || alias == "targets" {
userEnteredTargetsSnapshotsPass = true
targetsSnapshotsPass = retPass
}
if alias == "root" {
userEnteredRootsPass = true
rootsPass = retPass
}
return retPass, false, nil
}
}

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/docker/notary/cryptoservice" "github.com/docker/notary/cryptoservice"
"github.com/docker/notary/pkg/passphrase"
"github.com/docker/notary/signer" "github.com/docker/notary/signer"
"github.com/docker/notary/signer/api" "github.com/docker/notary/signer/api"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
@ -24,7 +25,7 @@ var (
sClient pb.SignerClient sClient pb.SignerClient
grpcServer *grpc.Server grpcServer *grpc.Server
void *pb.Void void *pb.Void
pr trustmanager.PassphraseRetriever pr passphrase.Retriever
) )
func init() { func init() {

View File

@ -6,6 +6,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/docker/notary/pkg/passphrase"
"github.com/endophage/gotuf/data" "github.com/endophage/gotuf/data"
) )
@ -24,27 +26,21 @@ type KeyStore interface {
RemoveKey(name string) error RemoveKey(name string) error
} }
// PassphraseRetriever is a callback function that should retrieve a passphrase
// 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(keyId, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error)
// KeyFileStore persists and manages private keys on disk // KeyFileStore persists and manages private keys on disk
type KeyFileStore struct { type KeyFileStore struct {
SimpleFileStore SimpleFileStore
PassphraseRetriever PassphraseRetriever passphrase.Retriever
} }
// KeyMemoryStore manages private keys in memory // KeyMemoryStore manages private keys in memory
type KeyMemoryStore struct { type KeyMemoryStore struct {
MemoryFileStore MemoryFileStore
PassphraseRetriever PassphraseRetriever passphrase.Retriever
} }
// NewKeyFileStore returns a new KeyFileStore creating a private directory to // NewKeyFileStore returns a new KeyFileStore creating a private directory to
// hold the keys. // hold the keys.
func NewKeyFileStore(baseDir string, passphraseRetriever PassphraseRetriever) (*KeyFileStore, error) { func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyFileStore, error) {
fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension) fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension)
if err != nil { if err != nil {
return nil, err return nil, err
@ -81,7 +77,7 @@ func (s *KeyFileStore) RemoveKey(name string) error {
} }
// NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory // NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory
func NewKeyMemoryStore(passphraseRetriever PassphraseRetriever) *KeyMemoryStore { func NewKeyMemoryStore(passphraseRetriever passphrase.Retriever) *KeyMemoryStore {
memStore := NewMemoryFileStore() memStore := NewMemoryFileStore()
return &KeyMemoryStore{*memStore, passphraseRetriever} return &KeyMemoryStore{*memStore, passphraseRetriever}
@ -114,7 +110,7 @@ func (s *KeyMemoryStore) RemoveKey(name string) error {
return removeKey(s, name) return removeKey(s, name)
} }
func addKey(s LimitedFileStore, passphraseRetriever PassphraseRetriever, name, alias string, privKey data.PrivateKey) error { func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, name, alias string, privKey data.PrivateKey) error {
pemPrivKey, err := KeyToPEM(privKey) pemPrivKey, err := KeyToPEM(privKey)
if err != nil { if err != nil {
return err return err
@ -166,7 +162,7 @@ func getKeyAlias(s LimitedFileStore, keyID string) (string, error) {
} }
// GetKey returns the PrivateKey given a KeyID // GetKey returns the PrivateKey given a KeyID
func getKey(s LimitedFileStore, passphraseRetriever PassphraseRetriever, name string) (data.PrivateKey, error) { func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, name string) (data.PrivateKey, error) {
keyAlias, err := getKeyAlias(s, name) keyAlias, err := getKeyAlias(s, name)
if err != nil { if err != nil {
return nil, err return nil, err