diff options
| author | Jakob Unterwurzacher | 2021-10-21 15:58:19 +0200 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2021-10-21 15:58:19 +0200 | 
| commit | d14c9340d6fb473e9837e91db8b6e869c37ad8e5 (patch) | |
| tree | 253ba3c3db8a97ba7fdcd5d59b699db92da1cea2 | |
| parent | d583bdb79e6f05bce2451a7e220e553209da4c1d (diff) | |
cli: add -longnamemax
Fixes https://github.com/rfjakob/gocryptfs/issues/499
| -rw-r--r-- | Documentation/MANPAGE.md | 22 | ||||
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | cli_args.go | 8 | ||||
| -rw-r--r-- | cli_args_test.go | 11 | ||||
| -rw-r--r-- | init_dir.go | 4 | ||||
| -rw-r--r-- | internal/configfile/validate.go | 15 | ||||
| -rw-r--r-- | mount.go | 4 | ||||
| -rw-r--r-- | tests/cli/longnamemax_test.go | 61 | 
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. @@ -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  } @@ -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) +		} +	} +} | 
