aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/MANPAGE.md22
-rw-r--r--README.md6
-rw-r--r--cli_args.go8
-rw-r--r--cli_args_test.go11
-rw-r--r--init_dir.go4
-rw-r--r--internal/configfile/validate.go15
-rw-r--r--mount.go4
-rw-r--r--tests/cli/longnamemax_test.go61
8 files changed, 121 insertions, 10 deletions
diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md
index 01e4b5a..b9c72dd 100644
--- a/Documentation/MANPAGE.md
+++ b/Documentation/MANPAGE.md
@@ -123,6 +123,28 @@ and https://github.com/rfjakob/gocryptfs/issues/596 for background info.
Use HKDF to derive separate keys for content and name encryption from
the master key. Default true.
+#### -longnamemax
+
+ integer value, allowed range 62...255
+
+Hash file names that (in encrypted form) exceed this length. The default
+is 255, which aligns with the usual name length limit on Linux and
+provides best performance.
+
+However, online storage may impose lower limits on file name and/or
+path length. In this case, setting -longnamemax to a lower value
+can be helpful.
+
+The lower the value, the more extra `.name` files
+must be created, which slows down directory listings.
+
+Values below 62 are not allowed as then the hashed name
+would be longer than the original name.
+
+Example:
+
+ -longnamemax 100
+
#### -plaintextnames
Do not encrypt file names and symlink targets.
diff --git a/README.md b/README.md
index 1a02857..35c2bda 100644
--- a/README.md
+++ b/README.md
@@ -196,6 +196,12 @@ RM: 2,367
Changelog
---------
+#### vNEXT
+* Add **`-longnamemax`** flag to `-init` ([#499](https://github.com/rfjakob/gocryptfs/issues/499)).
+ Can be used to work around file or path length restrictions on online storage.
+ See the [man page](https://github.com/rfjakob/gocryptfs/blob/master/Documentation/MANPAGE.md#-longnamemax)
+ for details.
+
#### v2.2.1, 2021-10-20
* Fix `-force_owner` only taking effect after 2 seconds ([#609](https://github.com/rfjakob/gocryptfs/issues/609)).
This was a regression introduced in v2.0.
diff --git a/cli_args.go b/cli_args.go
index b415b21..e925345 100644
--- a/cli_args.go
+++ b/cli_args.go
@@ -45,6 +45,8 @@ type argContainer struct {
notifypid, scryptn int
// Idle time before autounmount
idle time.Duration
+ // -longnamemax (hash encrypted names that are longer than this)
+ longnamemax uint8
// Helper variables that are NOT cli options all start with an underscore
// _configCustom is true when the user sets a custom config file name.
_configCustom bool
@@ -215,6 +217,8 @@ func parseCliOpts(osArgs []string) (args argContainer) {
flagSet.StringSliceVar(&args.badname, "badname", nil, "Glob pattern invalid file names that should be shown")
flagSet.StringSliceVar(&args.passfile, "passfile", nil, "Read password from file")
+ flagSet.Uint8Var(&args.longnamemax, "longnamemax", 255, "Hash encrypted names that are longer than this")
+
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
"successful mount - used internally for daemonization")
const scryptn = "scryptn"
@@ -292,6 +296,10 @@ func parseCliOpts(osArgs []string) (args argContainer) {
os.Exit(exitcodes.Usage)
}
}
+ if args.longnamemax > 0 && args.longnamemax < 62 {
+ tlog.Fatal.Printf("-longnamemax: value %d is outside allowed range 62 ... 255", args.longnamemax)
+ os.Exit(exitcodes.Usage)
+ }
return args
}
diff --git a/cli_args_test.go b/cli_args_test.go
index 6c923f4..74255f2 100644
--- a/cli_args_test.go
+++ b/cli_args_test.go
@@ -116,11 +116,12 @@ func TestConvertToDoubleDash(t *testing.T) {
func TestParseCliOpts(t *testing.T) {
defaultArgs := argContainer{
- longnames: true,
- raw64: true,
- hkdf: true,
- openssl: stupidgcm.PreferOpenSSLAES256GCM(), // depends on CPU and build flags
- scryptn: 16,
+ longnames: true,
+ longnamemax: 255,
+ raw64: true,
+ hkdf: true,
+ openssl: stupidgcm.PreferOpenSSLAES256GCM(), // depends on CPU and build flags
+ scryptn: 16,
}
type testcaseContainer struct {
diff --git a/init_dir.go b/init_dir.go
index ab4c3df..9658bab 100644
--- a/init_dir.go
+++ b/init_dir.go
@@ -102,7 +102,9 @@ func initDir(args *argContainer) {
Fido2CredentialID: fido2CredentialID,
Fido2HmacSalt: fido2HmacSalt,
DeterministicNames: args.deterministic_names,
- XChaCha20Poly1305: args.xchacha})
+ XChaCha20Poly1305: args.xchacha,
+ LongNameMax: args.longnamemax,
+ })
if err != nil {
tlog.Fatal.Println(err)
os.Exit(exitcodes.WriteConf)
diff --git a/internal/configfile/validate.go b/internal/configfile/validate.go
index 1611ab0..ab8917d 100644
--- a/internal/configfile/validate.go
+++ b/internal/configfile/validate.go
@@ -47,10 +47,10 @@ func (cf *ConfFile) Validate() error {
}
// Filename encryption
{
- if cf.IsFeatureFlagSet(FlagPlaintextNames) && cf.IsFeatureFlagSet(FlagEMENames) {
- return fmt.Errorf("Can't have both PlaintextNames and EMENames feature flags")
- }
if cf.IsFeatureFlagSet(FlagPlaintextNames) {
+ if cf.IsFeatureFlagSet(FlagEMENames) {
+ return fmt.Errorf("PlaintextNames conflicts with EMENames feature flag")
+ }
if cf.IsFeatureFlagSet(FlagDirIV) {
return fmt.Errorf("PlaintextNames conflicts with DirIV feature flag")
}
@@ -60,10 +60,19 @@ func (cf *ConfFile) Validate() error {
if cf.IsFeatureFlagSet(FlagRaw64) {
return fmt.Errorf("PlaintextNames conflicts with Raw64 feature flag")
}
+ if cf.IsFeatureFlagSet(FlagLongNameMax) {
+ return fmt.Errorf("PlaintextNames conflicts with LongNameMax feature flag")
+ }
}
if cf.IsFeatureFlagSet(FlagEMENames) {
// All combinations of DirIV, LongNames, Raw64 allowed
}
+ if cf.LongNameMax != 0 && !cf.IsFeatureFlagSet(FlagLongNameMax) {
+ return fmt.Errorf("LongNameMax=%d but the LongNameMax feature flag is NOT set", cf.LongNameMax)
+ }
+ if cf.LongNameMax == 0 && cf.IsFeatureFlagSet(FlagLongNameMax) {
+ return fmt.Errorf("LongNameMax=0 but the LongNameMax feature flag IS set")
+ }
}
return nil
}
diff --git a/mount.go b/mount.go
index 004c646..434edad 100644
--- a/mount.go
+++ b/mount.go
@@ -292,6 +292,8 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
// Settings from the config file override command line args
frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames)
frontendArgs.DeterministicNames = !confFile.IsFeatureFlagSet(configfile.FlagDirIV)
+ // Things that don't have to be in frontendArgs are only in args
+ args.longnamemax = confFile.LongNameMax
args.raw64 = confFile.IsFeatureFlagSet(configfile.FlagRaw64)
args.hkdf = confFile.IsFeatureFlagSet(configfile.FlagHKDF)
// Note: this will always return the non-openssl variant
@@ -324,7 +326,7 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
// Init crypto backend
cCore := cryptocore.New(masterkey, cryptoBackend, IVBits, args.hkdf)
cEnc := contentenc.New(cCore, contentenc.DefaultBS)
- nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, 0,
+ nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, args.longnamemax,
args.raw64, []string(args.badname), frontendArgs.DeterministicNames)
// After the crypto backend is initialized,
// we can purge the master key from memory.
diff --git a/tests/cli/longnamemax_test.go b/tests/cli/longnamemax_test.go
new file mode 100644
index 0000000..fc429f6
--- /dev/null
+++ b/tests/cli/longnamemax_test.go
@@ -0,0 +1,61 @@
+package cli
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "testing"
+
+ "github.com/rfjakob/gocryptfs/v2/internal/configfile"
+
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+// Create & test fs with -longnamemax=100
+func TestLongnamemax100(t *testing.T) {
+ cDir := test_helpers.InitFS(nil, "-longnamemax", "100")
+ pDir := cDir + ".mnt"
+
+ // Check config file sanity
+ _, c, err := configfile.LoadAndDecrypt(cDir+"/"+configfile.ConfDefaultName, testPw)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ if !c.IsFeatureFlagSet(configfile.FlagLongNameMax) {
+ t.Error("FlagLongNameMax should be on")
+ }
+ if c.LongNameMax != 100 {
+ t.Errorf("LongNameMax=%d, want 100", c.LongNameMax)
+ }
+
+ // Check that it takes effect
+ test_helpers.MountOrExit(cDir, pDir, "-extpass", "echo test")
+ defer test_helpers.UnmountPanic(pDir)
+
+ for l := 1; l <= 255; l++ {
+ path := pDir + "/" + strings.Repeat("x", l)
+ if err := ioutil.WriteFile(path, nil, 0600); err != nil {
+ t.Fatal(err)
+ }
+ matches, err := filepath.Glob(cDir + "/gocryptfs.longname.*")
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = syscall.Unlink(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // As determined experimentally, a name of length >= 64 causes a longname
+ // to be created.
+ if l <= 63 && len(matches) != 0 {
+ t.Errorf("l=%d: should not see a longname yet", l)
+ }
+ if l >= 64 && len(matches) != 2 {
+ t.Errorf("l=%d: should see a longname now", l)
+ }
+ }
+}