summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorJakob Unterwurzacher2016-02-06 22:54:14 +0100
committerJakob Unterwurzacher2016-02-06 22:54:14 +0100
commite111e20649cfacd7b02dd454d75db879aa2ca53c (patch)
tree5020d3172bfa2462f05898473093dabd3688e64f /internal
parent5abd9cec136bfb981c728eb3bf0f92b2282601c6 (diff)
longnames part I: Create and OpenDir work with long filenames > 176 bytes
Todo: Rename, Unlink, Rmdir, Mknod, Mkdir
Diffstat (limited to 'internal')
-rw-r--r--internal/configfile/config_file.go4
-rw-r--r--internal/fusefrontend/args.go1
-rw-r--r--internal/fusefrontend/fs.go54
-rw-r--r--internal/fusefrontend/names.go26
-rw-r--r--internal/nametransform/longnames.go68
-rw-r--r--internal/nametransform/longnames_test.go22
-rw-r--r--internal/nametransform/name_api.go4
-rw-r--r--internal/nametransform/names_core.go5
-rw-r--r--internal/nametransform/names_diriv.go25
-rw-r--r--internal/nametransform/names_noiv.go2
-rw-r--r--internal/nametransform/names_test.go2
11 files changed, 174 insertions, 39 deletions
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)