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.
This commit is contained in:
Sebastian Lackner 2017-11-19 13:30:04 +01:00 committed by rfjakob
parent 1b0426bcb2
commit f3c777d5ea
6 changed files with 49 additions and 7 deletions

View File

@ -51,6 +51,13 @@ be suitable.
#### -d, -debug #### -d, -debug
Enable debug output 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 #### -extpass string
Use an external program (like ssh-askpass) for the password prompt. Use an external program (like ssh-askpass) for the password prompt.
The program should return the password on stdout, a trailing newline is The program should return the password on stdout, a trailing newline is

View File

@ -22,7 +22,7 @@ type argContainer struct {
plaintextnames, quiet, nosyslog, wpanic, plaintextnames, quiet, nosyslog, wpanic,
longnames, allow_other, ro, reverse, aessiv, nonempty, raw64, longnames, allow_other, ro, reverse, aessiv, nonempty, raw64,
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info, noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info,
sharedstorage bool sharedstorage, devrandom bool
masterkey, mountpoint, cipherdir, cpuprofile, extpass, masterkey, mountpoint, cipherdir, cpuprofile, extpass,
memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string
// Configuration file name override // 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.hh, "hh", false, "Show this long help text")
flagSet.BoolVar(&args.info, "info", false, "Display information about CIPHERDIR") 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.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.masterkey, "masterkey", "", "Mount with explicit master key")
flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file")
flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file") flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file")

View File

@ -39,7 +39,7 @@ func initDir(args *argContainer) {
password := readpassword.Twice(args.extpass) password := readpassword.Twice(args.extpass)
readpassword.CheckTrailingGarbage() readpassword.CheckTrailingGarbage()
creator := tlog.ProgramName + " " + GitVersion 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 { if err != nil {
tlog.Fatal.Println(err) tlog.Fatal.Println(err)
os.Exit(exitcodes.WriteConf) os.Exit(exitcodes.WriteConf)

View File

@ -5,7 +5,9 @@ package configfile
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log"
"github.com/rfjakob/gocryptfs/internal/contentenc" "github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/cryptocore" "github.com/rfjakob/gocryptfs/internal/cryptocore"
@ -47,10 +49,25 @@ type ConfFile struct {
filename string 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 // CreateConfFile - create a new config with a random key encrypted with
// "password" and write it to "filename". // "password" and write it to "filename".
// Uses scrypt with cost parameter logN. // 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 var cf ConfFile
cf.filename = filename cf.filename = filename
cf.Creator = creator cf.Creator = creator
@ -72,7 +89,12 @@ func CreateConfFile(filename string, password string, plaintextNames bool, logN
} }
// Generate new random master key // 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 // Encrypt it using the password
// This sets ScryptObject and EncryptedKey // This sets ScryptObject and EncryptedKey

View File

@ -60,7 +60,7 @@ func TestLoadV2StrangeFeature(t *testing.T) {
} }
func TestCreateConfDefault(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 { if err != nil {
t.Fatal(err) 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) { 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -102,7 +109,7 @@ func TestCreateConfPlaintextnames(t *testing.T) {
// Reverse mode uses AESSIV // Reverse mode uses AESSIV
func TestCreateConfFileAESSIV(t *testing.T) { 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -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 // Test -init with -aessiv
func TestInitAessiv(t *testing.T) { func TestInitAessiv(t *testing.T) {
dir := test_helpers.InitFS(t, "-aessiv") dir := test_helpers.InitFS(t, "-aessiv")