longnames part I: Create and OpenDir work with long filenames > 176 bytes

Todo: Rename, Unlink, Rmdir, Mknod, Mkdir
This commit is contained in:
Jakob Unterwurzacher 2016-02-06 22:54:14 +01:00
parent 5abd9cec13
commit e111e20649
12 changed files with 178 additions and 40 deletions

View File

@ -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

View File

@ -9,4 +9,5 @@ type Args struct {
DirIV bool
EMENames bool
GCMIV128 bool
LongNames bool
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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,
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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 {