passfile: directly read file instead of invoking cat
Allows better error handling, gets rid of the call to an external program, and fixes https://github.com/rfjakob/gocryptfs/issues/278 .
This commit is contained in:
parent
b29ee62749
commit
295d432175
@ -262,8 +262,13 @@ you are using Go 1.6+. In mode "auto", gocrypts chooses the faster
|
|||||||
option.
|
option.
|
||||||
|
|
||||||
#### -passfile string
|
#### -passfile string
|
||||||
Read password from the specified file. This is a shortcut for
|
Read password from the specified file. A warning will be printed if there
|
||||||
specifying '-extpass="/bin/cat -- FILE"'.
|
is more than one line, and only the first line will be used. A single
|
||||||
|
trailing newline is allowed and does not cause a warning.
|
||||||
|
|
||||||
|
Before gocryptfs v1.7, using `-passfile` was equivant to writing
|
||||||
|
`-extpass="/bin/cat -- FILE"`.
|
||||||
|
gocryptfs v1.7 and later directly read the file without invoking `cat`.
|
||||||
|
|
||||||
#### -passwd
|
#### -passwd
|
||||||
Change the password. Will ask for the old password, check if it is
|
Change the password. Will ask for the old password, check if it is
|
||||||
|
10
cli_args.go
10
cli_args.go
@ -243,9 +243,13 @@ func parseCliOpts() (args argContainer) {
|
|||||||
args.allow_other = false
|
args.allow_other = false
|
||||||
args.ko = "noexec"
|
args.ko = "noexec"
|
||||||
}
|
}
|
||||||
// '-passfile FILE' is a shortcut for -extpass='/bin/cat -- FILE'
|
if args.extpass != "" && args.passfile != "" {
|
||||||
if args.passfile != "" {
|
tlog.Fatal.Printf("The options -extpass and -passfile cannot be used at the same time")
|
||||||
args.extpass = "/bin/cat -- " + args.passfile
|
os.Exit(exitcodes.Usage)
|
||||||
|
}
|
||||||
|
if args.passfile != "" && args.masterkey != "" {
|
||||||
|
tlog.Fatal.Printf("The options -passfile and -masterkey cannot be used at the same time")
|
||||||
|
os.Exit(exitcodes.Usage)
|
||||||
}
|
}
|
||||||
if args.extpass != "" && args.masterkey != "" {
|
if args.extpass != "" && args.masterkey != "" {
|
||||||
tlog.Fatal.Printf("The options -extpass and -masterkey cannot be used at the same time")
|
tlog.Fatal.Printf("The options -extpass and -masterkey cannot be used at the same time")
|
||||||
|
@ -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.LoadAndDecrypt(fn, pw)
|
masterkey, _, err := configfile.LoadAndDecrypt(fn, pw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
@ -78,7 +78,7 @@ func initDir(args *argContainer) {
|
|||||||
password = readpassword.Trezor(trezorPayload)
|
password = readpassword.Trezor(trezorPayload)
|
||||||
} else {
|
} else {
|
||||||
// Normal password entry
|
// Normal password entry
|
||||||
password = readpassword.Twice(args.extpass)
|
password = readpassword.Twice(args.extpass, args.passfile)
|
||||||
readpassword.CheckTrailingGarbage()
|
readpassword.CheckTrailingGarbage()
|
||||||
}
|
}
|
||||||
creator := tlog.ProgramName + " " + GitVersion
|
creator := tlog.ProgramName + " " + GitVersion
|
||||||
|
@ -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,14 +34,16 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When extpass returns an empty string, we should crash.
|
// 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) {
|
func TestExtpassEmpty(t *testing.T) {
|
||||||
if os.Getenv("TEST_SLAVE") == "1" {
|
if os.Getenv("TEST_SLAVE") == "1" {
|
||||||
readPasswordExtpass("echo")
|
readPasswordExtpass("echo")
|
||||||
|
43
internal/readpassword/passfile.go
Normal file
43
internal/readpassword/passfile.go
Normal file
@ -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]
|
||||||
|
}
|
80
internal/readpassword/passfile_test.go
Normal file
80
internal/readpassword/passfile_test.go
Normal file
@ -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")
|
||||||
|
}
|
0
internal/readpassword/passfile_test_files/empty.txt
Normal file
0
internal/readpassword/passfile_test_files/empty.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
garbage
|
1
internal/readpassword/passfile_test_files/mypassword.txt
Normal file
1
internal/readpassword/passfile_test_files/mypassword.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
mypassword
|
@ -0,0 +1,2 @@
|
|||||||
|
mypassword
|
||||||
|
garbage
|
@ -0,0 +1 @@
|
|||||||
|
mypassword
|
1
internal/readpassword/passfile_test_files/newline.txt
Normal file
1
internal/readpassword/passfile_test_files/newline.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -24,7 +24,10 @@ 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. Leave "prompt" empty to use the default "Password: " prompt.
|
// 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 != "" {
|
if extpass != "" {
|
||||||
return readPasswordExtpass(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
|
// Twice is the same as Once but will prompt twice if we get the password from
|
||||||
// the terminal.
|
// the terminal.
|
||||||
func Twice(extpass string) []byte {
|
func Twice(extpass string, passfile string) []byte {
|
||||||
|
if passfile != "" {
|
||||||
|
return readPassFile(passfile)
|
||||||
|
}
|
||||||
if extpass != "" {
|
if extpass != "" {
|
||||||
return readPasswordExtpass(extpass)
|
return readPasswordExtpass(extpass)
|
||||||
}
|
}
|
||||||
@ -95,16 +101,7 @@ func readPasswordStdin(prompt string) []byte {
|
|||||||
// Exits on read error or empty result.
|
// Exits on read error or empty result.
|
||||||
func readPasswordExtpass(extpass string) []byte {
|
func readPasswordExtpass(extpass string) []byte {
|
||||||
tlog.Info.Println("Reading password from extpass program")
|
tlog.Info.Println("Reading password from extpass program")
|
||||||
var parts []string
|
parts := strings.Split(extpass, " ")
|
||||||
// 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, " ")
|
|
||||||
}
|
|
||||||
cmd := exec.Command(parts[0], parts[1:]...)
|
cmd := exec.Command(parts[0], parts[1:]...)
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
pipe, err := cmd.StdoutPipe()
|
pipe, err := cmd.StdoutPipe()
|
||||||
|
4
main.go
4
main.go
@ -53,7 +53,7 @@ func loadConfig(args *argContainer) (masterkey []byte, cf *configfile.ConfFile,
|
|||||||
pw = readpassword.Trezor(cf.TrezorPayload)
|
pw = readpassword.Trezor(cf.TrezorPayload)
|
||||||
} else {
|
} else {
|
||||||
// Normal password entry
|
// Normal password entry
|
||||||
pw = readpassword.Once(args.extpass, "")
|
pw = readpassword.Once(args.extpass, args.passfile, "")
|
||||||
}
|
}
|
||||||
tlog.Info.Println("Decrypting master key")
|
tlog.Info.Println("Decrypting master key")
|
||||||
masterkey, err = cf.DecryptMasterKey(pw)
|
masterkey, err = cf.DecryptMasterKey(pw)
|
||||||
@ -93,7 +93,7 @@ func changePassword(args *argContainer) {
|
|||||||
log.Panic("empty masterkey")
|
log.Panic("empty masterkey")
|
||||||
}
|
}
|
||||||
tlog.Info.Println("Please enter your new password.")
|
tlog.Info.Println("Please enter your new password.")
|
||||||
newPw := readpassword.Twice(args.extpass)
|
newPw := readpassword.Twice(args.extpass, args.passfile)
|
||||||
readpassword.CheckTrailingGarbage()
|
readpassword.CheckTrailingGarbage()
|
||||||
confFile.EncryptKey(masterkey, newPw, confFile.ScryptObject.LogN())
|
confFile.EncryptKey(masterkey, newPw, confFile.ScryptObject.LogN())
|
||||||
for i := range newPw {
|
for i := range newPw {
|
||||||
|
@ -43,7 +43,7 @@ func getMasterKey(args *argContainer) (masterkey []byte, confFile *configfile.Co
|
|||||||
masterkeyFromStdin := false
|
masterkeyFromStdin := false
|
||||||
// "-masterkey=stdin"
|
// "-masterkey=stdin"
|
||||||
if args.masterkey == "stdin" {
|
if args.masterkey == "stdin" {
|
||||||
args.masterkey = string(readpassword.Once("", "Masterkey"))
|
args.masterkey = string(readpassword.Once("", "", "Masterkey"))
|
||||||
masterkeyFromStdin = true
|
masterkeyFromStdin = true
|
||||||
}
|
}
|
||||||
// "-masterkey=941a6029-3adc6a1c-..."
|
// "-masterkey=941a6029-3adc6a1c-..."
|
||||||
|
Loading…
Reference in New Issue
Block a user