cmd/age: use CONIN$/CONOUT$ on Windows for password prompts

Fixes #128
Closes #274

Co-authored-by: codesoap <codesoap@mailbox.org>
This commit is contained in:
Filippo Valsorda
2021-06-02 10:58:45 +02:00
parent cde103daae
commit fa5b575ceb
3 changed files with 38 additions and 23 deletions

View File

@@ -225,8 +225,7 @@ func main() {
}
func passphrasePromptForEncryption() (string, error) {
fmt.Fprintf(os.Stderr, "Enter passphrase (leave empty to autogenerate a secure one): ")
pass, err := readPassphrase()
pass, err := readPassphrase("Enter passphrase (leave empty to autogenerate a secure one):")
if err != nil {
return "", fmt.Errorf("could not read passphrase: %v", err)
}
@@ -239,8 +238,7 @@ func passphrasePromptForEncryption() (string, error) {
p = strings.Join(words, "-")
fmt.Fprintf(os.Stderr, "Using the autogenerated passphrase %q.\n", p)
} else {
fmt.Fprintf(os.Stderr, "Confirm passphrase: ")
confirm, err := readPassphrase()
confirm, err := readPassphrase("Confirm passphrase:")
if err != nil {
return "", fmt.Errorf("could not read passphrase: %v", err)
}
@@ -345,8 +343,7 @@ func decrypt(keys []string, in io.Reader, out io.Writer) {
}
func passphrasePrompt() (string, error) {
fmt.Fprintf(os.Stderr, "Enter passphrase: ")
pass, err := readPassphrase()
pass, err := readPassphrase("Enter passphrase:")
if err != nil {
return "", fmt.Errorf("could not read passphrase: %v", err)
}

View File

@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"os"
"runtime"
"filippo.io/age"
"golang.org/x/term"
@@ -45,23 +46,40 @@ func (i *LazyScryptIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, err
return fileKey, err
}
// readPassphrase reads a passphrase from the terminal. If stdin is not
// connected to a terminal, it tries /dev/tty and fails if that's not available.
// It does not read from a non-terminal stdin, so it does not check stdinInUse.
func readPassphrase() ([]byte, error) {
fd := int(os.Stdin.Fd())
if !term.IsTerminal(fd) {
tty, err := os.Open("/dev/tty")
// readPassphrase reads a passphrase from the terminal. It does not read from a
// non-terminal stdin, so it does not check stdinInUse.
func readPassphrase(prompt string) ([]byte, error) {
var in, out *os.File
if runtime.GOOS == "windows" {
var err error
in, err = os.OpenFile("CONIN$", os.O_RDWR, 0)
if err != nil {
return nil, fmt.Errorf("standard input is not a terminal, and opening /dev/tty failed: %v", err)
return nil, err
}
defer in.Close()
out, err = os.OpenFile("CONOUT$", os.O_WRONLY, 0)
if err != nil {
return nil, err
}
defer out.Close()
} else if _, err := os.Stat("/dev/tty"); err == nil {
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
defer tty.Close()
fd = int(tty.Fd())
in, out = tty, tty
} else {
if !term.IsTerminal(int(os.Stdin.Fd())) {
return nil, fmt.Errorf("standard input is not a terminal, and /dev/tty is not available: %v", err)
}
in, out = os.Stdin, os.Stderr
}
defer fmt.Fprintf(os.Stderr, "\n")
p, err := term.ReadPassword(fd)
if err != nil {
return nil, err
}
return p, nil
fmt.Fprintf(out, "%s ", prompt)
// Use CRLF to work around an apparent bug in WSL2's handling of CONOUT$.
// Only when running a Windows binary from WSL2, the cursor would not go
// back to the start of the line with a simple LF. Honestly, it's impressive
// CONIN$ and CONOUT$ even work at all inside WSL2.
defer fmt.Fprintf(out, "\r\n")
return term.ReadPassword(int(in.Fd()))
}

View File

@@ -168,8 +168,8 @@ func parseSSHIdentity(name string, pemBytes []byte) ([]age.Identity, error) {
}
}
passphrasePrompt := func() ([]byte, error) {
fmt.Fprintf(os.Stderr, "Enter passphrase for %q: ", name)
pass, err := readPassphrase()
prompt := fmt.Sprintf("Enter passphrase for %q:", name)
pass, err := readPassphrase(prompt)
if err != nil {
return nil, fmt.Errorf("could not read passphrase for %q: %v", name, err)
}