From f3c777d5eaa682d878c638192311e52f9c204294 Mon Sep 17 00:00:00 2001 From: Sebastian Lackner Date: Sun, 19 Nov 2017 13:30:04 +0100 Subject: [PATCH] main: Add '-devrandom' commandline option Allows to use /dev/random for generating the master key instead of the default Go implementation. When the kernel random generator has been properly initialized both are considered equally secure, however: * Versions of Go prior to 1.9 just fall back to /dev/urandom if the getrandom() syscall would be blocking (Go Bug #19274) * Kernel versions prior to 3.17 do not support getrandom(), and there is no check if the random generator has been properly initialized before reading from /dev/urandom This is especially useful for embedded hardware with low-entroy. Please note that generation of the master key might block indefinitely if the kernel cannot harvest enough entropy. --- Documentation/MANPAGE.md | 7 +++++++ cli_args.go | 3 ++- init_dir.go | 2 +- internal/configfile/config_file.go | 26 ++++++++++++++++++++++++-- internal/configfile/config_test.go | 13 ++++++++++--- tests/cli/cli_test.go | 5 +++++ 6 files changed, 49 insertions(+), 7 deletions(-) diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index efb8565..d3a6c81 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -51,6 +51,13 @@ be suitable. #### -d, -debug Enable debug output +#### -devrandom +Use /dev/random for generating the master key instead of the default Go +implementation. This is especially useful on embedded systems with Go versions +prior to 1.9, which fall back to weak random data when the getrandom syscall +is blocking. Using this option can block indefinitely when the kernel cannot +harvest enough entropy. + #### -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 diff --git a/cli_args.go b/cli_args.go index 71ad6bd..7bde89d 100644 --- a/cli_args.go +++ b/cli_args.go @@ -22,7 +22,7 @@ type argContainer struct { plaintextnames, quiet, nosyslog, wpanic, longnames, allow_other, ro, reverse, aessiv, nonempty, raw64, noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info, - sharedstorage bool + sharedstorage, devrandom bool masterkey, mountpoint, cipherdir, cpuprofile, extpass, memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string // Configuration file name override @@ -132,6 +132,7 @@ func parseCliOpts() (args argContainer) { flagSet.BoolVar(&args.hh, "hh", false, "Show this long help text") flagSet.BoolVar(&args.info, "info", false, "Display information about CIPHERDIR") flagSet.BoolVar(&args.sharedstorage, "sharedstorage", false, "Make concurrent access to a shared CIPHERDIR safer") + flagSet.BoolVar(&args.devrandom, "devrandom", false, "Use /dev/random for generating master key") flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file") diff --git a/init_dir.go b/init_dir.go index 20c9fd9..f057bf4 100644 --- a/init_dir.go +++ b/init_dir.go @@ -39,7 +39,7 @@ func initDir(args *argContainer) { password := readpassword.Twice(args.extpass) readpassword.CheckTrailingGarbage() creator := tlog.ProgramName + " " + GitVersion - err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator, args.aessiv) + err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator, args.aessiv, args.devrandom) if err != nil { tlog.Fatal.Println(err) os.Exit(exitcodes.WriteConf) diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go index fea4a84..67d67f0 100644 --- a/internal/configfile/config_file.go +++ b/internal/configfile/config_file.go @@ -5,7 +5,9 @@ package configfile import ( "encoding/json" "fmt" + "io" "io/ioutil" + "log" "github.com/rfjakob/gocryptfs/internal/contentenc" "github.com/rfjakob/gocryptfs/internal/cryptocore" @@ -47,10 +49,25 @@ type ConfFile struct { filename string } +// randBytesDevRandom gets "n" random bytes from /dev/random or panics +func randBytesDevRandom(n int) []byte { + f, err := os.Open("/dev/random") + if err != nil { + log.Panic("Failed to open /dev/random: " + err.Error()) + } + defer f.Close() + b := make([]byte, n) + _, err = io.ReadFull(f, b) + if err != nil { + log.Panic("Failed to read random bytes: " + err.Error()) + } + return b +} + // CreateConfFile - create a new config with a random key encrypted with // "password" and write it to "filename". // Uses scrypt with cost parameter logN. -func CreateConfFile(filename string, password string, plaintextNames bool, logN int, creator string, aessiv bool) error { +func CreateConfFile(filename string, password string, plaintextNames bool, logN int, creator string, aessiv bool, devrandom bool) error { var cf ConfFile cf.filename = filename cf.Creator = creator @@ -72,7 +89,12 @@ func CreateConfFile(filename string, password string, plaintextNames bool, logN } // Generate new random master key - key := cryptocore.RandBytes(cryptocore.KeyLen) + var key []byte + if devrandom { + key = randBytesDevRandom(cryptocore.KeyLen) + } else { + key = cryptocore.RandBytes(cryptocore.KeyLen) + } // Encrypt it using the password // This sets ScryptObject and EncryptedKey diff --git a/internal/configfile/config_test.go b/internal/configfile/config_test.go index dc796ea..b984a37 100644 --- a/internal/configfile/config_test.go +++ b/internal/configfile/config_test.go @@ -60,7 +60,7 @@ func TestLoadV2StrangeFeature(t *testing.T) { } func TestCreateConfDefault(t *testing.T) { - err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", false) + err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", false, false) if err != nil { t.Fatal(err) } @@ -80,8 +80,15 @@ func TestCreateConfDefault(t *testing.T) { } } +func TestCreateConfDevRandom(t *testing.T) { + err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", false, true) + if err != nil { + t.Fatal(err) + } +} + func TestCreateConfPlaintextnames(t *testing.T) { - err := CreateConfFile("config_test/tmp.conf", "test", true, 10, "test", false) + err := CreateConfFile("config_test/tmp.conf", "test", true, 10, "test", false, false) if err != nil { t.Fatal(err) } @@ -102,7 +109,7 @@ func TestCreateConfPlaintextnames(t *testing.T) { // Reverse mode uses AESSIV func TestCreateConfFileAESSIV(t *testing.T) { - err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", true) + err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", true, false) if err != nil { t.Fatal(err) } diff --git a/tests/cli/cli_test.go b/tests/cli/cli_test.go index 97de8da..c7752f2 100644 --- a/tests/cli/cli_test.go +++ b/tests/cli/cli_test.go @@ -34,6 +34,11 @@ func TestInit(t *testing.T) { } } +// Test -init with -devrandom flag +func TestInitDevRandom(t *testing.T) { + test_helpers.InitFS(t, "-devrandom") +} + // Test -init with -aessiv func TestInitAessiv(t *testing.T) { dir := test_helpers.InitFS(t, "-aessiv")