readpassword: bubble up errors instead of exiting the process
This allows cleanups to happen in the caller, like removing the control socket. Fixes https://github.com/rfjakob/gocryptfs/issues/634
This commit is contained in:
parent
1eaf1211a2
commit
4b251f3ce1
@ -148,7 +148,11 @@ func dumpMasterKey(fn string, fido2Path string) {
|
|||||||
}
|
}
|
||||||
pw = fido2.Secret(fido2Path, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
|
pw = fido2.Secret(fido2Path, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
|
||||||
} else {
|
} else {
|
||||||
pw = readpassword.Once(nil, nil, "")
|
pw, err = readpassword.Once(nil, nil, "")
|
||||||
|
if err != nil {
|
||||||
|
tlog.Fatal.Println(err)
|
||||||
|
os.Exit(exitcodes.ReadPassword)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
masterkey, err := cf.DecryptMasterKey(pw)
|
masterkey, err := cf.DecryptMasterKey(pw)
|
||||||
// Purge password from memory
|
// Purge password from memory
|
||||||
|
@ -87,7 +87,11 @@ func initDir(args *argContainer) {
|
|||||||
password = fido2.Secret(args.fido2, fido2CredentialID, fido2HmacSalt)
|
password = fido2.Secret(args.fido2, fido2CredentialID, fido2HmacSalt)
|
||||||
} else {
|
} else {
|
||||||
// normal password entry
|
// normal password entry
|
||||||
password = readpassword.Twice([]string(args.extpass), []string(args.passfile))
|
password, err = readpassword.Twice([]string(args.extpass), []string(args.passfile))
|
||||||
|
if err != nil {
|
||||||
|
tlog.Fatal.Println(err)
|
||||||
|
os.Exit(exitcodes.ReadPassword)
|
||||||
|
}
|
||||||
fido2CredentialID = nil
|
fido2CredentialID = nil
|
||||||
fido2HmacSalt = nil
|
fido2HmacSalt = nil
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package readpassword
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
||||||
@ -16,68 +15,76 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
func TestExtpass(t *testing.T) {
|
func TestExtpass(t *testing.T) {
|
||||||
p1 := "ads2q4tw41reg52"
|
p1 := "ads2q4tw41reg52"
|
||||||
p2 := string(readPasswordExtpass([]string{"echo " + p1}))
|
p2, err := readPasswordExtpass([]string{"echo " + p1})
|
||||||
if p1 != p2 {
|
if err != nil {
|
||||||
t.Errorf("p1=%q != p2=%q", p1, p2)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if p1 != string(p2) {
|
||||||
|
t.Errorf("p1=%q != p2=%q", p1, string(p2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOnceExtpass(t *testing.T) {
|
func TestOnceExtpass(t *testing.T) {
|
||||||
p1 := "lkadsf0923rdfi48rqwhdsf"
|
p1 := "lkadsf0923rdfi48rqwhdsf"
|
||||||
p2 := string(Once([]string{"echo " + p1}, nil, ""))
|
p2, err := Once([]string{"echo " + p1}, nil, "")
|
||||||
if p1 != p2 {
|
if err != nil {
|
||||||
t.Errorf("p1=%q != p2=%q", p1, p2)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if p1 != string(p2) {
|
||||||
|
t.Errorf("p1=%q != p2=%q", p1, string(p2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// extpass with two arguments
|
// extpass with two arguments
|
||||||
func TestOnceExtpass2(t *testing.T) {
|
func TestOnceExtpass2(t *testing.T) {
|
||||||
p1 := "foo"
|
p1 := "foo"
|
||||||
p2 := string(Once([]string{"echo", p1}, nil, ""))
|
p2, err := Once([]string{"echo", p1}, nil, "")
|
||||||
if p1 != p2 {
|
if err != nil {
|
||||||
t.Errorf("p1=%q != p2=%q", p1, p2)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if p1 != string(p2) {
|
||||||
|
t.Errorf("p1=%q != p2=%q", p1, string(p2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// extpass with three arguments
|
// extpass with three arguments
|
||||||
func TestOnceExtpass3(t *testing.T) {
|
func TestOnceExtpass3(t *testing.T) {
|
||||||
p1 := "foo bar baz"
|
p1 := "foo bar baz"
|
||||||
p2 := string(Once([]string{"echo", "foo", "bar", "baz"}, nil, ""))
|
p2, err := Once([]string{"echo", "foo", "bar", "baz"}, nil, "")
|
||||||
if p1 != p2 {
|
if err != nil {
|
||||||
t.Errorf("p1=%q != p2=%q", p1, p2)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if p1 != string(p2) {
|
||||||
|
t.Errorf("p1=%q != p2=%q", p1, string(p2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOnceExtpassSpaces(t *testing.T) {
|
func TestOnceExtpassSpaces(t *testing.T) {
|
||||||
p1 := "mypassword"
|
p1 := "mypassword"
|
||||||
p2 := string(Once([]string{"cat", "passfile_test_files/file with spaces.txt"}, nil, ""))
|
p2, err := Once([]string{"cat", "passfile_test_files/file with spaces.txt"}, nil, "")
|
||||||
if p1 != p2 {
|
if err != nil {
|
||||||
t.Errorf("p1=%q != p2=%q", p1, p2)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if p1 != string(p2) {
|
||||||
|
t.Errorf("p1=%q != p2=%q", p1, string(p2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTwiceExtpass(t *testing.T) {
|
func TestTwiceExtpass(t *testing.T) {
|
||||||
p1 := "w5w44t3wfe45srz434"
|
p1 := "w5w44t3wfe45srz434"
|
||||||
p2 := string(Once([]string{"echo " + p1}, nil, ""))
|
p2, err := Once([]string{"echo " + p1}, nil, "")
|
||||||
if p1 != p2 {
|
if err != nil {
|
||||||
t.Errorf("p1=%q != p2=%q", p1, p2)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if p1 != string(p2) {
|
||||||
|
t.Errorf("p1=%q != p2=%q", p1, string(p2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When extpass returns an empty string, we should crash.
|
// Empty extpass should fail
|
||||||
//
|
|
||||||
// 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" {
|
_, err := readPasswordExtpass([]string{"echo"})
|
||||||
readPasswordExtpass([]string{"echo"})
|
if err == nil {
|
||||||
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")
|
t.Fatal("empty password should have failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,28 +2,31 @@ package readpassword
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
|
|
||||||
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// readPassFileConcatenate reads the first line from each file name and
|
// readPassFileConcatenate reads the first line from each file name and
|
||||||
// concatenates the results. The result does not contain any newlines.
|
// concatenates the results. The result does not contain any newlines.
|
||||||
func readPassFileConcatenate(passfileSlice []string) (result []byte) {
|
func readPassFileConcatenate(passfileSlice []string) (result []byte, err error) {
|
||||||
for _, e := range passfileSlice {
|
for _, e := range passfileSlice {
|
||||||
result = append(result, readPassFile(e)...)
|
add, err := readPassFile(e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return result
|
result = append(result, add...)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPassFile reads the first line from the passed file name.
|
// readPassFile reads the first line from the passed file name.
|
||||||
func readPassFile(passfile string) []byte {
|
func readPassFile(passfile string) ([]byte, error) {
|
||||||
tlog.Info.Printf("passfile: reading from file %q", passfile)
|
tlog.Info.Printf("passfile: reading from file %q", passfile)
|
||||||
f, err := os.Open(passfile)
|
f, err := os.Open(passfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Printf("fatal: passfile: could not open %q: %v", passfile, err)
|
return nil, fmt.Errorf("fatal: passfile: could not open %q: %v", passfile, err)
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
// +1 for an optional trailing newline,
|
// +1 for an optional trailing newline,
|
||||||
@ -31,23 +34,20 @@ func readPassFile(passfile string) []byte {
|
|||||||
buf := make([]byte, maxPasswordLen+2)
|
buf := make([]byte, maxPasswordLen+2)
|
||||||
n, err := f.Read(buf)
|
n, err := f.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Printf("fatal: passfile: could not read from %q: %v", passfile, err)
|
return nil, fmt.Errorf("fatal: passfile: could not read from %q: %v", passfile, err)
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
buf = buf[:n]
|
buf = buf[:n]
|
||||||
// Split into first line and "trailing garbage"
|
// Split into first line and "trailing garbage"
|
||||||
lines := bytes.SplitN(buf, []byte("\n"), 2)
|
lines := bytes.SplitN(buf, []byte("\n"), 2)
|
||||||
if len(lines[0]) == 0 {
|
if len(lines[0]) == 0 {
|
||||||
tlog.Fatal.Printf("fatal: passfile: empty first line in %q", passfile)
|
return nil, fmt.Errorf("fatal: passfile: empty first line in %q", passfile)
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
if len(lines[0]) > maxPasswordLen {
|
if len(lines[0]) > maxPasswordLen {
|
||||||
tlog.Fatal.Printf("fatal: passfile: max password length (%d bytes) exceeded", maxPasswordLen)
|
return nil, fmt.Errorf("fatal: passfile: max password length (%d bytes) exceeded", maxPasswordLen)
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
if len(lines) > 1 && len(lines[1]) > 0 {
|
if len(lines) > 1 && len(lines[1]) > 0 {
|
||||||
tlog.Warn.Printf("warning: passfile: ignoring trailing garbage (%d bytes) after first line",
|
tlog.Warn.Printf("warning: passfile: ignoring trailing garbage (%d bytes) after first line",
|
||||||
len(lines[1]))
|
len(lines[1]))
|
||||||
}
|
}
|
||||||
return lines[0]
|
return lines[0], nil
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package readpassword
|
package readpassword
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,76 +15,49 @@ func TestPassfile(t *testing.T) {
|
|||||||
{"file with spaces.txt", "mypassword"},
|
{"file with spaces.txt", "mypassword"},
|
||||||
}
|
}
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
pw := readPassFile("passfile_test_files/" + tc.file)
|
pw, err := readPassFile("passfile_test_files/" + tc.file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if string(pw) != tc.want {
|
if string(pw) != tc.want {
|
||||||
t.Errorf("Wrong result: want=%q have=%q", tc.want, pw)
|
t.Errorf("Wrong result: want=%q have=%q", tc.want, pw)
|
||||||
}
|
}
|
||||||
// Calling readPassFileConcatenate with only one element should give the
|
// Calling readPassFileConcatenate with only one element should give the
|
||||||
// same result
|
// same result
|
||||||
pw = readPassFileConcatenate([]string{"passfile_test_files/" + tc.file})
|
pw, err = readPassFileConcatenate([]string{"passfile_test_files/" + tc.file})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if string(pw) != tc.want {
|
if string(pw) != tc.want {
|
||||||
t.Errorf("Wrong result: want=%q have=%q", tc.want, pw)
|
t.Errorf("Wrong result: want=%q have=%q", tc.want, pw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPassFile() should exit instead of returning an empty string.
|
// readPassFile() should fail instead of returning an empty string.
|
||||||
//
|
|
||||||
// The TEST_SLAVE magic is explained at
|
|
||||||
// https://talks.golang.org/2014/testing.slide#23 , mirror:
|
|
||||||
// http://web.archive.org/web/20200426174352/https://talks.golang.org/2014/testing.slide#23
|
|
||||||
func TestPassfileEmpty(t *testing.T) {
|
func TestPassfileEmpty(t *testing.T) {
|
||||||
if os.Getenv("TEST_SLAVE") == "1" {
|
_, err := readPassFile("passfile_test_files/empty.txt")
|
||||||
readPassFile("passfile_test_files/empty.txt")
|
if err == nil {
|
||||||
return
|
t.Fatal("should have failed")
|
||||||
}
|
}
|
||||||
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.
|
// File containing just a newline.
|
||||||
// readPassFile() should exit instead of returning an empty string.
|
// readPassFile() should fal instead of returning an empty string.
|
||||||
//
|
|
||||||
// The TEST_SLAVE magic is explained at
|
|
||||||
// https://talks.golang.org/2014/testing.slide#23 , mirror:
|
|
||||||
// http://web.archive.org/web/20200426174352/https://talks.golang.org/2014/testing.slide#23
|
|
||||||
func TestPassfileNewline(t *testing.T) {
|
func TestPassfileNewline(t *testing.T) {
|
||||||
if os.Getenv("TEST_SLAVE") == "1" {
|
_, err := readPassFile("passfile_test_files/newline.txt")
|
||||||
readPassFile("passfile_test_files/newline.txt")
|
if err == nil {
|
||||||
return
|
t.Fatal("should have failed")
|
||||||
}
|
}
|
||||||
cmd := exec.Command(os.Args[0], "-test.run=TestPassfileNewline$")
|
|
||||||
cmd.Env = append(os.Environ(), "TEST_SLAVE=1")
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Fatal("should have exited")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// File containing "\ngarbage".
|
// File containing "\ngarbage".
|
||||||
// readPassFile() should exit instead of returning an empty string.
|
// readPassFile() should return an error.
|
||||||
//
|
|
||||||
// The TEST_SLAVE magic is explained at
|
|
||||||
// https://talks.golang.org/2014/testing.slide#23 , mirror:
|
|
||||||
// http://web.archive.org/web/20200426174352/https://talks.golang.org/2014/testing.slide#23
|
|
||||||
func TestPassfileEmptyFirstLine(t *testing.T) {
|
func TestPassfileEmptyFirstLine(t *testing.T) {
|
||||||
if os.Getenv("TEST_SLAVE") == "1" {
|
_, err := readPassFile("passfile_test_files/empty_first_line.txt")
|
||||||
readPassFile("passfile_test_files/empty_first_line.txt")
|
if err == nil {
|
||||||
return
|
t.Fatal("should have failed")
|
||||||
}
|
}
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPassFileConcatenate tests readPassFileConcatenate
|
// TestPassFileConcatenate tests readPassFileConcatenate
|
||||||
@ -95,8 +66,11 @@ func TestPassFileConcatenate(t *testing.T) {
|
|||||||
"passfile_test_files/file with spaces.txt",
|
"passfile_test_files/file with spaces.txt",
|
||||||
"passfile_test_files/mypassword_garbage.txt",
|
"passfile_test_files/mypassword_garbage.txt",
|
||||||
}
|
}
|
||||||
res := string(readPassFileConcatenate(files))
|
res, err := readPassFileConcatenate(files)
|
||||||
if res != "mypasswordmypassword" {
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(res) != "mypasswordmypassword" {
|
||||||
t.Errorf("wrong result: %q", res)
|
t.Errorf("wrong result: %q", res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
|
|
||||||
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +21,7 @@ const (
|
|||||||
|
|
||||||
// Once tries to get a password from the user, either from the terminal, extpass, passfile
|
// Once tries to get a password from the user, either from the terminal, extpass, passfile
|
||||||
// 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, passfile []string, prompt string) []byte {
|
func Once(extpass []string, passfile []string, prompt string) ([]byte, error) {
|
||||||
if len(passfile) != 0 {
|
if len(passfile) != 0 {
|
||||||
return readPassFileConcatenate(passfile)
|
return readPassFileConcatenate(passfile)
|
||||||
}
|
}
|
||||||
@ -40,7 +39,7 @@ func Once(extpass []string, passfile []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, passfile []string) []byte {
|
func Twice(extpass []string, passfile []string) ([]byte, error) {
|
||||||
if len(passfile) != 0 {
|
if len(passfile) != 0 {
|
||||||
return readPassFileConcatenate(passfile)
|
return readPassFileConcatenate(passfile)
|
||||||
}
|
}
|
||||||
@ -50,54 +49,59 @@ func Twice(extpass []string, passfile []string) []byte {
|
|||||||
if !terminal.IsTerminal(int(os.Stdin.Fd())) {
|
if !terminal.IsTerminal(int(os.Stdin.Fd())) {
|
||||||
return readPasswordStdin("Password")
|
return readPasswordStdin("Password")
|
||||||
}
|
}
|
||||||
p1 := readPasswordTerminal("Password: ")
|
p1, err := readPasswordTerminal("Password: ")
|
||||||
p2 := readPasswordTerminal("Repeat: ")
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p2, err := readPasswordTerminal("Repeat: ")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if !bytes.Equal(p1, p2) {
|
if !bytes.Equal(p1, p2) {
|
||||||
tlog.Fatal.Println("Passwords do not match")
|
return nil, fmt.Errorf("Passwords do not match")
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
// Wipe the password duplicate from memory
|
// Wipe the password duplicate from memory
|
||||||
for i := range p2 {
|
for i := range p2 {
|
||||||
p2[i] = 0
|
p2[i] = 0
|
||||||
}
|
}
|
||||||
return p1
|
return p1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPasswordTerminal reads a line from the terminal.
|
// readPasswordTerminal reads a line from the terminal.
|
||||||
// Exits on read error or empty result.
|
// Exits on read error or empty result.
|
||||||
func readPasswordTerminal(prompt string) []byte {
|
func readPasswordTerminal(prompt string) ([]byte, error) {
|
||||||
fd := int(os.Stdin.Fd())
|
fd := int(os.Stdin.Fd())
|
||||||
fmt.Fprintf(os.Stderr, prompt)
|
fmt.Fprintf(os.Stderr, prompt)
|
||||||
// terminal.ReadPassword removes the trailing newline
|
// terminal.ReadPassword removes the trailing newline
|
||||||
p, err := terminal.ReadPassword(fd)
|
p, err := terminal.ReadPassword(fd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Printf("Could not read password from terminal: %v\n", err)
|
return nil, fmt.Errorf("Could not read password from terminal: %v\n", err)
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
if len(p) == 0 {
|
if len(p) == 0 {
|
||||||
tlog.Fatal.Println("Password is empty")
|
return nil, fmt.Errorf("Password is empty")
|
||||||
os.Exit(exitcodes.PasswordEmpty)
|
|
||||||
}
|
}
|
||||||
return p
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(prompt string) []byte {
|
func readPasswordStdin(prompt string) ([]byte, error) {
|
||||||
tlog.Info.Printf("Reading %s from stdin", prompt)
|
tlog.Info.Printf("Reading %s from stdin", prompt)
|
||||||
p := readLineUnbuffered(os.Stdin)
|
p, err := readLineUnbuffered(os.Stdin)
|
||||||
if len(p) == 0 {
|
if err != nil {
|
||||||
tlog.Fatal.Printf("Got empty %s from stdin", prompt)
|
return nil, err
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
return p
|
if len(p) == 0 {
|
||||||
|
return nil, fmt.Errorf("Got empty %s from stdin", prompt)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPasswordExtpass executes the "extpass" program and returns the first line
|
// readPasswordExtpass executes the "extpass" program and returns the first line
|
||||||
// of the output.
|
// of the output.
|
||||||
// Exits on read error or empty result.
|
// Exits on read error or empty result.
|
||||||
func readPasswordExtpass(extpass []string) []byte {
|
func readPasswordExtpass(extpass []string) ([]byte, error) {
|
||||||
var parts []string
|
var parts []string
|
||||||
if len(extpass) == 1 {
|
if len(extpass) == 1 {
|
||||||
parts = strings.Split(extpass[0], " ")
|
parts = strings.Split(extpass[0], " ")
|
||||||
@ -109,50 +113,47 @@ func readPasswordExtpass(extpass []string) []byte {
|
|||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
pipe, err := cmd.StdoutPipe()
|
pipe, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Printf("extpass pipe setup failed: %v", err)
|
return nil, fmt.Errorf("extpass pipe setup failed: %v", err)
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Printf("extpass cmd start failed: %v", err)
|
return nil, fmt.Errorf("extpass cmd start failed: %v", err)
|
||||||
os.Exit(exitcodes.ReadPassword)
|
}
|
||||||
|
p, err := readLineUnbuffered(pipe)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
p := readLineUnbuffered(pipe)
|
|
||||||
pipe.Close()
|
pipe.Close()
|
||||||
err = cmd.Wait()
|
err = cmd.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Printf("extpass program returned an error: %v", err)
|
return nil, fmt.Errorf("extpass program returned an error: %v", err)
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
if len(p) == 0 {
|
if len(p) == 0 {
|
||||||
tlog.Fatal.Println("extpass: password is empty")
|
return nil, fmt.Errorf("extpass: password is empty")
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
return p
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readLineUnbuffered reads single bytes from "r" util it gets "\n" or EOF.
|
// readLineUnbuffered reads single bytes from "r" util it gets "\n" or EOF.
|
||||||
// The returned string does NOT contain the trailing "\n".
|
// The returned string does NOT contain the trailing "\n".
|
||||||
func readLineUnbuffered(r io.Reader) (l []byte) {
|
func readLineUnbuffered(r io.Reader) (l []byte, err error) {
|
||||||
b := make([]byte, 1)
|
b := make([]byte, 1)
|
||||||
for {
|
for {
|
||||||
if len(l) > maxPasswordLen {
|
if len(l) > maxPasswordLen {
|
||||||
tlog.Fatal.Printf("fatal: maximum password length of %d bytes exceeded", maxPasswordLen)
|
return nil, fmt.Errorf("fatal: maximum password length of %d bytes exceeded", maxPasswordLen)
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
n, err := r.Read(b)
|
n, err := r.Read(b)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return l
|
return l, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Printf("readLineUnbuffered: %v", err)
|
return nil, fmt.Errorf("readLineUnbuffered: %v", err)
|
||||||
os.Exit(exitcodes.ReadPassword)
|
|
||||||
}
|
}
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if b[0] == '\n' {
|
if b[0] == '\n' {
|
||||||
return l
|
return l, nil
|
||||||
}
|
}
|
||||||
l = append(l, b...)
|
l = append(l, b...)
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Provide password via stdin, terminated by "\n".
|
// Provide password via stdin, terminated by "\n".
|
||||||
|
//
|
||||||
|
// The TEST_SLAVE magic is explained at
|
||||||
|
// https://talks.golang.org/2014/testing.slide#23 , mirror:
|
||||||
|
// http://web.archive.org/web/20200426174352/https://talks.golang.org/2014/testing.slide#23
|
||||||
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("foo"))
|
p2, err := readPasswordStdin("foo")
|
||||||
if p1 != p2 {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%q != %q", p1, p2)
|
fmt.Fprint(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if p1 != string(p2) {
|
||||||
|
fmt.Fprintf(os.Stderr, "%q != %q", p1, string(p2))
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -41,12 +49,20 @@ func TestStdin(t *testing.T) {
|
|||||||
|
|
||||||
// Provide password via stdin, terminated by EOF (pipe close). This should not
|
// Provide password via stdin, terminated by EOF (pipe close). This should not
|
||||||
// hang.
|
// hang.
|
||||||
|
//
|
||||||
|
// The TEST_SLAVE magic is explained at
|
||||||
|
// https://talks.golang.org/2014/testing.slide#23 , mirror:
|
||||||
|
// http://web.archive.org/web/20200426174352/https://talks.golang.org/2014/testing.slide#23
|
||||||
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("foo"))
|
p2, err := readPasswordStdin("foo")
|
||||||
if p1 != p2 {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%q != %q", p1, p2)
|
fmt.Fprint(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if p1 != string(p2) {
|
||||||
|
fmt.Fprintf(os.Stderr, "%q != %q", p1, string(p2))
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -76,7 +92,10 @@ 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("foo")
|
_, err := readPasswordStdin("foo")
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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")
|
||||||
|
14
main.go
14
main.go
@ -55,11 +55,15 @@ func loadConfig(args *argContainer) (masterkey []byte, cf *configfile.ConfFile,
|
|||||||
if cf.IsFeatureFlagSet(configfile.FlagFIDO2) {
|
if cf.IsFeatureFlagSet(configfile.FlagFIDO2) {
|
||||||
if args.fido2 == "" {
|
if args.fido2 == "" {
|
||||||
tlog.Fatal.Printf("Masterkey encrypted using FIDO2 token; need to use the --fido2 option.")
|
tlog.Fatal.Printf("Masterkey encrypted using FIDO2 token; need to use the --fido2 option.")
|
||||||
os.Exit(exitcodes.Usage)
|
return nil, nil, exitcodes.NewErr("", exitcodes.Usage)
|
||||||
}
|
}
|
||||||
pw = fido2.Secret(args.fido2, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
|
pw = fido2.Secret(args.fido2, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
|
||||||
} else {
|
} else {
|
||||||
pw = readpassword.Once([]string(args.extpass), []string(args.passfile), "")
|
pw, err = readpassword.Once([]string(args.extpass), []string(args.passfile), "")
|
||||||
|
if err != nil {
|
||||||
|
tlog.Fatal.Println(err)
|
||||||
|
return nil, nil, exitcodes.NewErr("", exitcodes.ReadPassword)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tlog.Info.Println("Decrypting master key")
|
tlog.Info.Println("Decrypting master key")
|
||||||
masterkey, err = cf.DecryptMasterKey(pw)
|
masterkey, err = cf.DecryptMasterKey(pw)
|
||||||
@ -93,7 +97,11 @@ func changePassword(args *argContainer) {
|
|||||||
os.Exit(exitcodes.Usage)
|
os.Exit(exitcodes.Usage)
|
||||||
}
|
}
|
||||||
tlog.Info.Println("Please enter your new password.")
|
tlog.Info.Println("Please enter your new password.")
|
||||||
newPw := readpassword.Twice([]string(args.extpass), []string(args.passfile))
|
newPw, err := readpassword.Twice([]string(args.extpass), []string(args.passfile))
|
||||||
|
if err != nil {
|
||||||
|
tlog.Fatal.Println(err)
|
||||||
|
os.Exit(exitcodes.ReadPassword)
|
||||||
|
}
|
||||||
logN := confFile.ScryptObject.LogN()
|
logN := confFile.ScryptObject.LogN()
|
||||||
if args._explicitScryptn {
|
if args._explicitScryptn {
|
||||||
logN = args.scryptn
|
logN = args.scryptn
|
||||||
|
@ -39,8 +39,12 @@ func unhexMasterKey(masterkey string, fromStdin bool) []byte {
|
|||||||
func handleArgsMasterkey(args *argContainer) (masterkey []byte) {
|
func handleArgsMasterkey(args *argContainer) (masterkey []byte) {
|
||||||
// "-masterkey=stdin"
|
// "-masterkey=stdin"
|
||||||
if args.masterkey == "stdin" {
|
if args.masterkey == "stdin" {
|
||||||
in := string(readpassword.Once(nil, nil, "Masterkey"))
|
in, err := readpassword.Once(nil, nil, "Masterkey")
|
||||||
return unhexMasterKey(in, true)
|
if err != nil {
|
||||||
|
tlog.Fatal.Println(err)
|
||||||
|
os.Exit(exitcodes.ReadPassword)
|
||||||
|
}
|
||||||
|
return unhexMasterKey(string(in), true)
|
||||||
}
|
}
|
||||||
// "-masterkey=941a6029-3adc6a1c-..."
|
// "-masterkey=941a6029-3adc6a1c-..."
|
||||||
if args.masterkey != "" {
|
if args.masterkey != "" {
|
||||||
|
Loading…
Reference in New Issue
Block a user