package main import ( "bufio" "crypto/rand" "fmt" "io" "math/big" "os" "strings" "golang.org/x/term" ) const ( alnum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" symbols = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" ) // readSecret reads a new secret for name. With echo (or when stdin is not a // terminal, e.g. piped input) it reads a single line; otherwise it prompts // twice without echo and checks the two entries match. func readSecret(name string, echo bool) ([]byte, error) { if echo || !term.IsTerminal(int(os.Stdin.Fd())) { line, err := bufio.NewReader(os.Stdin).ReadString('\n') if err != nil && err != io.EOF { return nil, err } return []byte(strings.TrimRight(line, "\r\n")), nil } fmt.Fprintf(os.Stderr, "Enter password for %s: ", name) p1, err := term.ReadPassword(int(os.Stdin.Fd())) fmt.Fprintln(os.Stderr) if err != nil { return nil, err } fmt.Fprintf(os.Stderr, "Retype password for %s: ", name) p2, err := term.ReadPassword(int(os.Stdin.Fd())) fmt.Fprintln(os.Stderr) if err != nil { return nil, err } if string(p1) != string(p2) { return nil, errf("the entered passwords do not match") } return p1, nil } // genPassword returns a cryptographically random password. func genPassword(length int, withSymbols bool) (string, error) { if length <= 0 { length = 25 } charset := alnum if withSymbols { charset += symbols } buf := make([]byte, length) max := big.NewInt(int64(len(charset))) for i := range buf { n, err := rand.Int(rand.Reader, max) if err != nil { return "", err } buf[i] = charset[n.Int64()] } return string(buf), nil } func defaultLength() int { return atoiDefault(os.Getenv("PASSWORD_STORE_GENERATED_LENGTH"), 25) }