From be9dfe3a894bd00a2157bfc3dd19e98bcc171691 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Mon, 19 Sep 2016 23:40:43 +0200 Subject: [PATCH] reverse: implement dynamic diriv Introduce a unique per-directory diriv that is generated by hashing the encrypted directory path. --- internal/fusefrontend_reverse/diriv.go | 42 +++++++++ internal/fusefrontend_reverse/rfs.go | 113 ++++++++++++++----------- internal/fusefrontend_reverse/rpath.go | 47 +++------- 3 files changed, 116 insertions(+), 86 deletions(-) create mode 100644 internal/fusefrontend_reverse/diriv.go diff --git a/internal/fusefrontend_reverse/diriv.go b/internal/fusefrontend_reverse/diriv.go new file mode 100644 index 0000000..c4a93e4 --- /dev/null +++ b/internal/fusefrontend_reverse/diriv.go @@ -0,0 +1,42 @@ +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 +} diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 3243dfd..d07971a 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -3,8 +3,7 @@ package fusefrontend_reverse import ( "fmt" "os" - "path" - "strings" + "path/filepath" "syscall" "github.com/hanwen/go-fuse/fuse" @@ -50,47 +49,59 @@ func NewFS(args fusefrontend.Args) *reverseFS { } } -func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { - // Handle gocryptfs.diriv - if relPath == nametransform.DirIVFilename || - strings.HasSuffix(relPath, nametransform.DirIVFilename) { - - fmt.Printf("Handling gocryptfs.diriv\n") - - cDir := path.Dir(relPath) - if cDir == "." { - cDir = "" - } - dir, err := rfs.decryptPath(cDir) - if err != nil { - fmt.Printf("decrypt err %q\n", cDir) - return nil, fuse.ToStatus(err) - } - // Does the parent dir exist? - a, status := rfs.loopbackfs.GetAttr(dir, context) - if !status.Ok() { - fmt.Printf("missing parent\n") - return nil, status - } - // Is it a dir at all? - if !a.IsDir() { - fmt.Printf("not isdir\n") - return nil, fuse.ENOTDIR - } - // Does the user have execute permissions? - if a.Mode&syscall.S_IXUSR == 0 { - fmt.Printf("not exec") - return nil, fuse.EPERM - } - // All good. Let's fake the file. - // We use the inode number of the parent dir (can this cause problems?). - a.Mode = DirIVMode - a.Size = nametransform.DirIVLen - a.Nlink = 1 - - return a, fuse.OK +// relDir is identical to filepath.Dir excepts that it returns "" when +// filepath.Dir would return ".". +// In the FUSE API, the root directory is called "", and we actually want that. +func relDir(path string) string { + dir := filepath.Dir(path) + if dir == "." { + return "" } + return dir +} +// dirIVAttr handles GetAttr requests for the virtual gocryptfs.diriv files. +func (rfs *reverseFS) dirIVAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { + cDir := relDir(relPath) + dir, err := rfs.decryptPath(cDir) + if err != nil { + fmt.Printf("decrypt err %q\n", cDir) + return nil, fuse.ToStatus(err) + } + // Does the parent dir exist? + a, status := rfs.loopbackfs.GetAttr(dir, context) + if !status.Ok() { + fmt.Printf("missing parent\n") + return nil, status + } + // Is it a dir at all? + if !a.IsDir() { + fmt.Printf("not isdir\n") + return nil, fuse.ENOTDIR + } + // Does the user have execute permissions? + if a.Mode&syscall.S_IXUSR == 0 { + fmt.Printf("not exec") + return nil, fuse.EPERM + } + // All good. Let's fake the file. + // We use the inode number of the parent dir (can this cause problems?). + a.Mode = DirIVMode + a.Size = nametransform.DirIVLen + a.Nlink = 1 + + return a, fuse.OK +} + +// isDirIV determines if the path points to a gocryptfs.diriv file +func isDirIV(relPath string) bool { + return filepath.Base(relPath) == nametransform.DirIVFilename +} + +func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { + if isDirIV(relPath) { + return rfs.dirIVAttr(relPath, context) + } if rfs.isFiltered(relPath) { return nil, fuse.EPERM } @@ -110,17 +121,23 @@ func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr } func (rfs *reverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status { + if isDirIV(relPath) { + return fuse.OK + } if rfs.isFiltered(relPath) { return fuse.EPERM } - cPath, err := rfs.abs(rfs.encryptPath(relPath)) + absPath, err := rfs.abs(rfs.decryptPath(relPath)) if err != nil { return fuse.ToStatus(err) } - return fuse.ToStatus(syscall.Access(cPath, mode)) + return fuse.ToStatus(syscall.Access(absPath, mode)) } func (rfs *reverseFS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { + if isDirIV(relPath) { + return NewDirIVFile(relPath) + } if rfs.isFiltered(relPath) { return nil, fuse.EPERM } @@ -135,8 +152,8 @@ func (rfs *reverseFS) Open(relPath string, flags uint32, context *fuse.Context) return NewFile(f, rfs.contentEnc) } -func (rfs *reverseFS) OpenDir(relPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { - relPath, err := rfs.decryptPath(relPath) +func (rfs *reverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { + relPath, err := rfs.decryptPath(cipherPath) if err != nil { return nil, fuse.ToStatus(err) } @@ -146,11 +163,9 @@ func (rfs *reverseFS) OpenDir(relPath string, context *fuse.Context) ([]fuse.Dir return nil, status } // Encrypt names + dirIV := deriveDirIV(cipherPath) for i := range entries { - entries[i].Name, err = rfs.encryptPath(entries[i].Name) - if err != nil { - return nil, fuse.ToStatus(err) - } + entries[i].Name = rfs.nameTransform.EncryptName(entries[i].Name, dirIV) } // Add virtual gocryptfs.diriv entries = append(entries, fuse.DirEntry{syscall.S_IFREG | 0400, nametransform.DirIVFilename}) diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go index 7e11ca3..a15b31a 100644 --- a/internal/fusefrontend_reverse/rpath.go +++ b/internal/fusefrontend_reverse/rpath.go @@ -2,18 +2,11 @@ package fusefrontend_reverse import ( "encoding/base64" - "fmt" "path/filepath" "strings" "syscall" ) -var zeroDirIV []byte - -func init() { - zeroDirIV = make([]byte, 16) -} - func (rfs *reverseFS) abs(relPath string, err error) (string, error) { if err != nil { return "", err @@ -21,45 +14,25 @@ func (rfs *reverseFS) abs(relPath string, err error) (string, error) { return filepath.Join(rfs.args.Cipherdir, relPath), nil } -const ( - ENCRYPT = iota - DECRYPT -) - -func (rfs *reverseFS) encryptPath(relPath string) (string, error) { - return rfs.transformPath(relPath, ENCRYPT) -} - func (rfs *reverseFS) decryptPath(relPath string) (string, error) { - return rfs.transformPath(relPath, DECRYPT) -} - -func (rfs *reverseFS) transformPath(relPath string, direction int) (string, error) { if rfs.args.PlaintextNames || relPath == "" { return relPath, nil } var err error var transformedParts []string parts := strings.Split(relPath, "/") - for _, part := range parts { + for i, part := range parts { var transformedPart string - switch direction { - case ENCRYPT: - transformedPart = rfs.nameTransform.EncryptName(part, zeroDirIV) - case DECRYPT: - transformedPart, err = rfs.nameTransform.DecryptName(part, zeroDirIV) - 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 { - fmt.Printf("converting to ENOENT\n") - return "", syscall.ENOENT - } - return "", err + 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 } - default: - panic("bug: invalid direction value") + return "", err } transformedParts = append(transformedParts, transformedPart) }