Add `-masterkey=stdin` functionality

https://github.com/rfjakob/gocryptfs/issues/218
This commit is contained in:
Jakob Unterwurzacher 2018-03-22 00:02:10 +01:00
parent 9c86daf499
commit 9bc039a4ba
10 changed files with 80 additions and 29 deletions

View File

@ -142,20 +142,24 @@ This flag is useful when recovering old gocryptfs filesystems using
"-masterkey". It is ignored (stays at the default) otherwise. "-masterkey". It is ignored (stays at the default) otherwise.
#### -masterkey string #### -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. 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 Note that the command line, and with it the master key, is visible to
anybody on the machine who can execute "ps -auxwww". anybody on the machine who can execute "ps -auxwww". Use "-masterkey=stdin"
This is meant as a recovery option for emergencies, such as if you have to avoid that risk.
forgotten the password or lost the config file.
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 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 settings have to be passed on the command line: `-aessiv` when you
mount a filesystem that was created using reverse mode, or mount a filesystem that was created using reverse mode, or
`-plaintextnames` for a filesystem that was created with that option. `-plaintextnames` for a filesystem that was created with that option.
Example master key: Examples:
6f717d8b-6b5f8e8a-fd0aa206-778ec093-62c5669b-abd229cd-241e00cd-b4d6713d -masterkey=6f717d8b-6b5f8e8a-fd0aa206-778ec093-62c5669b-abd229cd-241e00cd-b4d6713d
-masterkey=stdin
#### -memprofile string #### -memprofile string
Write memory profile to the specified file. This is useful when debugging Write memory profile to the specified file. This is useful when debugging

View File

