diff options
Diffstat (limited to 'internal/readpassword')
10 files changed, 144 insertions, 15 deletions
| diff --git a/internal/readpassword/extpass_test.go b/internal/readpassword/extpass_test.go index b35153f..037d111 100644 --- a/internal/readpassword/extpass_test.go +++ b/internal/readpassword/extpass_test.go @@ -26,7 +26,7 @@ func TestExtpass(t *testing.T) {  func TestOnceExtpass(t *testing.T) {  	p1 := "lkadsf0923rdfi48rqwhdsf" -	p2 := string(Once("echo "+p1, "")) +	p2 := string(Once("echo "+p1, "", ""))  	if p1 != p2 {  		t.Errorf("p1=%q != p2=%q", p1, p2)  	} @@ -34,14 +34,16 @@ func TestOnceExtpass(t *testing.T) {  func TestTwiceExtpass(t *testing.T) {  	p1 := "w5w44t3wfe45srz434" -	p2 := string(Once("echo "+p1, "")) +	p2 := string(Once("echo "+p1, "", ""))  	if p1 != p2 {  		t.Errorf("p1=%q != p2=%q", p1, p2)  	}  }  // When extpass returns an empty string, we should crash. -// https://talks.golang.org/2014/testing.slide#23 +// +// The TEST_SLAVE magic is explained at +// https://talks.golang.org/2014/testing.slide#23 .  func TestExtpassEmpty(t *testing.T) {  	if os.Getenv("TEST_SLAVE") == "1" {  		readPasswordExtpass("echo") diff --git a/internal/readpassword/passfile.go b/internal/readpassword/passfile.go new file mode 100644 index 0000000..73af279 --- /dev/null +++ b/internal/readpassword/passfile.go @@ -0,0 +1,43 @@ +package readpassword + +import ( +	"bytes" +	"os" + +	"github.com/rfjakob/gocryptfs/internal/exitcodes" +	"github.com/rfjakob/gocryptfs/internal/tlog" +) + +func readPassFile(passfile string) []byte { +	tlog.Info.Printf("passfile: reading from file %q", passfile) +	f, err := os.Open(passfile) +	if err != nil { +		tlog.Fatal.Printf("fatal: passfile: could not open %q: %v", passfile, err) +		os.Exit(exitcodes.ReadPassword) +	} +	defer f.Close() +	// +1 for an optional trailing newline, +	// +2 so we can detect if maxPasswordLen is exceeded. +	buf := make([]byte, maxPasswordLen+2) +	n, err := f.Read(buf) +	if err != nil { +		tlog.Fatal.Printf("fatal: passfile: could not read from %q: %v", passfile, err) +		os.Exit(exitcodes.ReadPassword) +	} +	buf = buf[:n] +	// Split into first line and "trailing garbage" +	lines := bytes.SplitN(buf, []byte("\n"), 2) +	if len(lines[0]) == 0 { +		tlog.Fatal.Printf("fatal: passfile: empty first line in %q", passfile) +		os.Exit(exitcodes.ReadPassword) +	} +	if len(lines[0]) > maxPasswordLen { +		tlog.Fatal.Printf("fatal: passfile: max password length (%d bytes) exceeded", maxPasswordLen) +		os.Exit(exitcodes.ReadPassword) +	} +	if len(lines) > 1 && len(lines[1]) > 0 { +		tlog.Warn.Printf("passfile: ignoring trailing garbage (%d bytes) after first line", +			len(lines[1])) +	} +	return lines[0] +} diff --git a/internal/readpassword/passfile_test.go b/internal/readpassword/passfile_test.go new file mode 100644 index 0000000..457170b --- /dev/null +++ b/internal/readpassword/passfile_test.go @@ -0,0 +1,80 @@ +package readpassword + +import ( +	"os" +	"os/exec" +	"testing" +) + +func TestPassfile(t *testing.T) { +	testcases := []struct { +		file string +		want string +	}{ +		{"mypassword.txt", "mypassword"}, +		{"mypassword_garbage.txt", "mypassword"}, +		{"mypassword_missing_newline.txt", "mypassword"}, +	} +	for _, tc := range testcases { +		pw := readPassFile("passfile_test_files/" + tc.file) +		if string(pw) != tc.want { +			t.Errorf("Wrong result: want=%q have=%q", tc.want, pw) +		} +	} +} + +// readPassFile() should exit instead of returning an empty string. +// +// The TEST_SLAVE magic is explained at +// https://talks.golang.org/2014/testing.slide#23 . +func TestPassfileEmpty(t *testing.T) { +	if os.Getenv("TEST_SLAVE") == "1" { +		readPassFile("passfile_test_files/empty.txt") +		return +	} +	cmd := exec.Command(os.Args[0], "-test.run=TestPassfileEmpty$") +	cmd.Env = append(os.Environ(), "TEST_SLAVE=1") +	err := cmd.Run() +	if err != nil { +		return +	} +	t.Fatal("should have exited") +} + +// File containing just a newline. +// readPassFile() should exit instead of returning an empty string. +// +// The TEST_SLAVE magic is explained at +// https://talks.golang.org/2014/testing.slide#23 . +func TestPassfileNewline(t *testing.T) { +	if os.Getenv("TEST_SLAVE") == "1" { +		readPassFile("passfile_test_files/newline.txt") +		return +	} +	cmd := exec.Command(os.Args[0], "-test.run=TestPassfileEmpty$") +	cmd.Env = append(os.Environ(), "TEST_SLAVE=1") +	err := cmd.Run() +	if err != nil { +		return +	} +	t.Fatal("should have exited") +} + +// File containing "\ngarbage". +// readPassFile() should exit instead of returning an empty string. +// +// The TEST_SLAVE magic is explained at +// https://talks.golang.org/2014/testing.slide#23 . +func TestPassfileEmptyFirstLine(t *testing.T) { +	if os.Getenv("TEST_SLAVE") == "1" { +		readPassFile("passfile_test_files/empty_first_line.txt") +		return +	} +	cmd := exec.Command(os.Args[0], "-test.run=TestPassfileEmptyFirstLine$") +	cmd.Env = append(os.Environ(), "TEST_SLAVE=1") +	err := cmd.Run() +	if err != nil { +		return +	} +	t.Fatal("should have exited") +} diff --git a/internal/readpassword/passfile_test_files/empty.txt b/internal/readpassword/passfile_test_files/empty.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/internal/readpassword/passfile_test_files/empty.txt diff --git a/internal/readpassword/passfile_test_files/empty_first_line.txt b/internal/readpassword/passfile_test_files/empty_first_line.txt new file mode 100644 index 0000000..a607e80 --- /dev/null +++ b/internal/readpassword/passfile_test_files/empty_first_line.txt @@ -0,0 +1,2 @@ + +garbage diff --git a/internal/readpassword/passfile_test_files/mypassword.txt b/internal/readpassword/passfile_test_files/mypassword.txt new file mode 100644 index 0000000..48d23cf --- /dev/null +++ b/internal/readpassword/passfile_test_files/mypassword.txt @@ -0,0 +1 @@ +mypassword diff --git a/internal/readpassword/passfile_test_files/mypassword_garbage.txt b/internal/readpassword/passfile_test_files/mypassword_garbage.txt new file mode 100644 index 0000000..74ba741 --- /dev/null +++ b/internal/readpassword/passfile_test_files/mypassword_garbage.txt @@ -0,0 +1,2 @@ +mypassword +garbage diff --git a/internal/readpassword/passfile_test_files/mypassword_missing_newline.txt b/internal/readpassword/passfile_test_files/mypassword_missing_newline.txt new file mode 100644 index 0000000..b3c42b5 --- /dev/null +++ b/internal/readpassword/passfile_test_files/mypassword_missing_newline.txt @@ -0,0 +1 @@ +mypassword
\ No newline at end of file diff --git a/internal/readpassword/passfile_test_files/newline.txt b/internal/readpassword/passfile_test_files/newline.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/internal/readpassword/passfile_test_files/newline.txt @@ -0,0 +1 @@ + diff --git a/internal/readpassword/read.go b/internal/readpassword/read.go index c99be5d..0378e53 100644 --- a/internal/readpassword/read.go +++ b/internal/readpassword/read.go @@ -24,7 +24,10 @@ const (  // Once tries to get a password from the user, either from the terminal, extpass  // or stdin. Leave "prompt" empty to use the default "Password: " prompt. -func Once(extpass string, prompt string) []byte { +func Once(extpass string, passfile string, prompt string) []byte { +	if passfile != "" { +		return readPassFile(passfile) +	}  	if extpass != "" {  		return readPasswordExtpass(extpass)  	} @@ -39,7 +42,10 @@ func Once(extpass string, prompt string) []byte {  // Twice is the same as Once but will prompt twice if we get the password from  // the terminal. -func Twice(extpass string) []byte { +func Twice(extpass string, passfile string) []byte { +	if passfile != "" { +		return readPassFile(passfile) +	}  	if extpass != "" {  		return readPasswordExtpass(extpass)  	} @@ -95,16 +101,7 @@ func readPasswordStdin(prompt string) []byte {  // Exits on read error or empty result.  func readPasswordExtpass(extpass string) []byte {  	tlog.Info.Println("Reading password from extpass program") -	var parts []string -	// The option "-passfile=FILE" gets transformed to -	// "-extpass="/bin/cat -- FILE". We don't want to split FILE on spaces, -	// so let's handle it manually. -	passfileCat := "/bin/cat -- " -	if strings.HasPrefix(extpass, passfileCat) { -		parts = []string{"/bin/cat", "--", extpass[len(passfileCat):]} -	} else { -		parts = strings.Split(extpass, " ") -	} +	parts := strings.Split(extpass, " ")  	cmd := exec.Command(parts[0], parts[1:]...)  	cmd.Stderr = os.Stderr  	pipe, err := cmd.StdoutPipe() | 
