cli: add -longnamemax
Fixes https://github.com/rfjakob/gocryptfs/issues/499
This commit is contained in:
parent
d583bdb79e
commit
d14c9340d6
@ -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
|
Use HKDF to derive separate keys for content and name encryption from
|
||||||
the master key. Default true.
|
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
|
#### -plaintextnames
|
||||||
Do not encrypt file names and symlink targets.
|
Do not encrypt file names and symlink targets.
|
||||||
|
|
||||||
|
@ -196,6 +196,12 @@ RM: 2,367
|
|||||||
Changelog
|
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
|
#### v2.2.1, 2021-10-20
|
||||||
* Fix `-force_owner` only taking effect after 2 seconds ([#609](https://github.com/rfjakob/gocryptfs/issues/609)).
|
* 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.
|
This was a regression introduced in v2.0.
|
||||||
|
@ -45,6 +45,8 @@ type argContainer struct {
|
|||||||
notifypid, scryptn int
|
notifypid, scryptn int
|
||||||
// Idle time before autounmount
|
// Idle time before autounmount
|
||||||
idle time.Duration
|
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
|
// 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 is true when the user sets a custom config file name.
|
||||||
_configCustom bool
|
_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.badname, "badname", nil, "Glob pattern invalid file names that should be shown")
|
||||||
flagSet.StringSliceVar(&args.passfile, "passfile", nil, "Read password from file")
|
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 "+
|
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
|
||||||
"successful mount - used internally for daemonization")
|
"successful mount - used internally for daemonization")
|
||||||
const scryptn = "scryptn"
|
const scryptn = "scryptn"
|
||||||
@ -292,6 +296,10 @@ func parseCliOpts(osArgs []string) (args argContainer) {
|
|||||||
os.Exit(exitcodes.Usage)
|
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
|
return args
|
||||||
}
|
}
|
||||||
|
@ -116,11 +116,12 @@ func TestConvertToDoubleDash(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseCliOpts(t *testing.T) {
|
func TestParseCliOpts(t *testing.T) {
|
||||||
defaultArgs := argContainer{
|
defaultArgs := argContainer{
|
||||||
longnames: true,
|
longnames: true,
|
||||||
raw64: true,
|
longnamemax: 255,
|
||||||
hkdf: true,
|
raw64: true,
|
||||||
openssl: stupidgcm.PreferOpenSSLAES256GCM(), // depends on CPU and build flags
|
hkdf: true,
|
||||||
scryptn: 16,
|
openssl: stupidgcm.PreferOpenSSLAES256GCM(), // depends on CPU and build flags
|
||||||
|
scryptn: 16,
|
||||||
}
|
}
|
||||||
|
|
||||||
type testcaseContainer struct {
|
type testcaseContainer struct {
|
||||||
|
@ -102,7 +102,9 @@ func initDir(args *argContainer) {
|
|||||||
Fido2CredentialID: fido2CredentialID,
|
Fido2CredentialID: fido2CredentialID,
|
||||||
Fido2HmacSalt: fido2HmacSalt,
|
Fido2HmacSalt: fido2HmacSalt,
|
||||||
DeterministicNames: args.deterministic_names,
|
DeterministicNames: args.deterministic_names,
|
||||||
XChaCha20Poly1305: args.xchacha})
|
XChaCha20Poly1305: args.xchacha,
|
||||||
|
LongNameMax: args.longnamemax,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Println(err)
|
tlog.Fatal.Println(err)
|
||||||
os.Exit(exitcodes.WriteConf)
|
os.Exit(exitcodes.WriteConf)
|
||||||
|
@ -47,10 +47,10 @@ func (cf *ConfFile) Validate() error {
|
|||||||
}
|
}
|
||||||
// Filename encryption
|
// 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(FlagPlaintextNames) {
|
||||||
|
if cf.IsFeatureFlagSet(FlagEMENames) {
|
||||||
|
return fmt.Errorf("PlaintextNames conflicts with EMENames feature flag")
|
||||||
|
}
|
||||||
if cf.IsFeatureFlagSet(FlagDirIV) {
|
if cf.IsFeatureFlagSet(FlagDirIV) {
|
||||||
return fmt.Errorf("PlaintextNames conflicts with DirIV feature flag")
|
return fmt.Errorf("PlaintextNames conflicts with DirIV feature flag")
|
||||||
}
|
}
|
||||||
@ -60,10 +60,19 @@ func (cf *ConfFile) Validate() error {
|
|||||||
if cf.IsFeatureFlagSet(FlagRaw64) {
|
if cf.IsFeatureFlagSet(FlagRaw64) {
|
||||||
return fmt.Errorf("PlaintextNames conflicts with Raw64 feature flag")
|
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) {
|
if cf.IsFeatureFlagSet(FlagEMENames) {
|
||||||
// All combinations of DirIV, LongNames, Raw64 allowed
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
4
mount.go
4
mount.go
@ -292,6 +292,8 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
|
|||||||
// Settings from the config file override command line args
|
// Settings from the config file override command line args
|
||||||
frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames)
|
frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames)
|
||||||
frontendArgs.DeterministicNames = !confFile.IsFeatureFlagSet(configfile.FlagDirIV)
|
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.raw64 = confFile.IsFeatureFlagSet(configfile.FlagRaw64)
|
||||||
args.hkdf = confFile.IsFeatureFlagSet(configfile.FlagHKDF)
|
args.hkdf = confFile.IsFeatureFlagSet(configfile.FlagHKDF)
|
||||||
// Note: this will always return the non-openssl variant
|
// 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
|
// Init crypto backend
|
||||||
cCore := cryptocore.New(masterkey, cryptoBackend, IVBits, args.hkdf)
|
cCore := cryptocore.New(masterkey, cryptoBackend, IVBits, args.hkdf)
|
||||||
cEnc := contentenc.New(cCore, contentenc.DefaultBS)
|
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)
|
args.raw64, []string(args.badname), frontendArgs.DeterministicNames)
|
||||||
// After the crypto backend is initialized,
|
// After the crypto backend is initialized,
|
||||||
// we can purge the master key from memory.
|
// we can purge the master key from memory.
|
||||||
|
61
tests/cli/longnamemax_test.go
Normal file
61
tests/cli/longnamemax_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user