reverse: resolve long names in Open and GetAttr

The last patch added functionality for generating gocryptfs.longname.*
files, this patch adds support for mapping them back to the full
filenames.

Note that resolving a long name needs a full readdir. A cache
will be implemented later on to improve performance.
This commit is contained in:
Jakob Unterwurzacher 2016-09-22 23:28:11 +02:00
parent 35bcc2dca2
commit a6a7b424f8
7 changed files with 217 additions and 57 deletions

View File

@ -1,42 +0,0 @@
package fusefrontend_reverse
import (
"crypto/sha256"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/rfjakob/gocryptfs/internal/nametransform"
)
// deriveDirIV derives the DirIV from the directory path by simply hashing it
func deriveDirIV(dirPath string) []byte {
hash := sha256.Sum256([]byte(dirPath))
return hash[:nametransform.DirIVLen]
}
type dirIVFile struct {
// Embed nodefs.defaultFile for a ENOSYS implementation of all methods
nodefs.File
// file content
content []byte
}
func NewDirIVFile(dirPath string) (nodefs.File, fuse.Status) {
return &dirIVFile{
File: nodefs.NewDefaultFile(),
content: deriveDirIV(dirPath),
}, fuse.OK
}
// Read - FUSE call
func (f *dirIVFile) Read(buf []byte, off int64) (resultData fuse.ReadResult, status fuse.Status) {
if off >= int64(len(f.content)) {
return nil, fuse.OK
}
end := int(off) + len(buf)
if end > len(f.content) {
end = len(f.content)
}
return fuse.ReadResultData(f.content[off:end]), fuse.OK
}

View File

@ -0,0 +1,26 @@
package fusefrontend_reverse
import (
"crypto/sha256"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/rfjakob/gocryptfs/internal/nametransform"
)
// deriveDirIV derives the DirIV from the encrypted directory path by
// hashing it
func deriveDirIV(dirPath string) []byte {
hash := sha256.Sum256([]byte(dirPath))
return hash[:nametransform.DirIVLen]
}
func (rfs *reverseFS) newDirIVFile(cRelPath string) (nodefs.File, fuse.Status) {
cDir := saneDir(cRelPath)
absDir, err := rfs.abs(rfs.decryptPath(cDir))
if err != nil {
return nil, fuse.ToStatus(err)
}
return rfs.NewVirtualFile(deriveDirIV(cDir), absDir)
}

View File

@ -0,0 +1,61 @@
package fusefrontend_reverse
import (
"os"
"path/filepath"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/rfjakob/gocryptfs/internal/nametransform"
)
const (
shortNameMax = 176
)
func (rfs *reverseFS) findLongnameParent(dir string, dirIV []byte, longname string) (string, error) {
absDir := filepath.Join(rfs.args.Cipherdir, dir)
dirfd, err := os.Open(absDir)
if err != nil {
return "", err
}
dirEntries, err := dirfd.Readdirnames(-1)
if err != nil {
return "", err
}
for _, e := range dirEntries {
if len(e) <= shortNameMax {
continue
}
cName := rfs.nameTransform.EncryptName(e, dirIV)
if len(cName) <= syscall.NAME_MAX {
panic("logic error or wrong shortNameMax constant?")
}
hName := nametransform.HashLongName(cName)
if longname == hName {
return e, nil
}
}
return "", syscall.ENOENT
}
func (rfs *reverseFS) newNameFile(relPath string) (nodefs.File, fuse.Status) {
dotName := filepath.Base(relPath) // gocryptfs.longname.XYZ.name
longname := dotName[:len(dotName)-len(nametransform.LongNameSuffix)] // gocryptfs.longname.XYZ
cDir := saneDir(relPath)
pDir, err := rfs.decryptPath(cDir)
if err != nil {
return nil, fuse.ToStatus(err)
}
dirIV := deriveDirIV(cDir)
e, err := rfs.findLongnameParent(pDir, dirIV, longname)
if err != nil {
return nil, fuse.ToStatus(err)
}
content := []byte(rfs.nameTransform.EncryptName(e, dirIV))
parentFile := filepath.Join(rfs.args.Cipherdir, pDir)
return rfs.NewVirtualFile(content, parentFile)
}

View File

