diff --git a/cli_args.go b/cli_args.go index 08b3186..7e8deeb 100644 --- a/cli_args.go +++ b/cli_args.go @@ -22,7 +22,7 @@ type argContainer struct { plaintextnames, quiet, nosyslog, wpanic, longnames, allow_other, reverse, aessiv, nonempty, raw64, noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info, - sharedstorage, devrandom, fsck bool + sharedstorage, devrandom, fsck, trezor bool // Mount options with opposites dev, nodev, suid, nosuid, exec, noexec, rw, ro bool masterkey, mountpoint, cipherdir, cpuprofile, extpass, @@ -138,6 +138,7 @@ func parseCliOpts() (args argContainer) { 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.BoolVar(&args.fsck, "fsck", false, "Run a filesystem check on CIPHERDIR") + flagSet.BoolVar(&args.trezor, "trezor", false, "Protect the masterkey using a SatoshiLabs Trezor instead of a password") // Mount options with opposites flagSet.BoolVar(&args.dev, "dev", false, "Allow device files") diff --git a/init_dir.go b/init_dir.go index 0e1ad95..ea6fff4 100644 --- a/init_dir.go +++ b/init_dir.go @@ -43,11 +43,12 @@ func isDir(dir string) error { return nil } -// initDir prepares a directory for use as a gocryptfs storage directory. +// initDir handles "gocryptfs -init". It prepares a directory for use as a +// gocryptfs storage directory. // In forward mode, this means creating the gocryptfs.conf and gocryptfs.diriv // files in an empty directory. // In reverse mode, we create .gocryptfs.reverse.conf and the directory does -// not to be empty. +// not need to be empty. func initDir(args *argContainer) { var err error if args.reverse { @@ -68,10 +69,18 @@ func initDir(args *argContainer) { tlog.Info.Printf("Choose a password for protecting your files.") } { + var password []byte + if args.trezor { + // Get binary data from from Trezor + password = readpassword.Trezor() + } else { + // Normal password entry + password = readpassword.Twice(args.extpass) + readpassword.CheckTrailingGarbage() + } creator := tlog.ProgramName + " " + GitVersion - password := readpassword.Twice(args.extpass) - readpassword.CheckTrailingGarbage() - err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator, args.aessiv, args.devrandom) + err = configfile.CreateConfFile(args.config, password, args.plaintextnames, + args.scryptn, creator, args.aessiv, args.devrandom, args.trezor) if err != nil { tlog.Fatal.Println(err) os.Exit(exitcodes.WriteConf) @@ -81,7 +90,7 @@ func initDir(args *argContainer) { } // password runs out of scope here } - // Forward mode with filename encryption enabled needs a gocryptfs.diriv + // Forward mode with filename encryption enabled needs a gocryptfs.diriv file // in the root dir if !args.plaintextnames && !args.reverse { err = nametransform.WriteDirIV(nil, args.cipherdir) diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go index 0d5e545..da6c4da 100644 --- a/internal/configfile/config_file.go +++ b/internal/configfile/config_file.go @@ -67,7 +67,8 @@ func randBytesDevRandom(n int) []byte { // 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 []byte, plaintextNames bool, logN int, creator string, aessiv bool, devrandom bool) error { +func CreateConfFile(filename string, password []byte, plaintextNames bool, + logN int, creator string, aessiv bool, devrandom bool, trezor bool) error { var cf ConfFile cf.filename = filename cf.Creator = creator @@ -87,6 +88,9 @@ func CreateConfFile(filename string, password []byte, plaintextNames bool, logN if aessiv { cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagAESSIV]) } + if trezor { + cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagTrezor]) + } { // Generate new random master key var key []byte diff --git a/internal/configfile/config_test.go b/internal/configfile/config_test.go index 15728c6..3152ab5 100644 --- a/internal/configfile/config_test.go +++ b/internal/configfile/config_test.go @@ -62,7 +62,7 @@ func TestLoadV2StrangeFeature(t *testing.T) { } func TestCreateConfDefault(t *testing.T) { - err := CreateConfFile("config_test/tmp.conf", testPw, false, 10, "test", false, false) + err := CreateConfFile("config_test/tmp.conf", testPw, false, 10, "test", false, false, false) if err != nil { t.Fatal(err) } @@ -83,14 +83,14 @@ func TestCreateConfDefault(t *testing.T) { } func TestCreateConfDevRandom(t *testing.T) { - err := CreateConfFile("config_test/tmp.conf", testPw, false, 10, "test", false, true) + err := CreateConfFile("config_test/tmp.conf", testPw, false, 10, "test", false, true, false) if err != nil { t.Fatal(err) } } func TestCreateConfPlaintextnames(t *testing.T) { - err := CreateConfFile("config_test/tmp.conf", testPw, true, 10, "test", false, false) + err := CreateConfFile("config_test/tmp.conf", testPw, true, 10, "test", false, false, false) if err != nil { t.Fatal(err) } @@ -111,7 +111,7 @@ func TestCreateConfPlaintextnames(t *testing.T) { // Reverse mode uses AESSIV func TestCreateConfFileAESSIV(t *testing.T) { - err := CreateConfFile("config_test/tmp.conf", testPw, false, 10, "test", true, false) + err := CreateConfFile("config_test/tmp.conf", testPw, false, 10, "test", true, false, false) if err != nil { t.Fatal(err) } diff --git a/internal/configfile/feature_flags.go b/internal/configfile/feature_flags.go index 2d609f2..141b007 100644 --- a/internal/configfile/feature_flags.go +++ b/internal/configfile/feature_flags.go @@ -25,6 +25,9 @@ const ( // Note that this flag does not change the password hashing algorithm // which always is scrypt. FlagHKDF + // FlagTrezor means that "-trezor" was used when creating the filesystem. + // The masterkey is protected using a Trezor device instead of a password. + FlagTrezor ) // knownFlags stores the known feature flags and their string representation @@ -37,6 +40,7 @@ var knownFlags = map[flagIota]string{ FlagAESSIV: "AESSIV", FlagRaw64: "Raw64", FlagHKDF: "HKDF", + FlagTrezor: "Trezor", } // Filesystems that do not have these feature flags set are deprecated. diff --git a/internal/exitcodes/exitcodes.go b/internal/exitcodes/exitcodes.go index fc65166..3fbfdc6 100644 --- a/internal/exitcodes/exitcodes.go +++ b/internal/exitcodes/exitcodes.go @@ -65,6 +65,9 @@ const ( FsckErrors = 26 // DeprecatedFS - this filesystem is deprecated DeprecatedFS = 27 + // TrezorError - an error was encountered while interacting with a Trezor + // device + TrezorError = 28 ) // Err wraps an error with an associated numeric exit code diff --git a/internal/readpassword/trezor.go b/internal/readpassword/trezor.go new file mode 100644 index 0000000..37dde79 --- /dev/null +++ b/internal/readpassword/trezor.go @@ -0,0 +1,26 @@ +package readpassword + +import ( + "os" + + "github.com/rfjakob/gocryptfs/internal/exitcodes" + "github.com/rfjakob/gocryptfs/internal/tlog" +) + +// Trezor reads 16 deterministically derived bytes from a +// SatoshiLabs Trezor USB security module. +// The bytes are pseudorandom binary data and may contain null bytes. +// This function either succeeds and returns 16 bytes or calls os.Exit to end +// the application. +func Trezor() []byte { + var err error + // TODO try to read bytes here.... + // Handle errors + if err != nil { + tlog.Fatal.Printf("xxx some error was encountered...") + os.Exit(exitcodes.TrezorError) + } + + tlog.Warn.Println("XXX readpassword.Trezor(): not implemented yet - returning hardcoded dummy bytes XXX") + return []byte("1234567890123456") +} diff --git a/main.go b/main.go index 226a20a..9fdbc6e 100644 --- a/main.go +++ b/main.go @@ -33,26 +33,33 @@ var raceDetector bool // loadConfig loads the config file "args.config", prompting the user for the password func loadConfig(args *argContainer) (masterkey []byte, confFile *configfile.ConfFile, err error) { - // Check if the file can be opened at all before prompting for a password - fd, err := os.Open(args.config) + // First check if the file can be read at all, and find out if a Trezor should + // be used instead of a password. + _, cf1, err := configfile.LoadConfFile(args.config, nil) if err != nil { tlog.Fatal.Printf("Cannot open config file: %v", err) - return nil, nil, exitcodes.NewErr(err.Error(), exitcodes.OpenConf) + return nil, nil, err } - fd.Close() - // The user has passed the master key (probably because he forgot the - // password). + // The user has passed the master key on the command line (probably because + // he forgot the password). if args.masterkey != "" { masterkey = parseMasterKey(args.masterkey, false) - _, confFile, err = configfile.LoadConfFile(args.config, nil) - } else { - pw := readpassword.Once(args.extpass, "") - tlog.Info.Println("Decrypting master key") - masterkey, confFile, err = configfile.LoadConfFile(args.config, pw) - for i := range pw { - pw[i] = 0 - } + return masterkey, cf1, nil } + var pw []byte + if cf1.IsFeatureFlagSet(configfile.FlagTrezor) { + // Get binary data from from Trezor + pw = readpassword.Trezor() + } else { + // Normal password entry + pw = readpassword.Once(args.extpass, "") + } + tlog.Info.Println("Decrypting master key") + masterkey, confFile, err = configfile.LoadConfFile(args.config, pw) + for i := range pw { + pw[i] = 0 + } + if err != nil { tlog.Fatal.Println(err) return nil, nil, err @@ -71,6 +78,9 @@ func changePassword(args *argContainer) { if err != nil { exitcodes.Exit(err) } + if len(masterkey) == 0 { + panic("empty masterkey") + } tlog.Info.Println("Please enter your new password.") newPw := readpassword.Twice(args.extpass) readpassword.CheckTrailingGarbage() diff --git a/masterkey.go b/masterkey.go index c67f115..1b4d9c3 100644 --- a/masterkey.go +++ b/masterkey.go @@ -103,7 +103,9 @@ func getMasterKey(args *argContainer) (masterkey []byte, confFile *configfile.Co } exitcodes.Exit(err) } - readpassword.CheckTrailingGarbage() + if !args.trezor { + readpassword.CheckTrailingGarbage() + } if !args.fsck { // We only want to print the masterkey message on a normal mount. printMasterKey(masterkey) diff --git a/tests/fsck/fsck_test.go b/tests/fsck/fsck_test.go index 2faed97..8e73cf7 100644 --- a/tests/fsck/fsck_test.go +++ b/tests/fsck/fsck_test.go @@ -69,6 +69,7 @@ func TestExampleFses(t *testing.T) { fsNames = append(fsNames, e.Name()) } for _, n := range fsNames { + t.Logf("Checking %q", n) path := "../example_filesystems/" + n cmd := exec.Command(test_helpers.GocryptfsBinary, "-fsck", "-extpass", "echo test", path) outBin, err := cmd.CombinedOutput()