diff options
| author | Jakob Unterwurzacher | 2018-03-22 00:02:10 +0100 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2018-03-22 00:02:10 +0100 | 
| commit | 9bc039a4bac6b51d9ebe116de5c311e90343a088 (patch) | |
| tree | f4ebd37e2cbe4088435421fb7c9550c186161b09 | |
| parent | 9c86daf499dca8a69b058ec56803d06fbba4fdab (diff) | |
Add `-masterkey=stdin` functionality
https://github.com/rfjakob/gocryptfs/issues/218
| -rw-r--r-- | Documentation/MANPAGE.md | 16 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | gocryptfs-xray/xray_main.go | 2 | ||||
| -rw-r--r-- | internal/readpassword/extpass_test.go | 4 | ||||
| -rw-r--r-- | internal/readpassword/read.go | 19 | ||||
| -rw-r--r-- | internal/readpassword/stdin_test.go | 6 | ||||
| -rw-r--r-- | main.go | 4 | ||||
| -rw-r--r-- | masterkey.go | 10 | ||||
| -rw-r--r-- | mount.go | 9 | ||||
| -rw-r--r-- | tests/example_filesystems/example_filesystems_test.go | 33 | 
10 files changed, 79 insertions, 28 deletions
| diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index b9b000a..e20b786 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -142,20 +142,24 @@ This flag is useful when recovering old gocryptfs filesystems using  "-masterkey". It is ignored (stays at the default) otherwise.  #### -masterkey string -Use a explicit master key specified on the command line. This +Use a explicit master key specified on the command line or, if the special +value "stdin" is used, read the masterkey from stdin. This  option can be used to mount a gocryptfs filesystem without a config file.  Note that the command line, and with it the master key, is visible to -anybody on the machine who can execute "ps -auxwww". -This is meant as a recovery option for emergencies, such as if you have -forgotten the password or lost the config file. +anybody on the machine who can execute "ps -auxwww". Use "-masterkey=stdin" +to avoid that risk. + +The masterkey option is meant as a recovery option for emergencies, such as +if you have forgotten the password or lost the config file.  Even if a config file exists, it will not be used. All non-standard  settings have to be passed on the command line: `-aessiv` when you  mount a filesystem that was created using reverse mode, or  `-plaintextnames` for a filesystem that was created with that option. -Example master key:   -6f717d8b-6b5f8e8a-fd0aa206-778ec093-62c5669b-abd229cd-241e00cd-b4d6713d +Examples:   +-masterkey=6f717d8b-6b5f8e8a-fd0aa206-778ec093-62c5669b-abd229cd-241e00cd-b4d6713d   +-masterkey=stdin  #### -memprofile string  Write memory profile to the specified file. This is useful when debugging @@ -153,6 +153,10 @@ RM:    4.42  Changelog  --------- +vNEXT, in progress +* Add `-masterkey=stdin` functionality +  ([#218](https://github.com/rfjakob/gocryptfs/issues/218)) +  v1.4.4, 2018-03-18  * Overwrite secrets in memory with zeros as soon as possible    ([#211](https://github.com/rfjakob/gocryptfs/issues/211)) diff --git a/gocryptfs-xray/xray_main.go b/gocryptfs-xray/xray_main.go index 522878a..1531eb4 100644 --- a/gocryptfs-xray/xray_main.go +++ b/gocryptfs-xray/xray_main.go @@ -60,7 +60,7 @@ func main() {  func dumpMasterKey(fn string) {  	tlog.Info.Enabled = false -	pw := readpassword.Once("") +	pw := readpassword.Once("", "")  	masterkey, _, err := configfile.LoadConfFile(fn, pw)  	if err != nil {  		fmt.Fprintln(os.Stderr, err) diff --git a/internal/readpassword/extpass_test.go b/internal/readpassword/extpass_test.go index cdfea4e..b35153f 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,7 +34,7 @@ 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)  	} diff --git a/internal/readpassword/read.go b/internal/readpassword/read.go index e2fce8a..c99be5d 100644 --- a/internal/readpassword/read.go +++ b/internal/readpassword/read.go @@ -23,15 +23,18 @@ const (  )  // Once tries to get a password from the user, either from the terminal, extpass -// or stdin. -func Once(extpass string) []byte { +// or stdin. Leave "prompt" empty to use the default "Password: " prompt. +func Once(extpass string, prompt string) []byte {  	if extpass != "" {  		return readPasswordExtpass(extpass)  	} +	if prompt == "" { +		prompt = "Password" +	}  	if !terminal.IsTerminal(int(os.Stdin.Fd())) { -		return readPasswordStdin() +		return readPasswordStdin(prompt)  	} -	return readPasswordTerminal("Password: ") +	return readPasswordTerminal(prompt + ": ")  }  // Twice is the same as Once but will prompt twice if we get the password from @@ -41,7 +44,7 @@ func Twice(extpass string) []byte {  		return readPasswordExtpass(extpass)  	}  	if !terminal.IsTerminal(int(os.Stdin.Fd())) { -		return readPasswordStdin() +		return readPasswordStdin("Password")  	}  	p1 := readPasswordTerminal("Password: ")  	p2 := readPasswordTerminal("Repeat: ") @@ -77,11 +80,11 @@ func readPasswordTerminal(prompt string) []byte {  // readPasswordStdin reads a line from stdin.  // It exits with a fatal error on read error or empty result. -func readPasswordStdin() []byte { -	tlog.Info.Println("Reading password from stdin") +func readPasswordStdin(prompt string) []byte { +	tlog.Info.Printf("Reading %s from stdin", prompt)  	p := readLineUnbuffered(os.Stdin)  	if len(p) == 0 { -		tlog.Fatal.Println("Got empty password from stdin") +		tlog.Fatal.Printf("Got empty %s from stdin", prompt)  		os.Exit(exitcodes.ReadPassword)  	}  	return p diff --git a/internal/readpassword/stdin_test.go b/internal/readpassword/stdin_test.go index 8cf9954..01dd701 100644 --- a/internal/readpassword/stdin_test.go +++ b/internal/readpassword/stdin_test.go @@ -11,7 +11,7 @@ import (  func TestStdin(t *testing.T) {  	p1 := "g55434t55wef"  	if os.Getenv("TEST_SLAVE") == "1" { -		p2 := string(readPasswordStdin()) +		p2 := string(readPasswordStdin("foo"))  		if p1 != p2 {  			fmt.Fprintf(os.Stderr, "%q != %q", p1, p2)  			os.Exit(1) @@ -44,7 +44,7 @@ func TestStdin(t *testing.T) {  func TestStdinEof(t *testing.T) {  	p1 := "asd45as5f4a36"  	if os.Getenv("TEST_SLAVE") == "1" { -		p2 := string(readPasswordStdin()) +		p2 := string(readPasswordStdin("foo"))  		if p1 != p2 {  			fmt.Fprintf(os.Stderr, "%q != %q", p1, p2)  			os.Exit(1) @@ -76,7 +76,7 @@ func TestStdinEof(t *testing.T) {  // Provide empty password via stdin  func TestStdinEmpty(t *testing.T) {  	if os.Getenv("TEST_SLAVE") == "1" { -		readPasswordStdin() +		readPasswordStdin("foo")  	}  	cmd := exec.Command(os.Args[0], "-test.run=TestStdinEmpty$")  	cmd.Env = append(os.Environ(), "TEST_SLAVE=1") @@ -43,10 +43,10 @@ func loadConfig(args *argContainer) (masterkey []byte, confFile *configfile.Conf  	// The user has passed the master key (probably because he forgot the  	// password).  	if args.masterkey != "" { -		masterkey = parseMasterKey(args.masterkey) +		masterkey = parseMasterKey(args.masterkey, false)  		_, confFile, err = configfile.LoadConfFile(args.config, nil)  	} else { -		pw := readpassword.Once(args.extpass) +		pw := readpassword.Once(args.extpass, "")  		tlog.Info.Println("Decrypting master key")  		masterkey, confFile, err = configfile.LoadConfFile(args.config, pw)  		for i := range pw { diff --git a/masterkey.go b/masterkey.go index 3d7afd1..6fda035 100644 --- a/masterkey.go +++ b/masterkey.go @@ -46,7 +46,7 @@ paper and store it in a drawer. Use "-q" to suppress this message.  // parseMasterKey - Parse a hex-encoded master key that was passed on the command line  // Calls os.Exit on failure -func parseMasterKey(masterkey string) []byte { +func parseMasterKey(masterkey string, fromStdin bool) []byte {  	masterkey = strings.Replace(masterkey, "-", "", -1)  	key, err := hex.DecodeString(masterkey)  	if err != nil { @@ -58,8 +58,10 @@ func parseMasterKey(masterkey string) []byte {  		os.Exit(exitcodes.MasterKey)  	}  	tlog.Info.Printf("Using explicit master key.") -	tlog.Info.Printf(tlog.ColorYellow + -		"THE MASTER KEY IS VISIBLE VIA \"ps ax\" AND MAY BE STORED IN YOUR SHELL HISTORY!\n" + -		"ONLY USE THIS MODE FOR EMERGENCIES." + tlog.ColorReset) +	if !fromStdin { +		tlog.Info.Printf(tlog.ColorYellow + +			"THE MASTER KEY IS VISIBLE VIA \"ps ax\" AND MAY BE STORED IN YOUR SHELL HISTORY!\n" + +			"ONLY USE THIS MODE FOR EMERGENCIES" + tlog.ColorReset) +	}  	return key  } @@ -98,9 +98,14 @@ func doMount(args *argContainer) {  	{  		// Get master key (may prompt for the password)  		var masterkey []byte +		masterkeyFromStdin := false +		if args.masterkey == "stdin" { +			args.masterkey = string(readpassword.Once("", "Masterkey")) +			masterkeyFromStdin = true +		}  		if args.masterkey != "" {  			// "-masterkey" -			masterkey = parseMasterKey(args.masterkey) +			masterkey = parseMasterKey(args.masterkey, masterkeyFromStdin)  		} else if args.zerokey {  			// "-zerokey"  			tlog.Info.Printf("Using all-zero dummy master key.") @@ -354,7 +359,7 @@ func initFuseFrontend(masterkey []byte, args *argContainer, confFile *configfile  	}  	srv, err := fuse.NewServer(conn.RawFS(), args.mountpoint, &mOpts)  	if err != nil { -		tlog.Fatal.Printf("fuse.NewServer failed: %v", err) +		tlog.Fatal.Printf("fuse.NewServer failed: %q", err)  		if runtime.GOOS == "darwin" {  			tlog.Info.Printf("Maybe you should run: /Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse")  		} diff --git a/tests/example_filesystems/example_filesystems_test.go b/tests/example_filesystems/example_filesystems_test.go index 5979bdc..4f29f8d 100644 --- a/tests/example_filesystems/example_filesystems_test.go +++ b/tests/example_filesystems/example_filesystems_test.go @@ -10,6 +10,7 @@ import (  	"flag"  	"fmt"  	"os" +	"os/exec"  	"syscall"  	"testing" @@ -252,6 +253,38 @@ func TestExampleFSv13(t *testing.T) {  	test_helpers.UnmountPanic(pDir)  } +// Check that the masterkey=stdin cli option works. +func TestExampleFSv13MasterkeyStdin(t *testing.T) { +	cDir := "v1.3" +	pDir := test_helpers.TmpDir + "/TestExampleFSv13MasterkeyStdin.mnt" +	err := os.Mkdir(pDir, 0777) +	if err != nil { +		t.Fatal(err) +	} +	args := []string{"-q", "-masterkey=stdin", opensslOpt, cDir, pDir} +	cmd := exec.Command(test_helpers.GocryptfsBinary, args...) +	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) +	} +	// Write masterkey to stdin +	p.Write([]byte("fd890dab-86bf61cf-ec5ad460-ad3ed01f-9c52d546-2a31783d-a56b088d-3d05232e")) +	p.Close() +	err = cmd.Wait() +	if err != nil { +		t.Error(err) +	} +	// Check that the fs decrypts ok & unmount +	checkExampleFSLongnames(t, pDir) +	test_helpers.UnmountPanic(pDir) +} +  // gocryptfs v1.3 introduced HKDF.  // We check the md5 sum of the encrypted version of a file to make sure we don't  // accidentially change the ciphertext generation. | 
