longnames part I: Create and OpenDir work with long filenames > 176 bytes
Todo: Rename, Unlink, Rmdir, Mknod, Mkdir
This commit is contained in:
parent
5abd9cec13
commit
e111e20649
@ -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
|
||||
|
@ -9,4 +9,5 @@ type Args struct {
|
||||
DirIV bool
|
||||
EMENames bool
|
||||
GCMIV128 bool
|
||||
LongNames bool
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
68
internal/nametransform/longnames.go
Normal file
68
internal/nametransform/longnames.go
Normal 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
|
||||
}
|
22
internal/nametransform/longnames_test.go
Normal file
22
internal/nametransform/longnames_test.go
Normal 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")
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
5
main.go
5
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user