@ -108,6 +108,13 @@ func isDirIV(relPath string) bool {
return filepath.Base(relPath) == nametransform.DirIVFilename
}
// isNameFile determines if the path points to a gocryptfs.longname.*.name
// file
func isNameFile(relPath string) bool {
fileType := nametransform.NameType(filepath.Base(relPath))
return fileType == nametransform.LongNameFilename
}
func (rfs *reverseFS) inoAwareStat(relPlainPath string) (*fuse.Attr, fuse.Status) {
absPath, err := rfs.abs(relPlainPath, nil)
if err != nil {
@ -148,12 +155,32 @@ func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr
if relPath == configfile.ConfDefaultName {
return rfs.inoAwareStat(configfile.ConfReverseName)
}
if isDirIV(relPath) {
return rfs.dirIVAttr(relPath, context)
}
if rfs.isFiltered(relPath) {
return nil, fuse.EPERM
}
// Handle virtual files
var f nodefs.File
var status fuse.Status
virtual := false
if isDirIV(relPath) {
virtual = true
f, status = rfs.newDirIVFile(relPath)
}
if isNameFile(relPath) {
virtual = true
f, status = rfs.newNameFile(relPath)
}
if virtual {
if !status.Ok() {
fmt.Printf("GetAttr %q: newXFile failed: %v\n", relPath, status)
return nil, status
}
var a fuse.Attr
status = f.GetAttr(&a)
return &a, status
}
cPath, err := rfs.decryptPath(relPath)
if err != nil {
return nil, fuse.ToStatus(err)
@ -191,7 +218,10 @@ func (rfs *reverseFS) Open(relPath string, flags uint32, context *fuse.Context)
return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context)
}
if isDirIV(relPath) {
return NewDirIVFile(relDir(relPath))
return rfs.newDirIVFile(relPath)
}
if isNameFile(relPath) {
return rfs.newNameFile(relPath)
}
if rfs.isFiltered(relPath) {
return nil, fuse.EPERM

View File

@ -5,8 +5,19 @@ import (
"path/filepath"
"strings"
"syscall"
"github.com/rfjakob/gocryptfs/internal/nametransform"
)
// saneDir is like filepath.Dir but returns "" instead of "."
func saneDir(path string) string {
d := filepath.Dir(path)
if d == "." {
return ""
}
return d
}
func (rfs *reverseFS) abs(relPath string, err error) (string, error) {
if err != nil {
return "", err
@ -22,17 +33,29 @@ func (rfs *reverseFS) decryptPath(relPath string) (string, error) {
var transformedParts []string
parts := strings.Split(relPath, "/")
for i, part := range parts {
// Start at the top and recurse
currentDir := filepath.Join(parts[:i]...)
nameType := nametransform.NameType(part)
dirIV := deriveDirIV(currentDir)
var transformedPart string
dirIV := deriveDirIV(filepath.Join(parts[:i]...))
transformedPart, err = rfs.nameTransform.DecryptName(part, dirIV)
if err != nil {
// We get lots of decrypt requests for names like ".Trash" that
// are invalid base64. Convert them to ENOENT so the correct
// error gets returned to the user.
if _, ok := err.(base64.CorruptInputError); ok {
return "", syscall.ENOENT
if nameType == nametransform.LongNameNone {
transformedPart, err = rfs.nameTransform.DecryptName(part, dirIV)
if err != nil {
// We get lots of decrypt requests for names like ".Trash" that
// are invalid base64. Convert them to ENOENT so the correct
// error gets returned to the user.
if _, ok := err.(base64.CorruptInputError); ok {
return "", syscall.ENOENT
}
return "", err
}
return "", err
} else if nameType == nametransform.LongNameContent {
transformedPart, err = rfs.findLongnameParent(currentDir, dirIV, part)
if err != nil {
return "", err
}
} else {
panic("longname bug, .name files should have been handled earlier")
}
transformedParts = append(transformedParts, transformedPart)
}

View File

@ -0,0 +1,57 @@
package fusefrontend_reverse
import (
"fmt"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
type virtualFile struct {
// Embed nodefs.defaultFile for a ENOSYS implementation of all methods
nodefs.File
// file content
content []byte
// absolute path to a parent file
parentFile string
// inode number
ino uint64
}
func (rfs *reverseFS) NewVirtualFile(content []byte, parentFile string) (nodefs.File, fuse.Status) {
return &virtualFile{
File: nodefs.NewDefaultFile(),
content: content,
parentFile: parentFile,
ino: rfs.inoGen.next(),
}, fuse.OK
}
// Read - FUSE call
func (f *virtualFile) Read(buf []byte, off int64) (resultData fuse.ReadResult, status fuse.Status) {
if off >= int64(len(f.content)) {
return nil, fuse.OK
}
end := int(off) + len(buf)
if end > len(f.content) {
end = len(f.content)
}
return fuse.ReadResultData(f.content[off:end]), fuse.OK
}
// GetAttr - FUSE call
func (f *virtualFile) GetAttr(a *fuse.Attr) fuse.Status {
var st syscall.Stat_t
err := syscall.Lstat(f.parentFile, &st)
if err != nil {
fmt.Printf("Lstat %q: %v\n", f.parentFile, err)
return fuse.ToStatus(err)
}
st.Ino = f.ino
st.Size = int64(len(f.content))
st.Mode = syscall.S_IFREG | 0400
st.Nlink = 1
a.FromStat(&st)
return fuse.OK
}

View File

@ -31,9 +31,14 @@ func HashLongName(name string) string {
// Values returned by IsLongName
const (
LongNameContent = iota
// File that stores the file content.
// Example: gocryptfs.longname.URrM8kgxTKYMgCk4hKk7RO9Lcfr30XQof4L_5bD9Iro=
LongNameContent = iota
// File that stores the full encrypted filename.
// Example: gocryptfs.longname.URrM8kgxTKYMgCk4hKk7RO9Lcfr30XQof4L_5bD9Iro=.name
LongNameFilename = iota
LongNameNone = iota
// Example: i1bpTaVLZq7sRNA9mL_2Ig==
LongNameNone = iota
)
// NameType - detect if cName is