diff --git a/internal/fusefrontend_reverse/rfile.go b/internal/fusefrontend_reverse/rfile.go new file mode 100644 index 0000000..746a0d6 --- /dev/null +++ b/internal/fusefrontend_reverse/rfile.go @@ -0,0 +1,27 @@ +package fusefrontend_reverse + +import ( + "os" + + "github.com/hanwen/go-fuse/fuse" + "github.com/hanwen/go-fuse/fuse/nodefs" + + "github.com/rfjakob/gocryptfs/internal/contentenc" +) + +type file struct { + fd *os.File + // Content encryption helper + contentEnc *contentenc.ContentEnc + + // nodefs.defaultFile returns ENOSYS for all operations + nodefs.File +} + +func NewFile(fd *os.File, contentEnc *contentenc.ContentEnc) (nodefs.File, fuse.Status) { + return &file{ + fd: fd, + contentEnc: contentEnc, + File: nodefs.NewDefaultFile(), + }, fuse.OK +} diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go new file mode 100644 index 0000000..914dccb --- /dev/null +++ b/internal/fusefrontend_reverse/rfs.go @@ -0,0 +1,93 @@ +package fusefrontend_reverse + +import ( + "os" + + "github.com/hanwen/go-fuse/fuse" + "github.com/hanwen/go-fuse/fuse/nodefs" + "github.com/hanwen/go-fuse/fuse/pathfs" + + "github.com/rfjakob/gocryptfs/internal/contentenc" + "github.com/rfjakob/gocryptfs/internal/cryptocore" + "github.com/rfjakob/gocryptfs/internal/fusefrontend" + "github.com/rfjakob/gocryptfs/internal/nametransform" +) + +type FS struct { + // loopbackFileSystem, see go-fuse/fuse/pathfs/loopback.go + pathfs.FileSystem + // Stores configuration arguments + args fusefrontend.Args + // Filename encryption helper + nameTransform *nametransform.NameTransform + // Content encryption helper + contentEnc *contentenc.ContentEnc +} + +// Encrypted FUSE overlay filesystem +func NewFS(args fusefrontend.Args) *FS { + cryptoCore := cryptocore.New(args.Masterkey, args.OpenSSL, true) + contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS) + nameTransform := nametransform.New(cryptoCore, args.LongNames) + + return &FS{ + FileSystem: pathfs.NewLoopbackFileSystem(args.Cipherdir), + args: args, + nameTransform: nameTransform, + contentEnc: contentEnc, + } +} + +func (fs *FS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { + if fs.isFiltered(relPath) { + return nil, fuse.EPERM + } + relPath, err := fs.decryptPath(relPath) + if err != nil { + return nil, fuse.ToStatus(err) + } + a, status := fs.FileSystem.GetAttr(relPath, context) + if a == nil { + return a, status + } + // Calculate encrypted file size + if a.IsRegular() { + a.Size = fs.contentEnc.PlainSizeToCipherSize(a.Size) + } + return a, fuse.OK +} + +func (fs *FS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { + if fs.isFiltered(relPath) { + return nil, fuse.EPERM + } + absPath, err := fs.abs(fs.decryptPath(relPath)) + if err != nil { + return nil, fuse.ToStatus(err) + } + f, err := os.OpenFile(absPath, int(flags), 0666) + if err != nil { + return nil, fuse.ToStatus(err) + } + return NewFile(f, fs.contentEnc) +} + +func (fs *FS) OpenDir(relPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { + relPath, err := fs.decryptPath(relPath) + if err != nil { + return nil, fuse.ToStatus(err) + } + // Read plaintext dir + entries, status := fs.FileSystem.OpenDir(relPath, context) + if entries == nil { + return nil, status + } + // Encrypt names + for i := range entries { + entries[i].Name, err = fs.encryptPath(entries[i].Name) + if err != nil { + return nil, fuse.ToStatus(err) + } + } + return entries, fuse.OK +} diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go new file mode 100644 index 0000000..9377958 --- /dev/null +++ b/internal/fusefrontend_reverse/rpath.go @@ -0,0 +1,56 @@ +package fusefrontend_reverse + +import ( + "path/filepath" + "strings" +) + +func (fs *FS) abs(relPath string, err error) (string, error) { + if err != nil { + return "", err + } + return filepath.Join(fs.args.Cipherdir, relPath), nil +} + +const ( + ENCRYPT = iota + DECRYPT +) + +func (fs *FS) encryptPath(relPath string) (string, error) { + return fs.transformPath(relPath, ENCRYPT) +} + +func (fs *FS) decryptPath(relPath string) (string, error) { + return fs.transformPath(relPath, DECRYPT) +} + +func (fs *FS) transformPath(relPath string, direction int) (string, error) { + if fs.args.PlaintextNames { + return relPath, nil + } + var err error + var transformedParts []string + iv := make([]byte, 16) + parts := strings.Split(relPath, "/") + for _, part := range parts { + var transformedPart string + switch direction { + case ENCRYPT: + transformedPart = fs.nameTransform.EncryptName(part, iv) + case DECRYPT: + transformedPart, err = fs.nameTransform.DecryptName(part, iv) + if err != nil { + return "", err + } + default: + panic("bug: invalid direction value") + } + transformedParts = append(transformedParts, transformedPart) + } + return filepath.Join(transformedParts...), nil +} + +func (fs *FS) isFiltered(relPath string) bool { + return false +}