From dc32710045f6f46913ae336b6fb77bf90b6bdb85 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Thu, 21 Oct 2021 09:37:04 +0200 Subject: [PATCH] nametransform: add longNameMax parameter Determines when to start hashing long names instead of hardcoded 255. Will be used to alleviate "name too long" issues some users see on cloud storage. https://github.com/rfjakob/gocryptfs/issues/499 --- internal/fusefrontend/xattr_unit_test.go | 2 +- internal/nametransform/badname.go | 2 +- internal/nametransform/longnames_test.go | 41 ++++++++++++++++++++++++ internal/nametransform/names.go | 28 ++++++++++++---- mount.go | 2 +- 5 files changed, 65 insertions(+), 10 deletions(-) diff --git a/internal/fusefrontend/xattr_unit_test.go b/internal/fusefrontend/xattr_unit_test.go index 5bffd5e..86c87a7 100644 --- a/internal/fusefrontend/xattr_unit_test.go +++ b/internal/fusefrontend/xattr_unit_test.go @@ -19,7 +19,7 @@ func newTestFS(args Args) *RootNode { key := make([]byte, cryptocore.KeyLen) cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true) cEnc := contentenc.New(cCore, contentenc.DefaultBS) - n := nametransform.New(cCore.EMECipher, true, true, nil, false) + n := nametransform.New(cCore.EMECipher, true, 0, true, nil, false) rn := NewRootNode(args, cEnc, n) oneSec := time.Second options := &fs.Options{ diff --git a/internal/nametransform/badname.go b/internal/nametransform/badname.go index eed0061..6e77561 100644 --- a/internal/nametransform/badname.go +++ b/internal/nametransform/badname.go @@ -48,7 +48,7 @@ func (be *NameTransform) EncryptAndHashBadName(name string, iv []byte, dirfd int //expand suffix on error continue } - if be.longNames && len(cName) > NameMax { + if len(cName) > be.longNameMax { cNamePart = be.HashLongName(cName) } cNameBadReverse := cNamePart + name[charpos:len(name)-len(BadnameSuffix)] diff --git a/internal/nametransform/longnames_test.go b/internal/nametransform/longnames_test.go index 4210492..7a4e915 100644 --- a/internal/nametransform/longnames_test.go +++ b/internal/nametransform/longnames_test.go @@ -1,7 +1,11 @@ package nametransform import ( + "strings" "testing" + + "github.com/rfjakob/gocryptfs/v2/internal/contentenc" + "github.com/rfjakob/gocryptfs/v2/internal/cryptocore" ) func TestIsLongName(t *testing.T) { @@ -28,3 +32,40 @@ func TestRemoveLongNameSuffix(t *testing.T) { t.Error(".name suffix not removed") } } + +func newLognamesTestInstance(longNameMax uint8) *NameTransform { + key := make([]byte, cryptocore.KeyLen) + cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true) + return New(cCore.EMECipher, true, longNameMax, true, nil, false) +} + +func TestLongNameMax(t *testing.T) { + iv := make([]byte, 16) + for max := 0; max <= NameMax; max++ { + n := newLognamesTestInstance(uint8(max)) + if max == 0 { + // effective value is 255 + max = NameMax + } + for l := 0; l <= NameMax+10; l++ { + name := strings.Repeat("x", l) + out, err := n.EncryptAndHashName(name, iv) + if l == 0 || l > NameMax { + if err == nil { + t.Errorf("should have rejected a name of length %d, but did not", l) + } + continue + } + cName, _ := n.EncryptName(name, iv) + rawLen := len(cName) + want := LongNameNone + if rawLen > max { + want = LongNameContent + } + have := NameType(out) + if have != want { + t.Errorf("l=%d max=%d: wanted %v, got %v\nname=%q\nout=%q", l, max, want, have, name, out) + } + } + } +} diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index d766d2f..939d31e 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -4,6 +4,7 @@ package nametransform import ( "crypto/aes" "encoding/base64" + "math" "path/filepath" "syscall" @@ -20,7 +21,9 @@ const ( // NameTransform is used to transform filenames. type NameTransform struct { emeCipher *eme.EMECipher - longNames bool + // Names longer than `longNameMax` are hashed. Set to MaxInt when + // longnames are disabled. + longNameMax int // B64 = either base64.URLEncoding or base64.RawURLEncoding, depending // on the Raw64 feature flag B64 *base64.Encoding @@ -30,17 +33,28 @@ type NameTransform struct { } // New returns a new NameTransform instance. -func New(e *eme.EMECipher, longNames bool, raw64 bool, badname []string, deterministicNames bool) *NameTransform { - tlog.Debug.Printf("nametransform.New: longNames=%v, raw64=%v, badname=%q", - longNames, raw64, badname) - +// +// If `longNames` is set, names longer than `longNameMax` are hashed to +// `gocryptfs.longname.[sha256]`. +// Pass `longNameMax = 0` to use the default value (255). +func New(e *eme.EMECipher, longNames bool, longNameMax uint8, raw64 bool, badname []string, deterministicNames bool) *NameTransform { + tlog.Debug.Printf("nametransform.New: longNameMax=%v, raw64=%v, badname=%q", + longNameMax, raw64, badname) b64 := base64.URLEncoding if raw64 { b64 = base64.RawURLEncoding } + var effectiveLongNameMax int = math.MaxInt + if longNames { + if longNameMax == 0 { + effectiveLongNameMax = NameMax + } else { + effectiveLongNameMax = int(longNameMax) + } + } return &NameTransform{ emeCipher: e, - longNames: longNames, + longNameMax: effectiveLongNameMax, B64: b64, badnamePatterns: badname, deterministicNames: deterministicNames, @@ -115,7 +129,7 @@ func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, err if err != nil { return "", err } - if be.longNames && len(cName) > NameMax { + if len(cName) > be.longNameMax { return be.HashLongName(cName), nil } return cName, nil diff --git a/mount.go b/mount.go index dfabbc9..004c646 100644 --- a/mount.go +++ b/mount.go @@ -324,7 +324,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, + nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, 0, args.raw64, []string(args.badname), frontendArgs.DeterministicNames) // After the crypto backend is initialized, // we can purge the master key from memory.