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 { } else {
cf.FeatureFlags = append(cf.FeatureFlags, FlagDirIV) cf.FeatureFlags = append(cf.FeatureFlags, FlagDirIV)
cf.FeatureFlags = append(cf.FeatureFlags, FlagEMENames) cf.FeatureFlags = append(cf.FeatureFlags, FlagEMENames)
cf.FeatureFlags = append(cf.FeatureFlags, FlagLongNames)
} }
// Write file to disk // Write file to disk
@ -169,12 +170,13 @@ const (
FlagDirIV = "DirIV" FlagDirIV = "DirIV"
FlagEMENames = "EMENames" FlagEMENames = "EMENames"
FlagGCMIV128 = "GCMIV128" FlagGCMIV128 = "GCMIV128"
FlagLongNames = "LongNames"
) )
// Verify that we understand a feature flag // Verify that we understand a feature flag
func (cf *ConfFile) isFeatureFlagKnown(flag string) bool { func (cf *ConfFile) isFeatureFlagKnown(flag string) bool {
switch flag { switch flag {
case FlagPlaintextNames, FlagDirIV, FlagEMENames, FlagGCMIV128: case FlagPlaintextNames, FlagDirIV, FlagEMENames, FlagGCMIV128, FlagLongNames:
return true return true
default: default:
return false return false

View File

@ -9,4 +9,5 @@ type Args struct {
DirIV bool DirIV bool
EMENames bool EMENames bool
GCMIV128 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) cryptoCore := cryptocore.New(args.Masterkey, args.OpenSSL, args.GCMIV128)
contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS) contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS)
nameTransform := nametransform.New(cryptoCore, args.EMENames) nameTransform := nametransform.New(cryptoCore, args.EMENames, args.LongNames)
return &FS{ return &FS{
FileSystem: pathfs.NewLoopbackFileSystem(args.Cipherdir), 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) { func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
toggledlog.Debug.Printf("FS.GetAttr('%s')", name) toggledlog.Debug.Printf("FS.GetAttr('%s')", name)
if fs.isFiltered(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) // Get DirIV (stays nil if DirIV if off)
var cachedIV []byte var cachedIV []byte
var cDirAbsPath string
if fs.args.DirIV { if fs.args.DirIV {
// Read the DirIV once and use it for all later name decryptions // Read the DirIV once and use it for all later name decryptions
cDirAbsPath := filepath.Join(fs.args.Cipherdir, cDirName) cDirAbsPath = filepath.Join(fs.args.Cipherdir, cDirName)
cachedIV, err = fs.nameTransform.ReadDirIV(cDirAbsPath) cachedIV, err = nametransform.ReadDirIV(cDirAbsPath)
if err != nil { if err != nil {
return nil, fuse.ToStatus(err) 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 // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
continue continue
} }
var name string = cName
if !fs.args.PlaintextNames { if fs.args.PlaintextNames {
name, err = fs.nameTransform.DecryptName(cName, cachedIV) 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 { if err != nil {
toggledlog.Warn.Printf("Invalid name \"%s\" in dir \"%s\": %s", cName, cDirName, err) 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 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 cipherEntries[i].Name = name
plain = append(plain, cipherEntries[i]) 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 { if err != nil {
return nil, fuse.ToStatus(err) 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)) f, err := os.OpenFile(cPath, iflags|os.O_CREATE, os.FileMode(mode))
if err != nil { if err != nil {
return nil, fuse.ToStatus(err) return nil, fuse.ToStatus(err)

View File

@ -3,8 +3,10 @@ package fusefrontend
// This file forwards file encryption operations to cryptfs // This file forwards file encryption operations to cryptfs
import ( import (
"path/filepath"
"github.com/rfjakob/gocryptfs/internal/configfile" "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 // 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 // gocryptfs.conf in the root directory is forbidden
if path == configfile.ConfDefaultName { 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) configfile.ConfDefaultName)
return true return true
} }
@ -25,6 +27,18 @@ func (fs *FS) isFiltered(path string) bool {
return false 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 // encryptPath - encrypt relative plaintext path
func (fs *FS) encryptPath(plainPath string) (string, error) { func (fs *FS) encryptPath(plainPath string) (string, error) {
if fs.args.PlaintextNames { if fs.args.PlaintextNames {
@ -34,8 +48,10 @@ func (fs *FS) encryptPath(plainPath string) (string, error) {
return fs.nameTransform.EncryptPathNoIV(plainPath), nil return fs.nameTransform.EncryptPathNoIV(plainPath), nil
} }
fs.dirIVLock.RLock() fs.dirIVLock.RLock()
defer fs.dirIVLock.RUnlock() cPath, err := fs.nameTransform.EncryptPathDirIV(plainPath, fs.args.Cipherdir)
return 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 // decryptPath - decrypt relative ciphertext path
@ -48,5 +64,5 @@ func (fs *FS) decryptPath(cipherPath string) (string, error) {
} }
fs.dirIVLock.RLock() fs.dirIVLock.RLock()
defer fs.dirIVLock.RUnlock() 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 { type NameTransform struct {
cryptoCore *cryptocore.CryptoCore cryptoCore *cryptocore.CryptoCore
useEME bool useEME bool
longNames bool
DirIVCache dirIVCache DirIVCache dirIVCache
} }
func New(c *cryptocore.CryptoCore, useEME bool) *NameTransform { func New(c *cryptocore.CryptoCore, useEME bool, longNames bool) *NameTransform {
return &NameTransform{ return &NameTransform{
cryptoCore: c, cryptoCore: c,
longNames: longNames,
useEME: useEME, 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" // encryptName - encrypt "plainName", return base64-encoded "cipherName64"
// The used encryption is either CBC or EME, depending on "useEME". // 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 := []byte(plainName)
bin = pad16(bin) bin = pad16(bin)

View File

@ -1,6 +1,7 @@
package nametransform package nametransform
import ( import (
"syscall"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -19,8 +20,9 @@ const (
DirIVFilename = "gocryptfs.diriv" DirIVFilename = "gocryptfs.diriv"
) )
// readDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path) // ReadDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path)
func (be *NameTransform) ReadDirIV(dir string) (iv []byte, readErr error) { // 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) ivfile := filepath.Join(dir, DirIVFilename)
toggledlog.Debug.Printf("ReadDirIV: reading %s\n", ivfile) toggledlog.Debug.Printf("ReadDirIV: reading %s\n", ivfile)
iv, readErr = ioutil.ReadFile(ivfile) iv, readErr = ioutil.ReadFile(ivfile)
@ -62,9 +64,11 @@ func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cip
parentDir := filepath.Dir(plainPath) parentDir := filepath.Dir(plainPath)
found, iv, cParentDir := be.DirIVCache.lookup(parentDir) found, iv, cParentDir := be.DirIVCache.lookup(parentDir)
if found { if found {
//fmt.Print("h")
baseName := filepath.Base(plainPath) 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 cipherPath = cParentDir + "/" + cBaseName
return cipherPath, nil return cipherPath, nil
} }
@ -73,29 +77,32 @@ func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cip
var encryptedNames []string var encryptedNames []string
plainNames := strings.Split(plainPath, "/") plainNames := strings.Split(plainPath, "/")
for _, plainName := range plainNames { for _, plainName := range plainNames {
iv, err = be.ReadDirIV(wd) iv, err = ReadDirIV(wd)
if err != nil { if err != nil {
return "", err 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) encryptedNames = append(encryptedNames, encryptedName)
wd = filepath.Join(wd, encryptedName) wd = filepath.Join(wd, encryptedName)
} }
// Cache the final DirIV
cipherPath = strings.Join(encryptedNames, "/") cipherPath = strings.Join(encryptedNames, "/")
// Cache the final DirIV
cParentDir = filepath.Dir(cipherPath) cParentDir = filepath.Dir(cipherPath)
be.DirIVCache.store(parentDir, iv, cParentDir) be.DirIVCache.store(parentDir, iv, cParentDir)
return cipherPath, nil return cipherPath, nil
} }
// DecryptPathDirIV - decrypt path using EME with DirIV // 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 wd = rootDir
var plainNames []string var plainNames []string
encryptedNames := strings.Split(encryptedPath, "/") encryptedNames := strings.Split(encryptedPath, "/")
toggledlog.Debug.Printf("DecryptPathDirIV: decrypting %v\n", encryptedNames) toggledlog.Debug.Printf("DecryptPathDirIV: decrypting %v\n", encryptedNames)
for _, encryptedName := range encryptedNames { for _, encryptedName := range encryptedNames {
iv, err := be.ReadDirIV(wd) iv, err := ReadDirIV(wd)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -49,7 +49,7 @@ func (be *NameTransform) translatePathNoIV(path string, op int) (string, error)
} }
var newPart string var newPart string
if op == OpEncrypt { if op == OpEncrypt {
newPart = be.encryptName(part, zeroIV) newPart = be.EncryptName(part, zeroIV)
} else { } else {
newPart, err = be.DecryptName(part, zeroIV) newPart, err = be.DecryptName(part, zeroIV)
if err != nil { if err != nil {

View File

@ -15,7 +15,7 @@ func TestEncryptPathNoIV(t *testing.T) {
key := make([]byte, cryptocore.KeyLen) key := make([]byte, cryptocore.KeyLen)
cc := cryptocore.New(key, false, true) cc := cryptocore.New(key, false, true)
fs := New(cc, true) fs := New(cc, true, false)
for _, n := range s { for _, n := range s {
c := fs.EncryptPathNoIV(n) c := fs.EncryptPathNoIV(n)

View File

@ -41,7 +41,8 @@ const (
type argContainer struct { type argContainer struct {
debug, init, zerokey, fusedebug, openssl, passwd, foreground, version, 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, masterkey, mountpoint, cipherdir, cpuprofile, config, extpass,
memprofile string memprofile string
notifypid, scryptn int 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.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.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.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.masterkey, "masterkey", "", "Mount with explicit master key")
flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file")
flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory 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, DirIV: args.diriv,
EMENames: args.emenames, EMENames: args.emenames,
GCMIV128: args.gcmiv128, GCMIV128: args.gcmiv128,
LongNames: args.longnames,
} }
// confFile is nil when "-zerokey" or "-masterkey" was used // confFile is nil when "-zerokey" or "-masterkey" was used
if confFile != nil { if confFile != nil {