cli: add -longnamemax

Fixes https://github.com/rfjakob/gocryptfs/issues/499
This commit is contained in:
Jakob Unterwurzacher 2021-10-21 15:58:19 +02:00
parent d583bdb79e
commit d14c9340d6
8 changed files with 121 additions and 10 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

View File

@ -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.

View File

@ -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)
}
}
}