From e111e20649cfacd7b02dd454d75db879aa2ca53c Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sat, 6 Feb 2016 22:54:14 +0100 Subject: [PATCH] longnames part I: Create and OpenDir work with long filenames > 176 bytes Todo: Rename, Unlink, Rmdir, Mknod, Mkdir --- internal/configfile/config_file.go | 4 +- internal/fusefrontend/args.go | 1 + internal/fusefrontend/fs.go | 54 ++++++++++++------- internal/fusefrontend/names.go | 26 +++++++-- internal/nametransform/longnames.go | 68 ++++++++++++++++++++++++ internal/nametransform/longnames_test.go | 22 ++++++++ internal/nametransform/name_api.go | 4 +- internal/nametransform/names_core.go | 5 +- internal/nametransform/names_diriv.go | 25 +++++---- internal/nametransform/names_noiv.go | 2 +- internal/nametransform/names_test.go | 2 +- main.go | 5 +- 12 files changed, 178 insertions(+), 40 deletions(-) create mode 100644 internal/nametransform/longnames.go create mode 100644 internal/nametransform/longnames_test.go diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go index 8c53a4b..0b910c0 100644 --- a/internal/configfile/config_file.go +++ b/internal/configfile/config_file.go @@ -56,6 +56,7 @@ func CreateConfFile(filename string, password string, plaintextNames bool, logN } else { cf.FeatureFlags = append(cf.FeatureFlags, FlagDirIV) cf.FeatureFlags = append(cf.FeatureFlags, FlagEMENames) + cf.FeatureFlags = append(cf.FeatureFlags, FlagLongNames) } // Write file to disk @@ -169,12 +170,13 @@ const ( FlagDirIV = "DirIV" FlagEMENames = "EMENames" FlagGCMIV128 = "GCMIV128" + FlagLongNames = "LongNames" ) // Verify that we understand a feature flag func (cf *ConfFile) isFeatureFlagKnown(flag string) bool { switch flag { - case FlagPlaintextNames, FlagDirIV, FlagEMENames, FlagGCMIV128: + case FlagPlaintextNames, FlagDirIV, FlagEMENames, FlagGCMIV128, FlagLongNames: return true default: return false diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index e8cab04..8520592 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -9,4 +9,5 @@ type Args struct { DirIV bool EMENames bool GCMIV128 bool + LongNames bool } diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index 007744c..a15e004 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -39,7 +39,7 @@ func NewFS(args Args) *FS { cryptoCore := cryptocore.New(args.Masterkey, args.OpenSSL, args.GCMIV128) contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS) - nameTransform := nametransform.New(cryptoCore, args.EMENames) + nameTransform := nametransform.New(cryptoCore, args.EMENames, args.LongNames) return &FS{ FileSystem: pathfs.NewLoopbackFileSystem(args.Cipherdir), @@ -49,18 +49,6 @@ func NewFS(args Args) *FS { } } -// GetBackingPath - get the absolute encrypted path of the backing file -// from the relative plaintext path "relPath" -func (fs *FS) getBackingPath(relPath string) (string, error) { - cPath, err := fs.encryptPath(relPath) - if err != nil { - return "", err - } - cAbsPath := filepath.Join(fs.args.Cipherdir, cPath) - toggledlog.Debug.Printf("getBackingPath: %s + %s -> %s", fs.args.Cipherdir, relPath, cAbsPath) - return cAbsPath, nil -} - func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) { toggledlog.Debug.Printf("FS.GetAttr('%s')", name) if fs.isFiltered(name) { @@ -97,10 +85,11 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f } // Get DirIV (stays nil if DirIV if off) var cachedIV []byte + var cDirAbsPath string if fs.args.DirIV { // Read the DirIV once and use it for all later name decryptions - cDirAbsPath := filepath.Join(fs.args.Cipherdir, cDirName) - cachedIV, err = fs.nameTransform.ReadDirIV(cDirAbsPath) + cDirAbsPath = filepath.Join(fs.args.Cipherdir, cDirName) + cachedIV, err = nametransform.ReadDirIV(cDirAbsPath) if err != nil { return nil, fuse.ToStatus(err) } @@ -117,14 +106,32 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled continue } - var name string = cName - if !fs.args.PlaintextNames { - name, err = fs.nameTransform.DecryptName(cName, cachedIV) - if err != nil { - toggledlog.Warn.Printf("Invalid name \"%s\" in dir \"%s\": %s", cName, cDirName, err) + + if fs.args.PlaintextNames { + plain = append(plain, cipherEntries[i]) + continue + } + + if fs.args.LongNames { + isLong := nametransform.IsLongName(cName) + if isLong == 1 { + cNameLong, err := nametransform.ReadLongName(filepath.Join(cDirAbsPath, cName)) + if err != nil { + toggledlog.Warn.Printf("Could not read long name for file %s, skipping file", cName) + continue + } + cName = cNameLong + } else if isLong == 2 { + // ignore "gocryptfs.longname.*.name" continue } } + name, err := fs.nameTransform.DecryptName(cName, cachedIV) + if err != nil { + toggledlog.Warn.Printf("Skipping invalid name '%s' in dir '%s': %s", cName, cDirName, err) + continue + } + cipherEntries[i].Name = name plain = append(plain, cipherEntries[i]) } @@ -172,6 +179,13 @@ func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Conte if err != nil { return nil, fuse.ToStatus(err) } + cBaseName := filepath.Base(cPath) + if fs.args.LongNames && nametransform.IsLongName(cBaseName) == 1 { + err = fs.nameTransform.WriteLongName(filepath.Dir(cPath), cBaseName, filepath.Base(path)) + if err != nil { + return nil, fuse.ToStatus(err) + } + } f, err := os.OpenFile(cPath, iflags|os.O_CREATE, os.FileMode(mode)) if err != nil { return nil, fuse.ToStatus(err) diff --git a/internal/fusefrontend/names.go b/internal/fusefrontend/names.go index 5760c87..e913792 100644 --- a/internal/fusefrontend/names.go +++ b/internal/fusefrontend/names.go @@ -3,8 +3,10 @@ package fusefrontend // This file forwards file encryption operations to cryptfs import ( + "path/filepath" + "github.com/rfjakob/gocryptfs/internal/configfile" - mylog "github.com/rfjakob/gocryptfs/internal/toggledlog" + "github.com/rfjakob/gocryptfs/internal/toggledlog" ) // isFiltered - check if plaintext "path" should be forbidden @@ -16,7 +18,7 @@ func (fs *FS) isFiltered(path string) bool { } // gocryptfs.conf in the root directory is forbidden if path == configfile.ConfDefaultName { - mylog.Info.Printf("The name /%s is reserved when -plaintextnames is used\n", + toggledlog.Info.Printf("The name /%s is reserved when -plaintextnames is used\n", configfile.ConfDefaultName) return true } @@ -25,6 +27,18 @@ func (fs *FS) isFiltered(path string) bool { return false } +// GetBackingPath - get the absolute encrypted path of the backing file +// from the relative plaintext path "relPath" +func (fs *FS) getBackingPath(relPath string) (string, error) { + cPath, err := fs.encryptPath(relPath) + if err != nil { + return "", err + } + cAbsPath := filepath.Join(fs.args.Cipherdir, cPath) + toggledlog.Debug.Printf("getBackingPath: %s + %s -> %s", fs.args.Cipherdir, relPath, cAbsPath) + return cAbsPath, nil +} + // encryptPath - encrypt relative plaintext path func (fs *FS) encryptPath(plainPath string) (string, error) { if fs.args.PlaintextNames { @@ -34,8 +48,10 @@ func (fs *FS) encryptPath(plainPath string) (string, error) { return fs.nameTransform.EncryptPathNoIV(plainPath), nil } fs.dirIVLock.RLock() - defer fs.dirIVLock.RUnlock() - return fs.nameTransform.EncryptPathDirIV(plainPath, fs.args.Cipherdir) + cPath, err := fs.nameTransform.EncryptPathDirIV(plainPath, fs.args.Cipherdir) + toggledlog.Debug.Printf("encryptPath '%s' -> '%s' (err: %v)", plainPath, cPath, err) + fs.dirIVLock.RUnlock() + return cPath, err } // decryptPath - decrypt relative ciphertext path @@ -48,5 +64,5 @@ func (fs *FS) decryptPath(cipherPath string) (string, error) { } fs.dirIVLock.RLock() defer fs.dirIVLock.RUnlock() - return fs.nameTransform.DecryptPathDirIV(cipherPath, fs.args.Cipherdir, fs.args.EMENames) + return fs.nameTransform.DecryptPathDirIV(cipherPath, fs.args.Cipherdir) } diff --git a/internal/nametransform/longnames.go b/internal/nametransform/longnames.go new file mode 100644 index 0000000..e442b64 --- /dev/null +++ b/internal/nametransform/longnames.go @@ -0,0 +1,68 @@ +package nametransform + +import ( + "syscall" + "path/filepath" + "io/ioutil" + "crypto/sha256" + "encoding/base64" + "strings" + + "github.com/rfjakob/gocryptfs/internal/toggledlog" +) + +// Files with long names are stored in two files: +// gocryptfs.longname.[sha256] <--- File content +// gocryptfs.longname.[sha256].name <--- File name +const longNamePrefix = "gocryptfs.longname." +const longNameSuffix = ".name" + +// HashLongName - take the hash of a long string "name" and return +// "gocryptfs.longname.[sha256]" +func HashLongName(name string) string { + hashBin := sha256.Sum256([]byte(name)) + hashBase64 := base64.URLEncoding.EncodeToString(hashBin[:]) + return longNamePrefix + hashBase64 +} + +// IsLongName - detect if cName is +// gocryptfs.longname.* ........ 1 +// gocryptfs.longname.*.name ... 2 +// else ........................ 0 +func IsLongName(cName string) int { + if !strings.HasPrefix(cName, longNamePrefix) { + return 0 + } + if strings.HasSuffix(cName, longNameSuffix) { + return 2 + } + return 1 +} + +// ReadLongName - read "path".name +func ReadLongName(path string) (string, error) { + content, err := ioutil.ReadFile(path+longNameSuffix) + if err != nil { + toggledlog.Warn.Printf("ReadLongName: %v", err) + } + return string(content), err +} + +// WriteLongName - +func (n *NameTransform) WriteLongName(cDir string, hashedName string, plainName string) (err error) { + if len(plainName) > syscall.NAME_MAX { + return syscall.ENAMETOOLONG + } + + dirIV, err := ReadDirIV(cDir) + if err != nil { + toggledlog.Warn.Printf("WriteLongName: %v", err) + return err + } + cName := n.EncryptName(plainName, dirIV) + err = ioutil.WriteFile(filepath.Join(cDir, hashedName + longNameSuffix), []byte(cName), 0600) + if err != nil { + toggledlog.Warn.Printf("WriteLongName: %v", err) + } + return err +} diff --git a/internal/nametransform/longnames_test.go b/internal/nametransform/longnames_test.go new file mode 100644 index 0000000..dc4098c --- /dev/null +++ b/internal/nametransform/longnames_test.go @@ -0,0 +1,22 @@ +package nametransform + +import ( + "testing" +) + +func TestIsLongName(t *testing.T) { + n := "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=.name" + if IsLongName(n) != 2 { + t.Errorf("False negative") + } + + n = "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=" + if IsLongName(n) != 1 { + t.Errorf("False negative") + } + + n = "LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=" + if IsLongName(n) != 0 { + t.Errorf("False positive") + } +} diff --git a/internal/nametransform/name_api.go b/internal/nametransform/name_api.go index fe68e09..391a5ce 100644 --- a/internal/nametransform/name_api.go +++ b/internal/nametransform/name_api.go @@ -5,12 +5,14 @@ import "github.com/rfjakob/gocryptfs/internal/cryptocore" type NameTransform struct { cryptoCore *cryptocore.CryptoCore useEME bool + longNames bool DirIVCache dirIVCache } -func New(c *cryptocore.CryptoCore, useEME bool) *NameTransform { +func New(c *cryptocore.CryptoCore, useEME bool, longNames bool) *NameTransform { return &NameTransform{ cryptoCore: c, + longNames: longNames, useEME: useEME, } } diff --git a/internal/nametransform/names_core.go b/internal/nametransform/names_core.go index 2eb0026..779b885 100644 --- a/internal/nametransform/names_core.go +++ b/internal/nametransform/names_core.go @@ -45,7 +45,10 @@ func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error // encryptName - encrypt "plainName", return base64-encoded "cipherName64" // The used encryption is either CBC or EME, depending on "useEME". -func (n *NameTransform) encryptName(plainName string, iv []byte) (cipherName64 string) { +// +// This function is exported because fusefrontend needs access to the full (not hashed) +// name if longname is used +func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 string) { bin := []byte(plainName) bin = pad16(bin) diff --git a/internal/nametransform/names_diriv.go b/internal/nametransform/names_diriv.go index 94c41c8..d45f91b 100644 --- a/internal/nametransform/names_diriv.go +++ b/internal/nametransform/names_diriv.go @@ -1,6 +1,7 @@ package nametransform import ( + "syscall" "fmt" "io/ioutil" "os" @@ -19,8 +20,9 @@ const ( DirIVFilename = "gocryptfs.diriv" ) -// readDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path) -func (be *NameTransform) ReadDirIV(dir string) (iv []byte, readErr error) { +// ReadDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path) +// This function is exported because it allows for an efficient readdir implementation +func ReadDirIV(dir string) (iv []byte, readErr error) { ivfile := filepath.Join(dir, DirIVFilename) toggledlog.Debug.Printf("ReadDirIV: reading %s\n", ivfile) iv, readErr = ioutil.ReadFile(ivfile) @@ -62,9 +64,11 @@ func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cip parentDir := filepath.Dir(plainPath) found, iv, cParentDir := be.DirIVCache.lookup(parentDir) if found { - //fmt.Print("h") baseName := filepath.Base(plainPath) - cBaseName := be.encryptName(baseName, iv) + cBaseName := be.EncryptName(baseName, iv) + if be.longNames && len(cBaseName) > syscall.NAME_MAX { + cBaseName = HashLongName(cBaseName) + } cipherPath = cParentDir + "/" + cBaseName return cipherPath, nil } @@ -73,29 +77,32 @@ func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cip var encryptedNames []string plainNames := strings.Split(plainPath, "/") for _, plainName := range plainNames { - iv, err = be.ReadDirIV(wd) + iv, err = ReadDirIV(wd) if err != nil { return "", err } - encryptedName := be.encryptName(plainName, iv) + encryptedName := be.EncryptName(plainName, iv) + if be.longNames && len(encryptedName) > syscall.NAME_MAX { + encryptedName = HashLongName(encryptedName) + } encryptedNames = append(encryptedNames, encryptedName) wd = filepath.Join(wd, encryptedName) } - // Cache the final DirIV cipherPath = strings.Join(encryptedNames, "/") + // Cache the final DirIV cParentDir = filepath.Dir(cipherPath) be.DirIVCache.store(parentDir, iv, cParentDir) return cipherPath, nil } // DecryptPathDirIV - decrypt path using EME with DirIV -func (be *NameTransform) DecryptPathDirIV(encryptedPath string, rootDir string, eme bool) (string, error) { +func (be *NameTransform) DecryptPathDirIV(encryptedPath string, rootDir string) (string, error) { var wd = rootDir var plainNames []string encryptedNames := strings.Split(encryptedPath, "/") toggledlog.Debug.Printf("DecryptPathDirIV: decrypting %v\n", encryptedNames) for _, encryptedName := range encryptedNames { - iv, err := be.ReadDirIV(wd) + iv, err := ReadDirIV(wd) if err != nil { return "", err } diff --git a/internal/nametransform/names_noiv.go b/internal/nametransform/names_noiv.go index f301e52..f1009e4 100644 --- a/internal/nametransform/names_noiv.go +++ b/internal/nametransform/names_noiv.go @@ -49,7 +49,7 @@ func (be *NameTransform) translatePathNoIV(path string, op int) (string, error) } var newPart string if op == OpEncrypt { - newPart = be.encryptName(part, zeroIV) + newPart = be.EncryptName(part, zeroIV) } else { newPart, err = be.DecryptName(part, zeroIV) if err != nil { diff --git a/internal/nametransform/names_test.go b/internal/nametransform/names_test.go index fdb9f05..4a0043b 100644 --- a/internal/nametransform/names_test.go +++ b/internal/nametransform/names_test.go @@ -15,7 +15,7 @@ func TestEncryptPathNoIV(t *testing.T) { key := make([]byte, cryptocore.KeyLen) cc := cryptocore.New(key, false, true) - fs := New(cc, true) + fs := New(cc, true, false) for _, n := range s { c := fs.EncryptPathNoIV(n) diff --git a/main.go b/main.go index 4666cec..2c1a26c 100644 --- a/main.go +++ b/main.go @@ -41,7 +41,8 @@ const ( type argContainer struct { debug, init, zerokey, fusedebug, openssl, passwd, foreground, version, - plaintextnames, quiet, diriv, emenames, gcmiv128, nosyslog, wpanic bool + plaintextnames, quiet, diriv, emenames, gcmiv128, nosyslog, wpanic, + longnames bool masterkey, mountpoint, cipherdir, cpuprofile, config, extpass, memprofile string notifypid, scryptn int @@ -165,6 +166,7 @@ func main() { flagSet.BoolVar(&args.gcmiv128, "gcmiv128", true, "Use an 128-bit IV for GCM encryption instead of Go's default of 96 bits") flagSet.BoolVar(&args.nosyslog, "nosyslog", false, "Do not redirect output to syslog when running in the background") flagSet.BoolVar(&args.wpanic, "wpanic", false, "When encountering a warning, panic and exit immediately") + flagSet.BoolVar(&args.longnames, "longnames", true, "Store names longer than 176 bytes in extra files") flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file") @@ -338,6 +340,7 @@ func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFi DirIV: args.diriv, EMENames: args.emenames, GCMIV128: args.gcmiv128, + LongNames: args.longnames, } // confFile is nil when "-zerokey" or "-masterkey" was used if confFile != nil {