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
This commit is contained in:
Jakob Unterwurzacher 2021-10-21 09:37:04 +02:00
parent a652be805e
commit dc32710045
5 changed files with 65 additions and 10 deletions

View File

@ -19,7 +19,7 @@ func newTestFS(args Args) *RootNode {
key := make([]byte, cryptocore.KeyLen) key := make([]byte, cryptocore.KeyLen)
cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true) cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true)
cEnc := contentenc.New(cCore, contentenc.DefaultBS) 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) rn := NewRootNode(args, cEnc, n)
oneSec := time.Second oneSec := time.Second
options := &fs.Options{ options := &fs.Options{

View File

@ -48,7 +48,7 @@ func (be *NameTransform) EncryptAndHashBadName(name string, iv []byte, dirfd int
//expand suffix on error //expand suffix on error
continue continue
} }
if be.longNames && len(cName) > NameMax { if len(cName) > be.longNameMax {
cNamePart = be.HashLongName(cName) cNamePart = be.HashLongName(cName)
} }
cNameBadReverse := cNamePart + name[charpos:len(name)-len(BadnameSuffix)] cNameBadReverse := cNamePart + name[charpos:len(name)-len(BadnameSuffix)]

View File

@ -1,7 +1,11 @@
package nametransform package nametransform
import ( import (
"strings"
"testing" "testing"
"github.com/rfjakob/gocryptfs/v2/internal/contentenc"
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
) )
func TestIsLongName(t *testing.T) { func TestIsLongName(t *testing.T) {
@ -28,3 +32,40 @@ func TestRemoveLongNameSuffix(t *testing.T) {
t.Error(".name suffix not removed") 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)
}
}
}
}

View File

@ -4,6 +4,7 @@ package nametransform
import ( import (
"crypto/aes" "crypto/aes"
"encoding/base64" "encoding/base64"
"math"
"path/filepath" "path/filepath"
"syscall" "syscall"
@ -20,7 +21,9 @@ const (
// NameTransform is used to transform filenames. // NameTransform is used to transform filenames.
type NameTransform struct { type NameTransform struct {
emeCipher *eme.EMECipher 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 // B64 = either base64.URLEncoding or base64.RawURLEncoding, depending
// on the Raw64 feature flag // on the Raw64 feature flag
B64 *base64.Encoding B64 *base64.Encoding
@ -30,17 +33,28 @@ type NameTransform struct {
} }
// New returns a new NameTransform instance. // 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", // If `longNames` is set, names longer than `longNameMax` are hashed to
longNames, raw64, badname) // `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 b64 := base64.URLEncoding
if raw64 { if raw64 {
b64 = base64.RawURLEncoding b64 = base64.RawURLEncoding
} }
var effectiveLongNameMax int = math.MaxInt
if longNames {
if longNameMax == 0 {
effectiveLongNameMax = NameMax
} else {
effectiveLongNameMax = int(longNameMax)
}
}
return &NameTransform{ return &NameTransform{
emeCipher: e, emeCipher: e,
longNames: longNames, longNameMax: effectiveLongNameMax,
B64: b64, B64: b64,
badnamePatterns: badname, badnamePatterns: badname,
deterministicNames: deterministicNames, deterministicNames: deterministicNames,
@ -115,7 +129,7 @@ func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, err
if err != nil { if err != nil {
return "", err return "", err
} }
if be.longNames && len(cName) > NameMax { if len(cName) > be.longNameMax {
return be.HashLongName(cName), nil return be.HashLongName(cName), nil
} }
return cName, nil return cName, nil

View File

@ -324,7 +324,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, nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, 0,
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.