@ -153,6 +153,10 @@ RM: 4.42
Changelog Changelog
--------- ---------
vNEXT, in progress
* Add `-masterkey=stdin` functionality
([#218](https://github.com/rfjakob/gocryptfs/issues/218))
v1.4.4, 2018-03-18 v1.4.4, 2018-03-18
* Overwrite secrets in memory with zeros as soon as possible * Overwrite secrets in memory with zeros as soon as possible
([#211](https://github.com/rfjakob/gocryptfs/issues/211)) ([#211](https://github.com/rfjakob/gocryptfs/issues/211))

View File

@ -60,7 +60,7 @@ func main() {
func dumpMasterKey(fn string) { func dumpMasterKey(fn string) {
tlog.Info.Enabled = false tlog.Info.Enabled = false
pw := readpassword.Once("") pw := readpassword.Once("", "")
masterkey, _, err := configfile.LoadConfFile(fn, pw) masterkey, _, err := configfile.LoadConfFile(fn, pw)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)

View File

@ -26,7 +26,7 @@ func TestExtpass(t *testing.T) {
func TestOnceExtpass(t *testing.T) { func TestOnceExtpass(t *testing.T) {
p1 := "lkadsf0923rdfi48rqwhdsf" p1 := "lkadsf0923rdfi48rqwhdsf"
p2 := string(Once("echo " + p1)) p2 := string(Once("echo "+p1, ""))
if p1 != p2 { if p1 != p2 {
t.Errorf("p1=%q != p2=%q", p1, p2) t.Errorf("p1=%q != p2=%q", p1, p2)
} }
@ -34,7 +34,7 @@ func TestOnceExtpass(t *testing.T) {
func TestTwiceExtpass(t *testing.T) { func TestTwiceExtpass(t *testing.T) {
p1 := "w5w44t3wfe45srz434" p1 := "w5w44t3wfe45srz434"
p2 := string(Once("echo " + p1)) p2 := string(Once("echo "+p1, ""))
if p1 != p2 { if p1 != p2 {
t.Errorf("p1=%q != p2=%q", p1, p2) t.Errorf("p1=%q != p2=%q", p1, p2)
} }

View File

@ -23,15 +23,18 @@ const (
) )
// Once tries to get a password from the user, either from the terminal, extpass // Once tries to get a password from the user, either from the terminal, extpass
// or stdin. // or stdin. Leave "prompt" empty to use the default "Password: " prompt.
func Once(extpass string) []byte { func Once(extpass string, prompt string) []byte {
if extpass != "" { if extpass != "" {
return readPasswordExtpass(extpass) return readPasswordExtpass(extpass)
} }
if !terminal.IsTerminal(int(os.Stdin.Fd())) { if prompt == "" {
return readPasswordStdin() prompt = "Password"
} }
return readPasswordTerminal("Password: ") if !terminal.IsTerminal(int(os.Stdin.Fd())) {
return readPasswordStdin(prompt)
}
return readPasswordTerminal(prompt + ": ")
} }
// Twice is the same as Once but will prompt twice if we get the password from // 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) return readPasswordExtpass(extpass)
} }
if !terminal.IsTerminal(int(os.Stdin.Fd())) { if !terminal.IsTerminal(int(os.Stdin.Fd())) {
return readPasswordStdin() return readPasswordStdin("Password")
} }
p1 := readPasswordTerminal("Password: ") p1 := readPasswordTerminal("Password: ")
p2 := readPasswordTerminal("Repeat: ") p2 := readPasswordTerminal("Repeat: ")
@ -77,11 +80,11 @@ func readPasswordTerminal(prompt string) []byte {
// readPasswordStdin reads a line from stdin. // readPasswordStdin reads a line from stdin.
// It exits with a fatal error on read error or empty result. // It exits with a fatal error on read error or empty result.
func readPasswordStdin() []byte { func readPasswordStdin(prompt string) []byte {
tlog.Info.Println("Reading password from stdin") tlog.Info.Printf("Reading %s from stdin", prompt)
p := readLineUnbuffered(os.Stdin) p := readLineUnbuffered(os.Stdin)
if len(p) == 0 { 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) os.Exit(exitcodes.ReadPassword)
} }
return p return p

View File

@ -11,7 +11,7 @@ import (
func TestStdin(t *testing.T) { func TestStdin(t *testing.T) {
p1 := "g55434t55wef" p1 := "g55434t55wef"
if os.Getenv("TEST_SLAVE") == "1" { if os.Getenv("TEST_SLAVE") == "1" {
p2 := string(readPasswordStdin()) p2 := string(readPasswordStdin("foo"))
if p1 != p2 { if p1 != p2 {
fmt.Fprintf(os.Stderr, "%q != %q", p1, p2) fmt.Fprintf(os.Stderr, "%q != %q", p1, p2)
os.Exit(1) os.Exit(1)
@ -44,7 +44,7 @@ func TestStdin(t *testing.T) {
func TestStdinEof(t *testing.T) { func TestStdinEof(t *testing.T) {
p1 := "asd45as5f4a36" p1 := "asd45as5f4a36"
if os.Getenv("TEST_SLAVE") == "1" { if os.Getenv("TEST_SLAVE") == "1" {
p2 := string(readPasswordStdin()) p2 := string(readPasswordStdin("foo"))
if p1 != p2 { if p1 != p2 {
fmt.Fprintf(os.Stderr, "%q != %q", p1, p2) fmt.Fprintf(os.Stderr, "%q != %q", p1, p2)
os.Exit(1) os.Exit(1)
@ -76,7 +76,7 @@ func TestStdinEof(t *testing.T) {
// Provide empty password via stdin // Provide empty password via stdin
func TestStdinEmpty(t *testing.T) { func TestStdinEmpty(t *testing.T) {
if os.Getenv("TEST_SLAVE") == "1" { if os.Getenv("TEST_SLAVE") == "1" {
readPasswordStdin() readPasswordStdin("foo")
} }
cmd := exec.Command(os.Args[0], "-test.run=TestStdinEmpty$") cmd := exec.Command(os.Args[0], "-test.run=TestStdinEmpty$")
cmd.Env = append(os.Environ(), "TEST_SLAVE=1") cmd.Env = append(os.Environ(), "TEST_SLAVE=1")

View File

@ -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 // The user has passed the master key (probably because he forgot the
// password). // password).
if args.masterkey != "" { if args.masterkey != "" {
masterkey = parseMasterKey(args.masterkey) masterkey = parseMasterKey(args.masterkey, false)
_, confFile, err = configfile.LoadConfFile(args.config, nil) _, confFile, err = configfile.LoadConfFile(args.config, nil)
} else { } else {
pw := readpassword.Once(args.extpass) pw := readpassword.Once(args.extpass, "")
tlog.Info.Println("Decrypting master key") tlog.Info.Println("Decrypting master key")
masterkey, confFile, err = configfile.LoadConfFile(args.config, pw) masterkey, confFile, err = configfile.LoadConfFile(args.config, pw)
for i := range pw { for i := range pw {

View File

@ -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 // parseMasterKey - Parse a hex-encoded master key that was passed on the command line
// Calls os.Exit on failure // Calls os.Exit on failure
func parseMasterKey(masterkey string) []byte { func parseMasterKey(masterkey string, fromStdin bool) []byte {
masterkey = strings.Replace(masterkey, "-", "", -1) masterkey = strings.Replace(masterkey, "-", "", -1)
key, err := hex.DecodeString(masterkey) key, err := hex.DecodeString(masterkey)
if err != nil { if err != nil {
@ -58,8 +58,10 @@ func parseMasterKey(masterkey string) []byte {
os.Exit(exitcodes.MasterKey) os.Exit(exitcodes.MasterKey)
} }
tlog.Info.Printf("Using explicit master key.") tlog.Info.Printf("Using explicit master key.")
tlog.Info.Printf(tlog.ColorYellow + if !fromStdin {
"THE MASTER KEY IS VISIBLE VIA \"ps ax\" AND MAY BE STORED IN YOUR SHELL HISTORY!\n" + tlog.Info.Printf(tlog.ColorYellow +
"ONLY USE THIS MODE FOR EMERGENCIES." + tlog.ColorReset) "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 return key
} }

View File

@ -98,9 +98,14 @@ func doMount(args *argContainer) {
{ {
// Get master key (may prompt for the password) // Get master key (may prompt for the password)
var masterkey []byte var masterkey []byte
masterkeyFromStdin := false
if args.masterkey == "stdin" {
args.masterkey = string(readpassword.Once("", "Masterkey"))
masterkeyFromStdin = true
}
if args.masterkey != "" { if args.masterkey != "" {
// "-masterkey" // "-masterkey"
masterkey = parseMasterKey(args.masterkey) masterkey = parseMasterKey(args.masterkey, masterkeyFromStdin)
} else if args.zerokey { } else if args.zerokey {
// "-zerokey" // "-zerokey"
tlog.Info.Printf("Using all-zero dummy master key.") 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) srv, err := fuse.NewServer(conn.RawFS(), args.mountpoint, &mOpts)
if err != nil { if err != nil {
tlog.Fatal.Printf("fuse.NewServer failed: %v", err) tlog.Fatal.Printf("fuse.NewServer failed: %q", err)
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
tlog.Info.Printf("Maybe you should run: /Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse") tlog.Info.Printf("Maybe you should run: /Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse")
} }

View File

@ -10,6 +10,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"os/exec"
"syscall" "syscall"
"testing" "testing"
@ -252,6 +253,38 @@ func TestExampleFSv13(t *testing.T) {
test_helpers.UnmountPanic(pDir) 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. // gocryptfs v1.3 introduced HKDF.
// We check the md5 sum of the encrypted version of a file to make sure we don't // We check the md5 sum of the encrypted version of a file to make sure we don't
// accidentially change the ciphertext generation. // accidentially change the ciphertext generation.