diff --git a/MANPAGE.md b/MANPAGE.md index 2f72169..517cc82 100644 --- a/MANPAGE.md +++ b/MANPAGE.md @@ -39,6 +39,11 @@ Options: **-debug** : Enable debug output +**-extpass string** +: Use an external program (like ssh-askpass) for the password prompt. +The program should return the password on stdout, a trailing newline is +stripped by gocryptfs. + **-f** : Stay in the foreground diff --git a/cryptfs/filter.go b/cryptfs/filter.go index 079b64f..f80889d 100644 --- a/cryptfs/filter.go +++ b/cryptfs/filter.go @@ -6,8 +6,9 @@ package cryptfs // when file names are not encrypted func (be *CryptFS) IsFiltered(path string) bool { // gocryptfs.conf in the root directory is forbidden - if be.plaintextNames == true && path == "gocryptfs.conf" { - Warn.Printf("The name \"/gocryptfs.conf\" is reserved when \"--plaintextnames\" is used\n") + if be.plaintextNames == true && path == ConfDefaultName { + Warn.Printf("The name /%s is reserved when -plaintextnames is used\n", + ConfDefaultName) return true } return false diff --git a/integration_tests/cli_test.go b/integration_tests/cli_test.go new file mode 100644 index 0000000..a696600 --- /dev/null +++ b/integration_tests/cli_test.go @@ -0,0 +1,46 @@ +package integration_tests + +// Test CLI operations like "-init", "-password" etc + +import ( + "os" + "os/exec" + "testing" + + "github.com/rfjakob/gocryptfs/cryptfs" +) + +func TestInit(t *testing.T) { + dir := tmpDir + "TestInit/" + err := os.Mkdir(dir, 0777) + if err != nil { + t.Fatal(err) + } + cmd := exec.Command(gocryptfsBinary, "-init", "-extpass", "echo test", dir) + if testing.Verbose() { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + err = cmd.Run() + if err != nil { + t.Error(err) + } + _, err = os.Stat(dir + cryptfs.ConfDefaultName) + if err != nil { + t.Error(err) + } +} + +// "dir" has been initialized by TestInit +func TestPasswd(t *testing.T) { + dir := tmpDir + "TestInit/" + cmd := exec.Command(gocryptfsBinary, "-passwd", "-extpass", "echo test", dir) + if testing.Verbose() { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + err := cmd.Run() + if err != nil { + t.Error(err) + } +} diff --git a/integration_tests/helpers.go b/integration_tests/helpers.go index fdad28b..3ab1d21 100644 --- a/integration_tests/helpers.go +++ b/integration_tests/helpers.go @@ -10,13 +10,13 @@ import ( "testing" ) +// Note: the code assumes that all have a trailing slash const tmpDir = "/tmp/gocryptfs_main_test/" - -// Mountpoint -// Note: the code assumes that both have a trailing slash! const plainDir = tmpDir + "plain/" const cipherDir = tmpDir + "cipher/" +const gocryptfsBinary = "../gocryptfs" + func resetTmpDir() { fu := exec.Command("fusermount", "-z", "-u", plainDir) fu.Run() @@ -40,11 +40,11 @@ func mount(extraArgs ...string) { //args = append(args, "--fusedebug") args = append(args, cipherDir) args = append(args, plainDir) - c := exec.Command("../gocryptfs", args...) - // Warning messages clutter the test output. Uncomment if you want to debug - // failures. - //c.Stdout = os.Stdout - //c.Stderr = os.Stderr + c := exec.Command(gocryptfsBinary, args...) + if testing.Verbose() { + c.Stdout = os.Stdout + c.Stderr = os.Stderr + } err := c.Run() if err != nil { fmt.Println(err) diff --git a/main.go b/main.go index e9eef33..5fcf95e 100644 --- a/main.go +++ b/main.go @@ -44,7 +44,7 @@ func initDir(args *argContainer) { } cryptfs.Info.Printf("Choose a password for protecting your files.\n") - password := readPasswordTwice() + password := readPasswordTwice(args.extpass) err = cryptfs.CreateConfFile(args.config, password, args.plaintextnames) if err != nil { fmt.Println(err) @@ -66,25 +66,25 @@ func usageText() { type argContainer struct { debug, init, zerokey, fusedebug, openssl, passwd, foreground, version, plaintextnames, quiet bool - masterkey, mountpoint, cipherdir, cpuprofile, config string - notifypid int + masterkey, mountpoint, cipherdir, cpuprofile, config, extpass string + notifypid int } var flagSet *flag.FlagSet // loadConfig - load the config file "filename", prompting the user for the password -func loadConfig(filename string) (masterkey []byte, confFile *cryptfs.ConfFile) { +func loadConfig(args *argContainer) (masterkey []byte, confFile *cryptfs.ConfFile) { // Check if the file exists at all before prompting for a password - _, err := os.Stat(filename) + _, err := os.Stat(args.config) if err != nil { fmt.Print(err) os.Exit(ERREXIT_LOADCONF) } fmt.Printf("Password: ") - pw := readPassword() + pw := readPassword(args.extpass) cryptfs.Info.Printf("Decrypting master key... ") cryptfs.Warn.Disable() // Silence DecryptBlock() error messages on incorrect password - masterkey, confFile, err = cryptfs.LoadConfFile(filename, pw) + masterkey, confFile, err = cryptfs.LoadConfFile(args.config, pw) cryptfs.Warn.Enable() if err != nil { fmt.Println(err) @@ -97,10 +97,10 @@ func loadConfig(filename string) (masterkey []byte, confFile *cryptfs.ConfFile) } // changePassword - change the password of config file "filename" -func changePassword(filename string) { - masterkey, confFile := loadConfig(filename) +func changePassword(args *argContainer) { + masterkey, confFile := loadConfig(args) fmt.Printf("Please enter your new password.\n") - newPw := readPasswordTwice() + newPw := readPasswordTwice(args.extpass) confFile.EncryptKey(masterkey, newPw) err := confFile.WriteFile() if err != nil { @@ -139,6 +139,7 @@ func main() { flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") flagSet.StringVar(&args.config, "config", "", "Use specified config file instead of CIPHERDIR/gocryptfs.conf") + flagSet.StringVar(&args.extpass, "extpass", "", "Use external program for the password prompt") flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+ "successful mount - used internally for daemonization") flagSet.Parse(os.Args[1:]) @@ -215,7 +216,7 @@ func main() { fmt.Printf("Usage: %s -passwd [OPTIONS] CIPHERDIR\n", PROGRAM_NAME) os.Exit(ERREXIT_USAGE) } - changePassword(args.config) // does not return + changePassword(&args) // does not return } // Mount // Check mountpoint @@ -248,7 +249,7 @@ func main() { } else { // Load master key from config file var confFile *cryptfs.ConfFile - masterkey, confFile = loadConfig(args.config) + masterkey, confFile = loadConfig(&args) printMasterKey(masterkey) args.plaintextnames = confFile.PlaintextNames() } diff --git a/password.go b/password.go index 6b3452a..ede65dd 100644 --- a/password.go +++ b/password.go @@ -2,15 +2,18 @@ package main import ( "fmt" - "golang.org/x/crypto/ssh/terminal" "os" + "os/exec" + "strings" + + "golang.org/x/crypto/ssh/terminal" ) -func readPasswordTwice() string { +func readPasswordTwice(extpass string) string { fmt.Printf("Password: ") - p1 := readPassword() - fmt.Printf("Repeat: ") - p2 := readPassword() + p1 := readPassword(extpass) + fmt.Printf("Repeat: ") + p2 := readPassword(extpass) if p1 != p2 { fmt.Printf("Passwords do not match\n") os.Exit(ERREXIT_PASSWORD) @@ -18,14 +21,39 @@ func readPasswordTwice() string { return p1 } -// Get password from terminal -func readPassword() string { - fd := int(os.Stdin.Fd()) - p, err := terminal.ReadPassword(fd) - fmt.Printf("\n") - if err != nil { - fmt.Printf("Error: Could not read password: %v\n", err) +// 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 { + fmt.Printf("extpass program returned error: %v\n", err) + os.Exit(ERREXIT_PASSWORD) + } + fmt.Printf("(extpass)\n") + // 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 { + fmt.Printf("Error: Could not read password from terminal: %v\n", err) + os.Exit(ERREXIT_PASSWORD) + } + fmt.Printf("\n") + } + password = string(output) + if password == "" { + fmt.Printf("Error: password is empty\n") os.Exit(ERREXIT_PASSWORD) } - return string(p) + return password }