Skip to content

Commit d14c934

Browse files
committed
cli: add -longnamemax
Fixes #499
1 parent d583bdb commit d14c934

File tree

8 files changed

+121
-10
lines changed

8 files changed

+121
-10
lines changed

Documentation/MANPAGE.md

+22
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,28 @@ and https://github.com/rfjakob/gocryptfs/issues/596 for background info.
123123
Use HKDF to derive separate keys for content and name encryption from
124124
the master key. Default true.
125125

126+
#### -longnamemax
127+
128+
integer value, allowed range 62...255
129+
130+
Hash file names that (in encrypted form) exceed this length. The default
131+
is 255, which aligns with the usual name length limit on Linux and
132+
provides best performance.
133+
134+
However, online storage may impose lower limits on file name and/or
135+
path length. In this case, setting -longnamemax to a lower value
136+
can be helpful.
137+
138+
The lower the value, the more extra `.name` files
139+
must be created, which slows down directory listings.
140+
141+
Values below 62 are not allowed as then the hashed name
142+
would be longer than the original name.
143+
144+
Example:
145+
146+
-longnamemax 100
147+
126148
#### -plaintextnames
127149
Do not encrypt file names and symlink targets.
128150

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@ RM: 2,367
196196
Changelog
197197
---------
198198

199+
#### vNEXT
200+
* Add **`-longnamemax`** flag to `-init` ([#499](https://github.com/rfjakob/gocryptfs/issues/499)).
201+
Can be used to work around file or path length restrictions on online storage.
202+
See the [man page](https://github.com/rfjakob/gocryptfs/blob/master/Documentation/MANPAGE.md#-longnamemax)
203+
for details.
204+
199205
#### v2.2.1, 2021-10-20
200206
* Fix `-force_owner` only taking effect after 2 seconds ([#609](https://github.com/rfjakob/gocryptfs/issues/609)).
201207
This was a regression introduced in v2.0.

cli_args.go

+8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ type argContainer struct {
4545
notifypid, scryptn int
4646
// Idle time before autounmount
4747
idle time.Duration
48+
// -longnamemax (hash encrypted names that are longer than this)
49+
longnamemax uint8
4850
// Helper variables that are NOT cli options all start with an underscore
4951
// _configCustom is true when the user sets a custom config file name.
5052
_configCustom bool
@@ -215,6 +217,8 @@ func parseCliOpts(osArgs []string) (args argContainer) {
215217
flagSet.StringSliceVar(&args.badname, "badname", nil, "Glob pattern invalid file names that should be shown")
216218
flagSet.StringSliceVar(&args.passfile, "passfile", nil, "Read password from file")
217219

220+
flagSet.Uint8Var(&args.longnamemax, "longnamemax", 255, "Hash encrypted names that are longer than this")
221+
218222
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
219223
"successful mount - used internally for daemonization")
220224
const scryptn = "scryptn"
@@ -292,6 +296,10 @@ func parseCliOpts(osArgs []string) (args argContainer) {
292296
os.Exit(exitcodes.Usage)
293297
}
294298
}
299+
if args.longnamemax > 0 && args.longnamemax < 62 {
300+
tlog.Fatal.Printf("-longnamemax: value %d is outside allowed range 62 ... 255", args.longnamemax)
301+
os.Exit(exitcodes.Usage)
302+
}
295303

296304
return args
297305
}

cli_args_test.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,12 @@ func TestConvertToDoubleDash(t *testing.T) {
116116

117117
func TestParseCliOpts(t *testing.T) {
118118
defaultArgs := argContainer{
119-
longnames: true,
120-
raw64: true,
121-
hkdf: true,
122-
openssl: stupidgcm.PreferOpenSSLAES256GCM(), // depends on CPU and build flags
123-
scryptn: 16,
119+
longnames: true,
120+
longnamemax: 255,
121+
raw64: true,
122+
hkdf: true,
123+
openssl: stupidgcm.PreferOpenSSLAES256GCM(), // depends on CPU and build flags
124+
scryptn: 16,
124125
}
125126

126127
type testcaseContainer struct {

init_dir.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ func initDir(args *argContainer) {
102102
Fido2CredentialID: fido2CredentialID,
103103
Fido2HmacSalt: fido2HmacSalt,
104104
DeterministicNames: args.deterministic_names,
105-
XChaCha20Poly1305: args.xchacha})
105+
XChaCha20Poly1305: args.xchacha,
106+
LongNameMax: args.longnamemax,
107+
})
106108
if err != nil {
107109
tlog.Fatal.Println(err)
108110
os.Exit(exitcodes.WriteConf)

internal/configfile/validate.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ func (cf *ConfFile) Validate() error {
4747
}
4848
// Filename encryption
4949
{
50-
if cf.IsFeatureFlagSet(FlagPlaintextNames) && cf.IsFeatureFlagSet(FlagEMENames) {
51-
return fmt.Errorf("Can't have both PlaintextNames and EMENames feature flags")
52-
}
5350
if cf.IsFeatureFlagSet(FlagPlaintextNames) {
51+
if cf.IsFeatureFlagSet(FlagEMENames) {
52+
return fmt.Errorf("PlaintextNames conflicts with EMENames feature flag")
53+
}
5454
if cf.IsFeatureFlagSet(FlagDirIV) {
5555
return fmt.Errorf("PlaintextNames conflicts with DirIV feature flag")
5656
}
@@ -60,10 +60,19 @@ func (cf *ConfFile) Validate() error {
6060
if cf.IsFeatureFlagSet(FlagRaw64) {
6161
return fmt.Errorf("PlaintextNames conflicts with Raw64 feature flag")
6262
}
63+
if cf.IsFeatureFlagSet(FlagLongNameMax) {
64+
return fmt.Errorf("PlaintextNames conflicts with LongNameMax feature flag")
65+
}
6366
}
6467
if cf.IsFeatureFlagSet(FlagEMENames) {
6568
// All combinations of DirIV, LongNames, Raw64 allowed
6669
}
70+
if cf.LongNameMax != 0 && !cf.IsFeatureFlagSet(FlagLongNameMax) {
71+
return fmt.Errorf("LongNameMax=%d but the LongNameMax feature flag is NOT set", cf.LongNameMax)
72+
}
73+
if cf.LongNameMax == 0 && cf.IsFeatureFlagSet(FlagLongNameMax) {
74+
return fmt.Errorf("LongNameMax=0 but the LongNameMax feature flag IS set")
75+
}
6776
}
6877
return nil
6978
}

mount.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,8 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
292292
// Settings from the config file override command line args
293293
frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames)
294294
frontendArgs.DeterministicNames = !confFile.IsFeatureFlagSet(configfile.FlagDirIV)
295+
// Things that don't have to be in frontendArgs are only in args
296+
args.longnamemax = confFile.LongNameMax
295297
args.raw64 = confFile.IsFeatureFlagSet(configfile.FlagRaw64)
296298
args.hkdf = confFile.IsFeatureFlagSet(configfile.FlagHKDF)
297299
// Note: this will always return the non-openssl variant
@@ -324,7 +326,7 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
324326
// Init crypto backend
325327
cCore := cryptocore.New(masterkey, cryptoBackend, IVBits, args.hkdf)
326328
cEnc := contentenc.New(cCore, contentenc.DefaultBS)
327-
nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, 0,
329+
nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, args.longnamemax,
328330
args.raw64, []string(args.badname), frontendArgs.DeterministicNames)
329331
// After the crypto backend is initialized,
330332
// we can purge the master key from memory.

tests/cli/longnamemax_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"syscall"
10+
"testing"
11+
12+
"github.com/rfjakob/gocryptfs/v2/internal/configfile"
13+
14+
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
15+
)
16+
17+
// Create & test fs with -longnamemax=100
18+
func TestLongnamemax100(t *testing.T) {
19+
cDir := test_helpers.InitFS(nil, "-longnamemax", "100")
20+
pDir := cDir + ".mnt"
21+
22+
// Check config file sanity
23+
_, c, err := configfile.LoadAndDecrypt(cDir+"/"+configfile.ConfDefaultName, testPw)
24+
if err != nil {
25+
fmt.Println(err)
26+
os.Exit(1)
27+
}
28+
if !c.IsFeatureFlagSet(configfile.FlagLongNameMax) {
29+
t.Error("FlagLongNameMax should be on")
30+
}
31+
if c.LongNameMax != 100 {
32+
t.Errorf("LongNameMax=%d, want 100", c.LongNameMax)
33+
}
34+
35+
// Check that it takes effect
36+
test_helpers.MountOrExit(cDir, pDir, "-extpass", "echo test")
37+
defer test_helpers.UnmountPanic(pDir)
38+
39+
for l := 1; l <= 255; l++ {
40+
path := pDir + "/" + strings.Repeat("x", l)
41+
if err := ioutil.WriteFile(path, nil, 0600); err != nil {
42+
t.Fatal(err)
43+
}
44+
matches, err := filepath.Glob(cDir + "/gocryptfs.longname.*")
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
err = syscall.Unlink(path)
49+
if err != nil {
50+
t.Fatal(err)
51+
}
52+
// As determined experimentally, a name of length >= 64 causes a longname
53+
// to be created.
54+
if l <= 63 && len(matches) != 0 {
55+
t.Errorf("l=%d: should not see a longname yet", l)
56+
}
57+
if l >= 64 && len(matches) != 2 {
58+
t.Errorf("l=%d: should see a longname now", l)
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)