mirror of https://github.com/docker/docs.git
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:
parent
ca98668cfc
commit
c35c1ea254
|
@ -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 {
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue