// Package ask implements interactive password retrieval package ask import ( "errors" "io" "os" "dim13.org/signify/bhash" "golang.org/x/crypto/ssh/terminal" ) var ( ErrNoPassword = errors.New("please provide a password") ErrNoMatch = errors.New("passwords don't match") ) const ( promtPassphrase = "passphrase: " promtConfirmed = "confirm passphrase: " ) type Passphrase struct{} func (Passphrase) Derive(salt []byte, rounds int, length int) ([]byte, error) { pass, err := passphrase(os.Stdin) if err != nil { return nil, err } return bhash.Pbkdf([]byte(pass), salt, rounds, length) } type Confirmed struct{} func (Confirmed) Derive(salt []byte, rounds int, length int) ([]byte, error) { pass, err := confirmed(os.Stdin) if err != nil { return nil, err } return bhash.Pbkdf([]byte(pass), salt, rounds, length) } // confirmed asks for password twice func confirmed(fd *os.File) (string, error) { restore, err := makeRaw(fd) if err != nil { return "", err } defer restore() pass, err := readPassword(fd, promtPassphrase) if err != nil { return "", err } pass2, err := readPassword(fd, promtConfirmed) if err != nil { return "", err } if pass != pass2 { return "", ErrNoMatch } return pass, nil } // passphrase asks for passphrase once func passphrase(fd *os.File) (string, error) { restore, err := makeRaw(fd) if err != nil { return "", err } defer restore() return readPassword(fd, promtPassphrase) } func readPassword(rw io.ReadWriter, prompt string) (string, error) { pass, err := terminal.NewTerminal(rw, "").ReadPassword(prompt) if err != nil { return "", err } if len(pass) == 0 { return "", ErrNoPassword } return pass, nil } func makeRaw(f *os.File) (func(), error) { fd := int(f.Fd()) oldState, err := terminal.MakeRaw(fd) if err != nil { return nil, err } return func() { terminal.Restore(fd, oldState) }, nil }