aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2016-06-15 22:43:31 +0200
committerJakob Unterwurzacher2016-06-15 22:44:24 +0200
commitc89455063cfd9c531c0a671251ccfcd46f09403d (patch)
tree1bd5330aad0ac7b16ecb5b35150a304e56271be3
parent218bf83ce399832a0eccfbd025e5dd0399db6bed (diff)
readpassword: create internal package for password reading
* Supports stdin * Add tests for extpass and stdin As per user request at https://github.com/rfjakob/gocryptfs/issues/30
-rw-r--r--internal/readpassword/extpass_test.go55
-rw-r--r--internal/readpassword/read.go133
-rw-r--r--internal/readpassword/stdin_test.go100
-rw-r--r--main.go14
-rw-r--r--password.go64
-rw-r--r--tests/integration_tests/cli_test.go30
6 files changed, 318 insertions, 78 deletions
diff --git a/internal/readpassword/extpass_test.go b/internal/readpassword/extpass_test.go
new file mode 100644
index 0000000..6eda142
--- /dev/null
+++ b/internal/readpassword/extpass_test.go
@@ -0,0 +1,55 @@
+package readpassword
+
+import (
+ "os"
+ "os/exec"
+ "testing"
+
+ "github.com/rfjakob/gocryptfs/internal/toggledlog"
+)
+
+func TestMain(m *testing.M) {
+ // Shut up info output
+ toggledlog.Info.Enabled = false
+ m.Run()
+}
+
+func TestExtpass(t *testing.T) {
+ p1 := "ads2q4tw41reg52"
+ p2 := readPasswordExtpass("echo " + p1)
+ if p1 != p2 {
+ t.Errorf("p1=%q != p2=%q", p1, p2)
+ }
+}
+
+func TestOnceExtpass(t *testing.T) {
+ p1 := "lkadsf0923rdfi48rqwhdsf"
+ p2 := Once("echo " + p1)
+ if p1 != p2 {
+ t.Errorf("p1=%q != p2=%q", p1, p2)
+ }
+}
+
+func TestTwiceExtpass(t *testing.T) {
+ p1 := "w5w44t3wfe45srz434"
+ p2 := 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
+func TestExtpassEmpty(t *testing.T) {
+ if os.Getenv("TEST_SLAVE") == "1" {
+ readPasswordExtpass("echo")
+ return
+ }
+ cmd := exec.Command(os.Args[0], "-test.run=TestExtpassEmpty$")
+ cmd.Env = append(os.Environ(), "TEST_SLAVE=1")
+ err := cmd.Run()
+ if err != nil {
+ return
+ }
+ t.Fatal("empty password should have failed")
+}
diff --git a/internal/readpassword/read.go b/internal/readpassword/read.go
new file mode 100644
index 0000000..f316846
--- /dev/null
+++ b/internal/readpassword/read.go
@@ -0,0 +1,133 @@
+package readpassword
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "strings"
+
+ "golang.org/x/crypto/ssh/terminal"
+
+ "github.com/rfjakob/gocryptfs/internal/toggledlog"
+)
+
+const (
+ exitCode = 9
+)
+
+// TODO
+var colorReset, colorRed string
+
+// Once() tries to get a password from the user, either from the terminal,
+// extpass or stdin.
+func Once(extpass string) string {
+ if extpass != "" {
+ return readPasswordExtpass(extpass)
+ }
+ if !terminal.IsTerminal(int(os.Stdin.Fd())) {
+ return readPasswordStdin()
+ }
+ return readPasswordTerminal("Password: ")
+}
+
+// Twice() is the same as Once but will prompt twice if we get
+// the password from the terminal.
+func Twice(extpass string) string {
+ if extpass != "" {
+ return readPasswordExtpass(extpass)
+ }
+ if !terminal.IsTerminal(int(os.Stdin.Fd())) {
+ return readPasswordStdin()
+ }
+ p1 := readPasswordTerminal("Password: ")
+ p2 := readPasswordTerminal("Repeat: ")
+ if p1 != p2 {
+ toggledlog.Fatal.Println(colorRed + "Passwords do not match" + colorReset)
+ os.Exit(exitCode)
+ }
+ return p1
+}
+
+// readPasswordTerminal reads a line from the terminal.
+// Exits on read error or empty result.
+func readPasswordTerminal(prompt string) string {
+ fd := int(os.Stdin.Fd())
+ fmt.Fprintf(os.Stderr, prompt)
+ // terminal.ReadPassword removes the trailing newline
+ p, err := terminal.ReadPassword(fd)
+ if err != nil {
+ toggledlog.Fatal.Printf(colorRed+"Could not read password from terminal: %v\n"+colorReset, err)
+ os.Exit(exitCode)
+ }
+ fmt.Fprintf(os.Stderr, "\n")
+ if len(p) == 0 {
+ toggledlog.Fatal.Println(colorRed + "Password is empty" + colorReset)
+ os.Exit(exitCode)
+ }
+ return string(p)
+}
+
+// readPasswordStdin reads a line from stdin
+// Exits on read error or empty result.
+func readPasswordStdin() string {
+ toggledlog.Info.Println("Reading password from stdin")
+ p := readLineUnbuffered(os.Stdin)
+ if len(p) == 0 {
+ fmt.Fprintf(os.Stderr, "FOOOOOO\n")
+ toggledlog.Fatal.Println(colorRed + "Got empty password from stdin" + colorReset)
+ os.Exit(exitCode)
+ }
+ return p
+}
+
+// readPasswordExtpass executes the "extpass" program and returns the first line
+// of the output.
+// Exits on read error or empty result.
+func readPasswordExtpass(extpass string) string {
+ toggledlog.Info.Println("Reading password from extpass program")
+ parts := strings.Split(extpass, " ")
+ cmd := exec.Command(parts[0], parts[1:]...)
+ cmd.Stderr = os.Stderr
+ pipe, err := cmd.StdoutPipe()
+ if err != nil {
+ toggledlog.Fatal.Printf(colorRed+"extpass pipe setup failed: %v\n"+colorReset, err)
+ os.Exit(exitCode)
+ }
+ err = cmd.Start()
+ if err != nil {
+ toggledlog.Fatal.Printf(colorRed+"extpass cmd start failed: %v\n"+colorReset, err)
+ os.Exit(exitCode)
+ }
+ p := readLineUnbuffered(pipe)
+ pipe.Close()
+ cmd.Wait()
+ if len(p) == 0 {
+ toggledlog.Fatal.Println(colorRed + "extpass: password is empty" + colorReset)
+ os.Exit(exitCode)
+ }
+ return p
+}
+
+// readLineUnbuffered reads single bytes from "r" util it gets "\n" or EOF.
+// The returned string does NOT contain the trailing "\n".
+func readLineUnbuffered(r io.Reader) (l string) {
+ b := make([]byte, 1)
+ for {
+ n, err := r.Read(b)
+ if err == io.EOF {
+ return l
+ }
+ if err != nil {
+ toggledlog.Fatal.Printf(colorRed+"readLineUnbuffered: %v\n"+colorReset, err)
+ os.Exit(exitCode)
+ }
+ if n == 0 {
+ continue
+ }
+ if b[0] == '\n' {
+ return l
+ }
+ l = l + string(b)
+ }
+}
diff --git a/internal/readpassword/stdin_test.go b/internal/readpassword/stdin_test.go
new file mode 100644
index 0000000..2d9f93f
--- /dev/null
+++ b/internal/readpassword/stdin_test.go
@@ -0,0 +1,100 @@
+package readpassword
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "testing"
+)
+
+// Provide password via stdin, terminated by "\n".
+func TestStdin(t *testing.T) {
+ p1 := "g55434t55wef"
+ if os.Getenv("TEST_SLAVE") == "1" {
+ p2 := readPasswordStdin()
+ if p1 != p2 {
+ fmt.Fprintf(os.Stderr, "%q != %q", p1, p2)
+ os.Exit(1)
+ }
+ return
+ }
+ cmd := exec.Command(os.Args[0], "-test.run=TestStdin$")
+ cmd.Env = append(os.Environ(), "TEST_SLAVE=1")
+ cmd.Stderr = os.Stderr
+ pipe, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = cmd.Start()
+ if err != nil {
+ t.Fatal(err)
+ }
+ n, err := pipe.Write([]byte(p1 + "\n"))
+ if n == 0 || err != nil {
+ t.Fatal(err)
+ }
+ err = cmd.Wait()
+ if err != nil {
+ t.Fatalf("slave failed with %v", err)
+ }
+}
+
+// Provide password via stdin, terminated by EOF (pipe close). This should not
+// hang.
+func TestStdinEof(t *testing.T) {
+ p1 := "asd45as5f4a36"
+ if os.Getenv("TEST_SLAVE") == "1" {
+ p2 := readPasswordStdin()
+ if p1 != p2 {
+ fmt.Fprintf(os.Stderr, "%q != %q", p1, p2)
+ os.Exit(1)
+ }
+ return
+ }
+ cmd := exec.Command(os.Args[0], "-test.run=TestStdinEof$")
+ cmd.Env = append(os.Environ(), "TEST_SLAVE=1")
+ cmd.Stderr = os.Stderr
+ pipe, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = cmd.Start()
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = pipe.Write([]byte(p1))
+ if err != nil {
+ t.Fatal(err)
+ }
+ pipe.Close()
+ err = cmd.Wait()
+ if err != nil {
+ t.Fatalf("slave failed with %v", err)
+ }
+}
+
+// Provide empty password via stdin
+func TestStdinEmpty(t *testing.T) {
+ if os.Getenv("TEST_SLAVE") == "1" {
+ readPasswordStdin()
+ }
+ cmd := exec.Command(os.Args[0], "-test.run=TestStdinEmpty$")
+ cmd.Env = append(os.Environ(), "TEST_SLAVE=1")
+ pipe, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = cmd.Start()
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = pipe.Write([]byte("\n"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ pipe.Close()
+ err = cmd.Wait()
+ if err == nil {
+ t.Fatalf("empty password should have failed")
+ }
+}
diff --git a/main.go b/main.go
index 6896f11..61b56e6 100644
--- a/main.go
+++ b/main.go
@@ -28,6 +28,7 @@ import (
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/prefer_openssl"
+ "github.com/rfjakob/gocryptfs/internal/readpassword"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
)
@@ -38,7 +39,6 @@ const (
ERREXIT_CIPHERDIR = 6
ERREXIT_INIT = 7
ERREXIT_LOADCONF = 8
- ERREXIT_PASSWORD = 9
ERREXIT_MOUNTPOINT = 10
)
@@ -71,7 +71,7 @@ func initDir(args *argContainer) {
} else {
toggledlog.Info.Printf("Using password provided via -extpass.")
}
- password := readPasswordTwice(args.extpass)
+ password := readpassword.Twice(args.extpass)
creator := toggledlog.ProgramName + " " + GitVersion
err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator)
if err != nil {
@@ -121,17 +121,13 @@ func loadConfig(args *argContainer) (masterkey []byte, confFile *configfile.Conf
toggledlog.Fatal.Printf(colorRed+"Config file not found: %v\n"+colorReset, err)
os.Exit(ERREXIT_LOADCONF)
}
- if args.extpass == "" {
- fmt.Fprintf(os.Stderr, "Password: ")
- }
- pw := readPassword(args.extpass)
- toggledlog.Info.Printf("Decrypting master key... ")
+ pw := readpassword.Once(args.extpass)
+ toggledlog.Info.Println("Decrypting master key")
masterkey, confFile, err = configfile.LoadConfFile(args.config, pw)
if err != nil {
toggledlog.Fatal.Println(colorRed + err.Error() + colorReset)
os.Exit(ERREXIT_LOADCONF)
}
- toggledlog.Info.Printf("done.")
return masterkey, confFile
}
@@ -140,7 +136,7 @@ func loadConfig(args *argContainer) (masterkey []byte, confFile *configfile.Conf
func changePassword(args *argContainer) {
masterkey, confFile := loadConfig(args)
toggledlog.Info.Println("Please enter your new password.")
- newPw := readPasswordTwice(args.extpass)
+ newPw := readpassword.Twice(args.extpass)
confFile.EncryptKey(masterkey, newPw, confFile.ScryptObject.LogN())
err := confFile.WriteFile()
if err != nil {
diff --git a/password.go b/password.go
deleted file mode 100644
index 01c71a7..0000000
--- a/password.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "os/exec"
- "strings"
-
- "golang.org/x/crypto/ssh/terminal"
-
- "github.com/rfjakob/gocryptfs/internal/toggledlog"
-)
-
-func readPasswordTwice(extpass string) string {
- if extpass == "" {
- fmt.Fprintf(os.Stderr, "Password: ")
- p1 := readPassword("")
- fmt.Fprintf(os.Stderr, "Repeat: ")
- p2 := readPassword("")
- if p1 != p2 {
- toggledlog.Fatal.Println(colorRed + "Passwords do not match" + colorReset)
- os.Exit(ERREXIT_PASSWORD)
- }
- return p1
- } else {
- return readPassword(extpass)
- }
-}
-
-// readPassword - get password from terminal
-// or from the "extpass" program
-func readPassword(extpass string) string {
- var password string
- var err error
- var output []byte
- if extpass != "" {
- parts := strings.Split(extpass, " ")
- cmd := exec.Command(parts[0], parts[1:]...)
- cmd.Stderr = os.Stderr
- output, err = cmd.Output()
- if err != nil {
- toggledlog.Fatal.Printf(colorRed+"extpass program returned error: %v\n"+colorReset, err)
- os.Exit(ERREXIT_PASSWORD)
- }
- // Trim trailing newline like terminal.ReadPassword() does
- if output[len(output)-1] == '\n' {
- output = output[:len(output)-1]
- }
- } else {
- fd := int(os.Stdin.Fd())
- output, err = terminal.ReadPassword(fd)
- if err != nil {
- toggledlog.Fatal.Printf(colorRed+"Could not read password from terminal: %v\n"+colorReset, err)
- os.Exit(ERREXIT_PASSWORD)
- }
- fmt.Fprintf(os.Stderr, "\n")
- }
- password = string(output)
- if password == "" {
- toggledlog.Fatal.Printf(colorRed + "Password is empty\n" + colorReset)
- os.Exit(ERREXIT_PASSWORD)
- }
- return password
-}
diff --git a/tests/integration_tests/cli_test.go b/tests/integration_tests/cli_test.go
index bc604bd..0e88581 100644
--- a/tests/integration_tests/cli_test.go
+++ b/tests/integration_tests/cli_test.go
@@ -49,14 +49,34 @@ func TestPasswd(t *testing.T) {
t.Fatal(err)
}
// Change password using "-extpass"
- cmd2 := exec.Command(test_helpers.GocryptfsBinary, "-q", "-passwd", "-extpass", "echo test", dir)
- cmd2.Stdout = os.Stdout
- cmd2.Stderr = os.Stderr
- err = cmd2.Run()
+ cmd = exec.Command(test_helpers.GocryptfsBinary, "-q", "-passwd", "-extpass", "echo test", dir)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Run()
+ if err != nil {
+ t.Error(err)
+ }
+ // Change password using stdin
+ cmd = exec.Command(test_helpers.GocryptfsBinary, "-q", "-passwd", dir)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ p, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = cmd.Start()
+ if err != nil {
+ t.Error(err)
+ }
+ // Old password
+ p.Write([]byte("test\n"))
+ // New password
+ p.Write([]byte("newpasswd\n"))
+ p.Close()
+ err = cmd.Wait()
if err != nil {
t.Error(err)
}
-
}
// Test -init & -config flag