diff --git a/cmd/age/age.go b/cmd/age/age.go index 6ad43b4..8d44cf5 100644 --- a/cmd/age/age.go +++ b/cmd/age/age.go @@ -234,6 +234,16 @@ func main() { in = f } else { stdinInUse = true + if decryptFlag && term.IsTerminal(int(os.Stdin.Fd())) { + // If the input comes from a TTY, assume it's armored, and buffer up + // to the END line (or EOF/EOT) so that a password prompt or the + // output don't get in the way of typing the input. See Issue 364. + buf, err := bufferTerminalInput(in) + if err != nil { + errorf("failed to buffer terminal input: %v", err) + } + in = buf + } } if name := outFlag; name != "" && name != "-" { f := newLazyOpener(name) diff --git a/cmd/age/testdata/encrypted_keys.txt b/cmd/age/testdata/encrypted_keys.txt index 01a700e..8671079 100644 --- a/cmd/age/testdata/encrypted_keys.txt +++ b/cmd/age/testdata/encrypted_keys.txt @@ -1,4 +1,5 @@ -# TODO: age-encrypted private keys, multiple identities, -i ordering, -e -i +# TODO: age-encrypted private keys, multiple identities, -i ordering, -e -i, +# age file password prompt during encryption [windows] skip # no pty support @@ -48,6 +49,16 @@ pty terminal ! age -d -i key_rsa_other rsa_other.age stderr 'mismatched private and public SSH key' +# buffer armored ciphertext before prompting if stdin is the terminal +pty terminal +age -e -i key_ed25519 -a -o test.age input +exec cat test.age terminal # concatenated ciphertext + password +pty -stdin stdout +age -d -i key_ed25519 +ptyout 'Enter passphrase' +! stderr . +cmp stdout input + -- input -- test -- terminal -- diff --git a/cmd/age/testdata/terminal.txt b/cmd/age/testdata/terminal.txt index 1914984..cd2f5d4 100644 --- a/cmd/age/testdata/terminal.txt +++ b/cmd/age/testdata/terminal.txt @@ -37,6 +37,16 @@ pty terminal age -d test.age cmp stdout input +# buffer armored ciphertext before prompting if stdin is the terminal +pty terminal +age -p -a -o test.age input +exec cat test.age terminal # concatenated ciphertext + password +pty -stdin stdout +age -d +ptyout 'Enter passphrase' +! stderr . +cmp stdout input + -- input -- test -- terminal -- diff --git a/cmd/age/tui.go b/cmd/age/tui.go index f4f3e1f..47a1360 100644 --- a/cmd/age/tui.go +++ b/cmd/age/tui.go @@ -14,6 +14,7 @@ package main // No capitalized initials and no periods at the end. import ( + "bytes" "errors" "fmt" "io" @@ -21,6 +22,7 @@ import ( "os" "runtime" + "filippo.io/age/armor" "filippo.io/age/internal/plugin" "golang.org/x/term" ) @@ -205,3 +207,20 @@ var pluginTerminalUI = &plugin.ClientUI{ printf("waiting on %s plugin...", name) }, } + +func bufferTerminalInput(in io.Reader) (io.Reader, error) { + buf := &bytes.Buffer{} + if _, err := buf.ReadFrom(ReaderFunc(func(p []byte) (n int, err error) { + if bytes.Contains(buf.Bytes(), []byte(armor.Footer+"\n")) { + return 0, io.EOF + } + return in.Read(p) + })); err != nil { + return nil, err + } + return buf, nil +} + +type ReaderFunc func(p []byte) (n int, err error) + +func (f ReaderFunc) Read(p []byte) (n int, err error) { return f(p) }