This commit is contained in:
Mislav Marohnić 2016-08-20 21:18:33 +02:00
Родитель 36fa7fd902 f355b1442e
Коммит 33bd0d04f7
13 изменённых файлов: 420 добавлений и 113 удалений

Просмотреть файл

@ -148,7 +148,8 @@ func (c *Config) PromptForPassword(host, user string) (pass string) {
ui.Printf("%s password for %s (never stored): ", host, user)
if ui.IsTerminal(os.Stdout) {
pass = string(gopass.GetPasswd())
passBytes, _ := gopass.GetPasswd()
pass = string(passBytes)
} else {
pass = c.scanLine()
}

2
vendor/github.com/howeyc/gopass/LICENSE.txt сгенерированный поставляемый
Просмотреть файл

@ -1,3 +1,5 @@
ISC License
Copyright (c) 2012 Chris Howey
Permission to use, copy, modify, and distribute this software for any

14
vendor/github.com/howeyc/gopass/README.md сгенерированный поставляемый
Просмотреть файл

@ -1,6 +1,6 @@
# getpasswd in Go
# getpasswd in Go [![GoDoc](https://godoc.org/github.com/howeyc/gopass?status.svg)](https://godoc.org/github.com/howeyc/gopass) [![Build Status](https://secure.travis-ci.org/howeyc/gopass.png?branch=master)](http://travis-ci.org/howeyc/gopass)
Retrieve password from user terminal input without echo
Retrieve password from user terminal or piped input without echo.
Verified on BSD, Linux, and Windows.
@ -13,8 +13,14 @@ import "github.com/howeyc/gopass"
func main() {
fmt.Printf("Password: ")
pass := gopass.GetPasswd() // Silent, for *'s use gopass.GetPasswdMasked()
// Do something with pass
// Silent. For printing *'s use gopass.GetPasswdMasked()
pass, err := gopass.GetPasswd()
if err != nil {
// Handle gopass.ErrInterrupted or getch() read error
}
// Do something with pass
}
```

29
vendor/github.com/howeyc/gopass/bsd.go сгенерированный поставляемый
Просмотреть файл

@ -1,29 +0,0 @@
// +build freebsd openbsd netbsd
package gopass
/*
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
int getch() {
int ch;
struct termios t_old, t_new;
tcgetattr(STDIN_FILENO, &t_old);
t_new = t_old;
t_new.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &t_new);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &t_old);
return ch;
}
*/
import "C"
func getch() byte {
return byte(C.getch())
}

23
vendor/github.com/howeyc/gopass/nix.go сгенерированный поставляемый
Просмотреть файл

@ -1,23 +0,0 @@
// +build linux darwin
package gopass
import (
"syscall"
"golang.org/x/crypto/ssh/terminal"
)
func getch() byte {
if oldState, err := terminal.MakeRaw(0); err != nil {
panic(err)
} else {
defer terminal.Restore(0, oldState)
}
var buf [1]byte
if n, err := syscall.Read(0, buf[:]); n == 0 || err != nil {
panic(err)
}
return buf[0]
}

67
vendor/github.com/howeyc/gopass/pass.go сгенерированный поставляемый
Просмотреть файл

@ -1,44 +1,93 @@
package gopass
import (
"errors"
"fmt"
"io"
"os"
"golang.org/x/crypto/ssh/terminal"
)
var defaultGetCh = func() (byte, error) {
buf := make([]byte, 1)
if n, err := os.Stdin.Read(buf); n == 0 || err != nil {
if err != nil {
return 0, err
}
return 0, io.EOF
}
return buf[0], nil
}
var (
maxLength = 512
ErrInterrupted = errors.New("interrupted")
ErrMaxLengthExceeded = fmt.Errorf("maximum byte limit (%v) exceeded", maxLength)
// Provide variable so that tests can provide a mock implementation.
getch = defaultGetCh
)
// getPasswd returns the input read from terminal.
// If masked is true, typing will be matched by asterisks on the screen.
// Otherwise, typing will echo nothing.
func getPasswd(masked bool) []byte {
func getPasswd(masked bool) ([]byte, error) {
var err error
var pass, bs, mask []byte
if masked {
bs = []byte("\b \b")
mask = []byte("*")
}
for {
if v := getch(); v == 127 || v == 8 {
if terminal.IsTerminal(int(os.Stdin.Fd())) {
if oldState, err := terminal.MakeRaw(int(os.Stdin.Fd())); err != nil {
return pass, err
} else {
defer terminal.Restore(int(os.Stdin.Fd()), oldState)
}
}
// Track total bytes read, not just bytes in the password. This ensures any
// errors that might flood the console with nil or -1 bytes infinitely are
// capped.
var counter int
for counter = 0; counter <= maxLength; counter++ {
if v, e := getch(); e != nil {
err = e
break
} else if v == 127 || v == 8 {
if l := len(pass); l > 0 {
pass = pass[:l-1]
os.Stdout.Write(bs)
fmt.Print(string(bs))
}
} else if v == 13 || v == 10 {
break
} else if v == 3 {
err = ErrInterrupted
break
} else if v != 0 {
pass = append(pass, v)
os.Stdout.Write(mask)
fmt.Print(string(mask))
}
}
println()
return pass
if counter > maxLength {
err = ErrMaxLengthExceeded
}
fmt.Println()
return pass, err
}
// GetPasswd returns the password read from the terminal without echoing input.
// The returned byte array does not include end-of-line characters.
func GetPasswd() []byte {
func GetPasswd() ([]byte, error) {
return getPasswd(false)
}
// GetPasswdMasked returns the password read from the terminal, echoing asterisks.
// The returned byte array does not include end-of-line characters.
func GetPasswdMasked() []byte {
func GetPasswdMasked() ([]byte, error) {
return getPasswd(true)
}

228
vendor/github.com/howeyc/gopass/pass_test.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,228 @@
package gopass
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"testing"
"time"
)
// TestGetPasswd tests the password creation and output based on a byte buffer
// as input to mock the underlying getch() methods.
func TestGetPasswd(t *testing.T) {
type testData struct {
input []byte
// Due to how backspaces are written, it is easier to manually write
// each expected output for the masked cases.
masked string
password string
byesLeft int
reason string
}
ds := []testData{
testData{[]byte("abc\n"), "***\n", "abc", 0, "Password parsing should stop at \\n"},
testData{[]byte("abc\r"), "***\n", "abc", 0, "Password parsing should stop at \\r"},
testData{[]byte("a\nbc\n"), "*\n", "a", 3, "Password parsing should stop at \\n"},
testData{[]byte("*!]|\n"), "****\n", "*!]|", 0, "Special characters shouldn't affect the password."},
testData{[]byte("abc\r\n"), "***\n", "abc", 1,
"Password parsing should stop at \\r; Windows LINE_MODE should be unset so \\r is not converted to \\r\\n."},
testData{[]byte{'a', 'b', 'c', 8, '\n'}, "***\b \b\n", "ab", 0, "Backspace byte should remove the last read byte."},
testData{[]byte{'a', 'b', 127, 'c', '\n'}, "**\b \b*\n", "ac", 0, "Delete byte should remove the last read byte."},
testData{[]byte{'a', 'b', 127, 'c', 8, 127, '\n'}, "**\b \b*\b \b\b \b\n", "", 0, "Successive deletes continue to delete."},
testData{[]byte{8, 8, 8, '\n'}, "\n", "", 0, "Deletes before characters are noops."},
testData{[]byte{8, 8, 8, 'a', 'b', 'c', '\n'}, "***\n", "abc", 0, "Deletes before characters are noops."},
testData{[]byte{'a', 'b', 0, 'c', '\n'}, "***\n", "abc", 0,
"Nil byte should be ignored due; may get unintended nil bytes from syscalls on Windows."},
}
// Redirecting output for tests as they print to os.Stdout but we want to
// capture and test the output.
origStdOut := os.Stdout
for _, masked := range []bool{true, false} {
for _, d := range ds {
pipeBytesToStdin(d.input)
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err.Error())
}
os.Stdout = w
result, err := getPasswd(masked)
os.Stdout = origStdOut
if err != nil {
t.Errorf("Error getting password:", err.Error())
}
leftOnBuffer := flushStdin()
// Test output (masked and unmasked). Delete/backspace actually
// deletes, overwrites and deletes again. As a result, we need to
// remove those from the pipe afterwards to mimic the console's
// interpretation of those bytes.
w.Close()
output, err := ioutil.ReadAll(r)
if err != nil {
t.Fatal(err.Error())
}
var expectedOutput []byte
if masked {
expectedOutput = []byte(d.masked)
} else {
expectedOutput = []byte("\n")
}
if bytes.Compare(expectedOutput, output) != 0 {
t.Errorf("Expected output to equal %v (%q) but got %v (%q) instead when masked=%v. %s", expectedOutput, string(expectedOutput), output, string(output), masked, d.reason)
}
if string(result) != d.password {
t.Errorf("Expected %q but got %q instead when masked=%v. %s", d.password, result, masked, d.reason)
}
if leftOnBuffer != d.byesLeft {
t.Errorf("Expected %v bytes left on buffer but instead got %v when masked=%v. %s", d.byesLeft, leftOnBuffer, masked, d.reason)
}
}
}
}
// TestPipe ensures we get our expected pipe behavior.
func TestPipe(t *testing.T) {
type testData struct {
input string
password string
expError error
}
ds := []testData{
testData{"abc", "abc", io.EOF},
testData{"abc\n", "abc", nil},
testData{"abc\r", "abc", nil},
testData{"abc\r\n", "abc", nil},
}
for _, d := range ds {
_, err := pipeToStdin(d.input)
if err != nil {
t.Log("Error writing input to stdin:", err)
t.FailNow()
}
pass, err := GetPasswd()
if string(pass) != d.password {
t.Errorf("Expected %q but got %q instead.", d.password, string(pass))
}
if err != d.expError {
t.Errorf("Expected %v but got %q instead.", d.expError, err)
}
}
}
// flushStdin reads from stdin for .5 seconds to ensure no bytes are left on
// the buffer. Returns the number of bytes read.
func flushStdin() int {
ch := make(chan byte)
go func(ch chan byte) {
reader := bufio.NewReader(os.Stdin)
for {
b, err := reader.ReadByte()
if err != nil { // Maybe log non io.EOF errors, if you want
close(ch)
return
}
ch <- b
}
close(ch)
}(ch)
numBytes := 0
for {
select {
case _, ok := <-ch:
if !ok {
return numBytes
}
numBytes++
case <-time.After(500 * time.Millisecond):
return numBytes
}
}
return numBytes
}
// pipeToStdin pipes the given string onto os.Stdin by replacing it with an
// os.Pipe. The write end of the pipe is closed so that EOF is read after the
// final byte.
func pipeToStdin(s string) (int, error) {
pipeReader, pipeWriter, err := os.Pipe()
if err != nil {
fmt.Println("Error getting os pipes:", err)
os.Exit(1)
}
os.Stdin = pipeReader
w, err := pipeWriter.WriteString(s)
pipeWriter.Close()
return w, err
}
func pipeBytesToStdin(b []byte) (int, error) {
return pipeToStdin(string(b))
}
// TestGetPasswd_Err tests errors are properly handled from getch()
func TestGetPasswd_Err(t *testing.T) {
var inBuffer *bytes.Buffer
getch = func() (byte, error) {
b, err := inBuffer.ReadByte()
if err != nil {
return 13, err
}
if b == 'z' {
return 'z', fmt.Errorf("Forced error; byte returned should not be considered accurate.")
}
return b, nil
}
defer func() { getch = defaultGetCh }()
for input, expectedPassword := range map[string]string{"abc": "abc", "abzc": "ab"} {
inBuffer = bytes.NewBufferString(input)
p, err := GetPasswdMasked()
if string(p) != expectedPassword {
t.Errorf("Expected %q but got %q instead.", expectedPassword, p)
}
if err == nil {
t.Errorf("Expected error to be returned.")
}
}
}
func TestMaxPasswordLength(t *testing.T) {
type testData struct {
input []byte
expectedErr error
// Helper field to output in case of failure; rather than hundreds of
// bytes.
inputDesc string
}
ds := []testData{
testData{append(bytes.Repeat([]byte{'a'}, maxLength), '\n'), nil, fmt.Sprintf("%v 'a' bytes followed by a newline", maxLength)},
testData{append(bytes.Repeat([]byte{'a'}, maxLength+1), '\n'), ErrMaxLengthExceeded, fmt.Sprintf("%v 'a' bytes followed by a newline", maxLength+1)},
testData{append(bytes.Repeat([]byte{0x00}, maxLength+1), '\n'), ErrMaxLengthExceeded, fmt.Sprintf("%v 0x00 bytes followed by a newline", maxLength+1)},
}
for _, d := range ds {
pipeBytesToStdin(d.input)
_, err := GetPasswd()
if err != d.expectedErr {
t.Errorf("Expected error to be %v; isntead got %v from %v", d.expectedErr, err, d.inputDesc)
}
}
}

4
vendor/github.com/howeyc/gopass/vendor/golang.org/x/crypto/ssh/terminal/terminal.go сгенерированный поставляемый
Просмотреть файл

@ -789,6 +789,10 @@ func (t *Terminal) SetSize(width, height int) error {
// If the width didn't change then nothing else needs to be
// done.
return nil
case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
// If there is nothing on current line and no prompt printed,
// just do nothing
return nil
case width < oldWidth:
// Some terminals (e.g. xterm) will truncate lines that were
// too long when shinking. Others, (e.g. gnome-terminal) will

48
vendor/github.com/howeyc/gopass/vendor/golang.org/x/crypto/ssh/terminal/terminal_test.go сгенерированный поставляемый
Просмотреть файл

@ -6,6 +6,7 @@ package terminal
import (
"io"
"os"
"testing"
)
@ -241,3 +242,50 @@ func TestPasswordNotSaved(t *testing.T) {
t.Fatalf("password was saved in history")
}
}
var setSizeTests = []struct {
width, height int
}{
{40, 13},
{80, 24},
{132, 43},
}
func TestTerminalSetSize(t *testing.T) {
for _, setSize := range setSizeTests {
c := &MockTerminal{
toSend: []byte("password\r\x1b[A\r"),
bytesPerRead: 1,
}
ss := NewTerminal(c, "> ")
ss.SetSize(setSize.width, setSize.height)
pw, _ := ss.ReadPassword("Password: ")
if pw != "password" {
t.Fatalf("failed to read password, got %s", pw)
}
if string(c.received) != "Password: \r\n" {
t.Errorf("failed to set the temporary prompt expected %q, got %q", "Password: ", c.received)
}
}
}
func TestMakeRawState(t *testing.T) {
fd := int(os.Stdout.Fd())
if !IsTerminal(fd) {
t.Skip("stdout is not a terminal; skipping test")
}
st, err := GetState(fd)
if err != nil {
t.Fatalf("failed to get terminal state from GetState: %s", err)
}
defer Restore(fd, st)
raw, err := MakeRaw(fd)
if err != nil {
t.Fatalf("failed to get terminal state from MakeRaw: %s", err)
}
if *st != *raw {
t.Errorf("states do not match; was %v, expected %v", raw, st)
}
}

11
vendor/github.com/howeyc/gopass/vendor/golang.org/x/crypto/ssh/terminal/util.go сгенерированный поставляемый
Просмотреть файл

@ -14,7 +14,7 @@
// panic(err)
// }
// defer terminal.Restore(0, oldState)
package terminal
package terminal // import "golang.org/x/crypto/ssh/terminal"
import (
"io"
@ -44,8 +44,13 @@ func MakeRaw(fd int) (*State, error) {
}
newState := oldState.termios
newState.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF
newState.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG
// This attempts to replicate the behaviour documented for cfmakeraw in
// the termios(3) manpage.
newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
newState.Oflag &^= syscall.OPOST
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
newState.Cflag &^= syscall.CSIZE | syscall.PARENB
newState.Cflag |= syscall.CS8
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
return nil, err
}

58
vendor/github.com/howeyc/gopass/vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,58 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package terminal provides support functions for dealing with terminals, as
// commonly found on UNIX systems.
//
// Putting a terminal into raw mode is the most common requirement:
//
// oldState, err := terminal.MakeRaw(0)
// if err != nil {
// panic(err)
// }
// defer terminal.Restore(0, oldState)
package terminal
import (
"fmt"
"runtime"
)
type State struct{}
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
return false
}
// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
// GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal.
func GetState(fd int) (*State, error) {
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd int, state *State) error {
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
// GetSize returns the dimensions of the given terminal.
func GetSize(fd int) (width, height int, err error) {
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
// ReadPassword reads a line of input from a terminal without local echo. This
// is commonly used for inputting passwords and other sensitive data. The slice
// returned does not include the \n.
func ReadPassword(fd int) ([]byte, error) {
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}

4
vendor/github.com/howeyc/gopass/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go сгенерированный поставляемый
Просмотреть файл

@ -87,8 +87,8 @@ func MakeRaw(fd int) (*State, error) {
if e != 0 {
return nil, error(e)
}
st &^= (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0)
raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0)
if e != 0 {
return nil, error(e)
}

42
vendor/github.com/howeyc/gopass/win.go сгенерированный поставляемый
Просмотреть файл

@ -1,42 +0,0 @@
// +build windows
package gopass
import "syscall"
import "unsafe"
import "unicode/utf16"
func getch() byte {
modkernel32 := syscall.NewLazyDLL("kernel32.dll")
procReadConsole := modkernel32.NewProc("ReadConsoleW")
procGetConsoleMode := modkernel32.NewProc("GetConsoleMode")
procSetConsoleMode := modkernel32.NewProc("SetConsoleMode")
var mode uint32
pMode := &mode
procGetConsoleMode.Call(uintptr(syscall.Stdin), uintptr(unsafe.Pointer(pMode)))
var echoMode, lineMode uint32
echoMode = 4
lineMode = 2
var newMode uint32
newMode = mode ^ (echoMode | lineMode)
procSetConsoleMode.Call(uintptr(syscall.Stdin), uintptr(newMode))
line := make([]uint16, 1)
pLine := &line[0]
var n uint16
procReadConsole.Call(uintptr(syscall.Stdin), uintptr(unsafe.Pointer(pLine)), uintptr(len(line)), uintptr(unsafe.Pointer(&n)))
b := []byte(string(utf16.Decode(line)))
procSetConsoleMode.Call(uintptr(syscall.Stdin), uintptr(mode))
// Not sure how this could happen, but it did for someone
if len(b) > 0 {
return b[0]
} else {
return 13
}
}