Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passphrase fixups #906

Merged
merged 3 commits into from
Aug 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/notary/tuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,12 +403,12 @@ func (t *tufCommander) tufInit(cmd *cobra.Command, args []string) error {
if len(rootKeyList) < 1 {
cmd.Println("No root keys found. Generating a new root key...")
rootPublicKey, err := nRepo.CryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey)
rootKeyID = rootPublicKey.ID()
if err != nil {
return err
}
rootKeyID = rootPublicKey.ID()
} else {
// Choses the first root key available, which is initialization specific
// Chooses the first root key available, which is initialization specific
// but should return the HW one first.
rootKeyID = rootKeyList[0]
cmd.Printf("Root key found, using: %s\n", rootKeyID)
Expand Down
20 changes: 15 additions & 5 deletions passphrase/passphrase.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,22 @@ var (
// ErrTooManyAttempts is returned if the maximum number of passphrase
// entry attempts is reached.
ErrTooManyAttempts = errors.New("Too many attempts")

// ErrNoInput is returned if we do not have a valid input method for passphrases
ErrNoInput = errors.New("Please either use environment variables or STDIN with a terminal to provide key passphrases")
)

// PromptRetriever returns a new Retriever which will provide a prompt on stdin
// and stdout to retrieve a passphrase. The passphrase will be cached such that
// and stdout to retrieve a passphrase. stdin will be checked if it is a terminal,
// else the PromptRetriever will error when attempting to retrieve a passphrase.
// Upon successful passphrase retrievals, the passphrase will be cached such that
// subsequent prompts will produce the same passphrase.
func PromptRetriever() notary.PassRetriever {
if !term.IsTerminal(os.Stdin.Fd()) {
return func(string, string, bool, int) (string, bool, error) {
return "", false, ErrNoInput
}
}
return PromptRetrieverWithInOut(os.Stdin, os.Stdout, nil)
}

Expand Down Expand Up @@ -85,13 +95,13 @@ func (br *boundRetriever) requestPassphrase(keyName, alias string, createNew boo

// If typing on the terminal, we do not want the terminal to echo the
// password that is typed (so it doesn't display)
if term.IsTerminal(0) {
state, err := term.SaveState(0)
if term.IsTerminal(os.Stdin.Fd()) {
state, err := term.SaveState(os.Stdin.Fd())
if err != nil {
return "", false, err
}
term.DisableEcho(0, state)
defer term.RestoreTerminal(0, state)
term.DisableEcho(os.Stdin.Fd(), state)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is a good reason for us to switch to crypto/terminal in the future. Better API for retrieving passwords from the terminal.

defer term.RestoreTerminal(os.Stdin.Fd(), state)
}

indexOfLastSeparator := strings.LastIndex(keyName, string(filepath.Separator))
Expand Down
40 changes: 40 additions & 0 deletions passphrase/passphrase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,38 @@ func TestGetPassphraseForUsingDelegationKey(t *testing.T) {
}
}

// PromptRetrieverWithInOut prompts for passwords up to 10 times when creating
func TestGetPassphraseLimitsShortPassphrases(t *testing.T) {
var in bytes.Buffer
var out bytes.Buffer

retriever := PromptRetrieverWithInOut(&in, &out, nil)

repeatedShortPass := strings.Repeat("a\n", 22)
_, err := in.WriteString(repeatedShortPass)
require.NoError(t, err)

_, _, err = retriever("randomRepo", "targets/randomRole", true, 0)
require.Error(t, err)
require.IsType(t, ErrTooManyAttempts, err)
}

// PromptRetrieverWithInOut prompts for passwords up to 10 times when creating
func TestGetPassphraseLimitsMismatchingPassphrases(t *testing.T) {
var in bytes.Buffer
var out bytes.Buffer

retriever := PromptRetrieverWithInOut(&in, &out, nil)

repeatedShortPass := strings.Repeat("password\nmismatchingpass\n", 11)
_, err := in.WriteString(repeatedShortPass)
require.NoError(t, err)

_, _, err = retriever("randomRepo", "targets/randomRole", true, 0)
require.Error(t, err)
require.IsType(t, ErrTooManyAttempts, err)
}

// PromptRetrieverWithInOut prompts for creating delegations passwords if needed
func TestGetPassphraseForCreatingDelegationKey(t *testing.T) {
var in bytes.Buffer
Expand Down Expand Up @@ -113,3 +145,11 @@ func TestRolePromptingAndCaching(t *testing.T) {
require.NoError(t, err)
require.Contains(t, string(text), "Enter passphrase for targets/delegation/new key with ID 0123456 (repo):")
}

// TestPromptRetrieverNeedsTerminal checks that PromptRetriever errors when not run with a terminal stdin
func TestPromptRetrieverNeedsTerminal(t *testing.T) {
prompt := PromptRetriever()
_, _, err := prompt("repo/0123456789abcdef", "targets/delegation/new", false, 0)
require.Error(t, err)
require.IsType(t, ErrNoInput, err)
}
5 changes: 2 additions & 3 deletions trustmanager/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,12 @@ func (s *GenericKeyStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
name := filepath.Join(keyInfo.Gun, privKey.ID())
for attempts := 0; ; attempts++ {
chosenPassphrase, giveup, err = s.PassRetriever(name, keyInfo.Role, true, attempts)
if err != nil {
continue
if err == nil {
break
}
if giveup || attempts > 10 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: wondering if we should decrease this number to 3 or some such? That would give the user 5 tries. (currently it fails out after 12 tries - I was wondering whether that's too many)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. 10 is feeling like too many. Also would be good to move the exact number to a const.

return ErrAttemptsExceeded{}
}
break
}

if chosenPassphrase != "" {
Expand Down