From 2050c7f3b3822a4a3329d4ba5b146d269ef05b4d Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 25 Sep 2016 15:05:09 +0200 Subject: [PATCH] reverse: add gcmsiv flag and associated tests --- Documentation/MANPAGE.md | 9 ++++- cli_args.go | 3 +- init_dir.go | 16 +++++--- internal/configfile/config_file.go | 8 ++-- internal/configfile/config_test.go | 4 +- main.go | 20 ++++++---- test.bash | 4 +- tests/normal/cli_test.go | 59 +++++++++++++++++++++++++---- tests/reverse/correctness_test.go | 17 +++++++++ tests/reverse/longname_perf_test.go | 45 ++++++---------------- tests/reverse/main_test.go | 32 ++++++++++++++++ 11 files changed, 153 insertions(+), 64 deletions(-) create mode 100644 tests/reverse/correctness_test.go create mode 100644 tests/reverse/main_test.go diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index bf696f1..5c31a92 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -58,6 +58,9 @@ to mount the gocryptfs filesytem without user interaction. **-fusedebug** : Enable fuse library debug output +**-gcmsiv** +: Use the GCM-SIV encryption mode (implied by -reverse) + **-init** : Initialize encrypted directory @@ -94,7 +97,7 @@ runs as root, you can enable device files by passing the opposite mount option, interesting. For a complete liste see the section `FILESYSTEM-INDEPENDENT MOUNT OPTIONS` in mount(8). -**-openssl bool** +**-openssl bool/"auto"** : Use OpenSSL instead of built-in Go crypto (default "auto"). Using built-in crypto is 4x slower unless your CPU has AES instructions and you are using Go 1.6+. In mode "auto", gocrypts chooses the faster @@ -109,6 +112,10 @@ option. **-q, -quiet** : Quiet - silence informational messages +**-reverse** +: Reverse mode shows a read-only encrypted view of a plaintext +directory + **-ro** : Mount the filesystem read-only diff --git a/cli_args.go b/cli_args.go index 4c91f44..b50c350 100644 --- a/cli_args.go +++ b/cli_args.go @@ -14,7 +14,7 @@ import ( type argContainer struct { debug, init, zerokey, fusedebug, openssl, passwd, foreground, version, plaintextnames, quiet, nosyslog, wpanic, - longnames, allow_other, ro, reverse bool + longnames, allow_other, ro, reverse, gcmsiv bool masterkey, mountpoint, cipherdir, cpuprofile, extpass, memprofile, o string // Configuration file name override @@ -51,6 +51,7 @@ func parseCliOpts() (args argContainer) { "Only works if user_allow_other is set in /etc/fuse.conf.") flagSet.BoolVar(&args.ro, "ro", false, "Mount the filesystem read-only") flagSet.BoolVar(&args.reverse, "reverse", false, "Reverse mode") + flagSet.BoolVar(&args.gcmsiv, "gcmsiv", false, "GCM-SIV encryption") 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 fac4053..c9c7be6 100644 --- a/init_dir.go +++ b/init_dir.go @@ -39,7 +39,7 @@ func initDir(args *argContainer) { } password := readpassword.Twice(args.extpass) creator := tlog.ProgramName + " " + GitVersion - err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator, args.reverse) + err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator, args.gcmsiv) if err != nil { tlog.Fatal.Println(err) os.Exit(ERREXIT_INIT) @@ -53,8 +53,14 @@ func initDir(args *argContainer) { os.Exit(ERREXIT_INIT) } } - - tlog.Info.Printf(tlog.ColorGreen + "The filesystem has been created successfully." + tlog.ColorReset) + mountArgs := "" + fsName := "gocryptfs" + if args.reverse { + mountArgs = " -reverse" + fsName = "gocryptfs-reverse" + } + tlog.Info.Printf(tlog.ColorGreen+"The %s filesystem has been created successfully."+tlog.ColorReset, + fsName) wd, _ := os.Getwd() friendlyPath, _ := filepath.Rel(wd, args.cipherdir) if strings.HasPrefix(friendlyPath, "../") { @@ -62,7 +68,7 @@ func initDir(args *argContainer) { // keep the absolute path. friendlyPath = args.cipherdir } - tlog.Info.Printf(tlog.ColorGrey+"You can now mount it using: %s %s MOUNTPOINT"+tlog.ColorReset, - tlog.ProgramName, friendlyPath) + tlog.Info.Printf(tlog.ColorGrey+"You can now mount it using: %s%s %s MOUNTPOINT"+tlog.ColorReset, + tlog.ProgramName, mountArgs, friendlyPath) os.Exit(0) } diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go index b1504b4..32e7e66 100644 --- a/internal/configfile/config_file.go +++ b/internal/configfile/config_file.go @@ -45,7 +45,7 @@ type ConfFile struct { // 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, reverse bool) error { +func CreateConfFile(filename string, password string, plaintextNames bool, logN int, creator string, gcmsiv bool) error { var cf ConfFile cf.filename = filename cf.Creator = creator @@ -59,7 +59,7 @@ func CreateConfFile(filename string, password string, plaintextNames bool, logN cf.EncryptKey(key, password, logN) // Set feature flags - cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMIV128]) + cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMIV128]) // 128-bit IVs if plaintextNames { cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagPlaintextNames]) } else { @@ -67,8 +67,8 @@ func CreateConfFile(filename string, password string, plaintextNames bool, logN cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagEMENames]) cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagLongNames]) } - if reverse { - cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMSIV]) + if gcmsiv { + cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMSIV]) // GCM-SIV encryption mode } // Write file to disk diff --git a/internal/configfile/config_test.go b/internal/configfile/config_test.go index 72c25f6..ac85c8d 100644 --- a/internal/configfile/config_test.go +++ b/internal/configfile/config_test.go @@ -71,7 +71,7 @@ func TestCreateConfFile(t *testing.T) { } -func TestCreateConfFileReverse(t *testing.T) { +func TestCreateConfFileGCMSIV(t *testing.T) { err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", true) if err != nil { t.Fatal(err) @@ -87,7 +87,7 @@ func TestCreateConfFileReverse(t *testing.T) { func TestIsFeatureFlagKnown(t *testing.T) { // Test a few hardcoded values - testKnownFlags := []string{"DirIV", "PlaintextNames", "EMENames", "GCMIV128", "LongNames"} + testKnownFlags := []string{"DirIV", "PlaintextNames", "EMENames", "GCMIV128", "LongNames", "GCMSIV"} // And also everything in knownFlags (yes, it is likely that we end up with // some duplicates. Does not matter.) for _, f := range knownFlags { diff --git a/main.go b/main.go index ea9efad..e2777b0 100644 --- a/main.go +++ b/main.go @@ -111,11 +111,9 @@ func printVersion() { func main() { runtime.GOMAXPROCS(4) var err error - // Parse all command-line options (i.e. arguments starting with "-") // into "args". Path arguments are parsed below. args := parseCliOpts() - // Fork a child into the background if "-f" is not set AND we are mounting // a filesystem. The child will do all the work. if !args.foreground && flagSet.NArg() == 2 { @@ -152,6 +150,10 @@ func main() { if args.quiet { tlog.Info.Enabled = false } + // "-reverse" implies "-gcmsiv" + if args.reverse { + args.gcmsiv = true + } // "-config" if args.config != "" { args.config, err = filepath.Abs(args.config) @@ -201,7 +203,7 @@ func main() { } else { tlog.Debug.Printf("OpenSSL enabled") } - // Operation flags: init, passwd or mount + // Operation flags: -init or -passwd; otherwise: mount // "-init" if args.init { if flagSet.NArg() > 1 { @@ -288,8 +290,7 @@ func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFi if args.openssl { cryptoBackend = cryptocore.BackendOpenSSL } - if args.reverse { - // reverse implies GCMSIV + if args.gcmsiv { cryptoBackend = cryptocore.BackendGCMSIV } frontendArgs := fusefrontend.Args{ @@ -306,7 +307,7 @@ func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFi if confFile.IsFeatureFlagSet(configfile.FlagGCMSIV) { frontendArgs.CryptoBackend = cryptocore.BackendGCMSIV } else if args.reverse { - tlog.Fatal.Printf("GCMSIV is required by reverse mode, but not enabled in the config file") + tlog.Fatal.Printf("GCM-SIV is required by reverse mode, but not enabled in the config file") os.Exit(ERREXIT_USAGE) } } @@ -317,11 +318,14 @@ func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFi } jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t") tlog.Debug.Printf("frontendArgs: %s", string(jsonBytes)) - + if frontendArgs.CryptoBackend == cryptocore.BackendGCMSIV { + tlog.Info.Printf(tlog.ColorYellow + + "Warning: The GCM-SIV format used by reverse mode is not yet finalized.\n" + + "The on-disk format will change in the future." + tlog.ColorReset) + } var finalFs pathfs.FileSystem if args.reverse { finalFs = fusefrontend_reverse.NewFS(frontendArgs) - tlog.Info.Printf(tlog.ColorYellow + "REVERSE MODE IS EXPERIMENTAL!" + tlog.ColorReset) } else { finalFs = fusefrontend.NewFS(frontendArgs) } diff --git a/test.bash b/test.bash index 2a565b7..8711d8f 100755 --- a/test.bash +++ b/test.bash @@ -8,8 +8,8 @@ source build.bash go test ./... $* -# Clean up after ourself, but don't descend into maybe still mounted -# example filesystems +# Clean up after ourself, but don't descend into possibly still mounted +# example filesystems. # The tests cannot to this themselves as they are run in parallel rm -Rf --one-file-system /tmp/gocryptfs-test-parent diff --git a/tests/normal/cli_test.go b/tests/normal/cli_test.go index ed3111b..62ad217 100644 --- a/tests/normal/cli_test.go +++ b/tests/normal/cli_test.go @@ -5,7 +5,6 @@ package normal import ( "os" "os/exec" - "path/filepath" "testing" "github.com/rfjakob/gocryptfs/internal/configfile" @@ -24,18 +23,45 @@ func TestMain(m *testing.M) { // Test -init flag func TestInit(t *testing.T) { dir := test_helpers.InitFS(t) - _, err := os.Stat(filepath.Join(dir, configfile.ConfDefaultName)) + _, c, err := configfile.LoadConfFile(dir+"/"+configfile.ConfDefaultName, "test") if err != nil { t.Fatal(err) } + if c.IsFeatureFlagSet(configfile.FlagGCMSIV) { + t.Error("GCMSIV flag should not be set") + } } -// Test -passwd flag -func TestPasswd(t *testing.T) { - // Create FS - dir := test_helpers.InitFS(t) +// Test -init with -gcmsiv +func TestInitGcmsiv(t *testing.T) { + dir := test_helpers.InitFS(t, "-gcmsiv") + _, c, err := configfile.LoadConfFile(dir+"/"+configfile.ConfDefaultName, "test") + if err != nil { + t.Fatal(err) + } + if !c.IsFeatureFlagSet(configfile.FlagGCMSIV) { + t.Error("GCMSIV flag should be set but is not") + } +} + +// Test -init with -reverse +func TestInitReverse(t *testing.T) { + dir := test_helpers.InitFS(t, "-reverse") + _, c, err := configfile.LoadConfFile(dir+"/"+configfile.ConfReverseName, "test") + if err != nil { + t.Fatal(err) + } + if !c.IsFeatureFlagSet(configfile.FlagGCMSIV) { + t.Error("GCMSIV flag should be set but is not") + } +} + +func testPasswd(t *testing.T, dir string, extraArgs ...string) { // Change password using "-extpass" - cmd := exec.Command(test_helpers.GocryptfsBinary, "-q", "-passwd", "-extpass", "echo test", dir) + args := []string{"-q", "-passwd", "-extpass", "echo test"} + args = append(args, extraArgs...) + args = append(args, dir) + cmd := exec.Command(test_helpers.GocryptfsBinary, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() @@ -43,7 +69,10 @@ func TestPasswd(t *testing.T) { t.Error(err) } // Change password using stdin - cmd = exec.Command(test_helpers.GocryptfsBinary, "-q", "-passwd", dir) + args = []string{"-q", "-passwd", "-extpass", "echo test"} + args = append(args, extraArgs...) + args = append(args, dir) + cmd = exec.Command(test_helpers.GocryptfsBinary, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr p, err := cmd.StdinPipe() @@ -65,6 +94,20 @@ func TestPasswd(t *testing.T) { } } +// Test -passwd flag +func TestPasswd(t *testing.T) { + // Create FS + dir := test_helpers.InitFS(t) + testPasswd(t, dir) +} + +// Test -passwd with -reverse +func TestPasswdReverse(t *testing.T) { + // Create FS + dir := test_helpers.InitFS(t, "-reverse") + testPasswd(t, dir, "-reverse") +} + // Test -init & -config flag func TestInitConfig(t *testing.T) { config := test_helpers.TmpDir + "/TestInitConfig.conf" diff --git a/tests/reverse/correctness_test.go b/tests/reverse/correctness_test.go new file mode 100644 index 0000000..e268815 --- /dev/null +++ b/tests/reverse/correctness_test.go @@ -0,0 +1,17 @@ +package reverse_test + +import ( + "os" + "testing" +) + +func TestLongnameStat(t *testing.T) { + _, err := os.Stat(dirA + "/" + "") + if err != nil { + t.Error(err) + } + _, err = os.Stat(dirA + "/" + "") + if err != nil { + t.Error(err) + } +} diff --git a/tests/reverse/longname_perf_test.go b/tests/reverse/longname_perf_test.go index 43acd25..f170ad7 100644 --- a/tests/reverse/longname_perf_test.go +++ b/tests/reverse/longname_perf_test.go @@ -5,34 +5,17 @@ import ( "fmt" "os" "testing" - - "github.com/rfjakob/gocryptfs/tests/test_helpers" ) -var dirA, dirB, x240 string - -func TestMain(m *testing.M) { - x240 = string(bytes.Repeat([]byte("x"), 240)) - dirA = test_helpers.TmpDir + "/a" - dirB = test_helpers.TmpDir + "/b" - os.Mkdir(dirA, 0700) - os.Mkdir(dirB, 0700) - generateFiles(dirA) - test_helpers.MountOrExit(dirA, dirB, "-zerokey", "-reverse") - r := m.Run() - test_helpers.UnmountPanic(dirB) - os.RemoveAll(test_helpers.TmpDir) - os.Exit(r) -} - -func genName(i int) string { - return fmt.Sprintf("%04d.%s", i, x240) +func genName(i int, postfix string) string { + return fmt.Sprintf("%04d.%s", i, postfix) } // Create 10000 files with long names -func generateFiles(dir string) { +func generateLongnameFiles(dir string) { + x240 := string(bytes.Repeat([]byte("x"), 240)) for i := 0; i < 100000; i++ { - n := genName(i) + n := genName(i, x240) f, err := os.Create(dir + "/" + n) if err != nil { panic(err) @@ -41,18 +24,9 @@ func generateFiles(dir string) { } } -func TestLongnameStat(t *testing.T) { - _, err := os.Stat(dirA + "/" + genName(0)) - if err != nil { - t.Error(err) - } - _, err = os.Stat(dirA + "/" + genName(9999)) - if err != nil { - t.Error(err) - } -} - func BenchmarkLongnameStat(b *testing.B) { + // Setup + generateLongnameFiles(dirA) dirFd, err := os.Open(dirB) if err != nil { b.Fatal(err) @@ -63,6 +37,7 @@ func BenchmarkLongnameStat(b *testing.B) { } l := len(encryptedNames) dirFd.Close() + // Benchmark b.ResetTimer() for i := 0; i < b.N; i++ { _, err := os.Stat(dirB + "/" + encryptedNames[i%l]) @@ -70,4 +45,8 @@ func BenchmarkLongnameStat(b *testing.B) { b.Fatal(err) } } + // Cleanup + b.StopTimer() + os.RemoveAll(dirA) + os.Mkdir(dirA, 0700) } diff --git a/tests/reverse/main_test.go b/tests/reverse/main_test.go new file mode 100644 index 0000000..3d10750 --- /dev/null +++ b/tests/reverse/main_test.go @@ -0,0 +1,32 @@ +package reverse_test + +import ( + "os" + "testing" + + "github.com/rfjakob/gocryptfs/tests/test_helpers" +) + +var dirA, dirB, dirC string + +func TestMain(m *testing.M) { + dirA = test_helpers.TmpDir + "/a" + dirB = test_helpers.TmpDir + "/b" + dirC = test_helpers.TmpDir + "/c" + if err := os.Mkdir(dirA, 0700); err != nil { + panic(err) + } + if err := os.Mkdir(dirB, 0700); err != nil { + panic(err) + } + if err := os.Mkdir(dirC, 0700); err != nil { + panic(err) + } + test_helpers.MountOrExit(dirA, dirB, "-zerokey", "-reverse") + test_helpers.MountOrExit(dirB, dirC, "-zerokey", "-gcmsiv") + r := m.Run() + test_helpers.UnmountPanic(dirC) + test_helpers.UnmountPanic(dirB) + os.RemoveAll(test_helpers.TmpDir) + os.Exit(r) +}