diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/configfile/config_file.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend/args.go | 1 | ||||
| -rw-r--r-- | internal/fusefrontend/fs.go | 54 | ||||
| -rw-r--r-- | internal/fusefrontend/names.go | 26 | ||||
| -rw-r--r-- | internal/nametransform/longnames.go | 68 | ||||
| -rw-r--r-- | internal/nametransform/longnames_test.go | 22 | ||||
| -rw-r--r-- | internal/nametransform/name_api.go | 4 | ||||
| -rw-r--r-- | internal/nametransform/names_core.go | 5 | ||||
| -rw-r--r-- | internal/nametransform/names_diriv.go | 25 | ||||
| -rw-r--r-- | internal/nametransform/names_noiv.go | 2 | ||||
| -rw-r--r-- | internal/nametransform/names_test.go | 2 | 
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) | 
