From 84ed139cd2cede9b773fe7892a0bc2515fc1f00f Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 2 Aug 2020 19:33:12 +0200 Subject: [PATCH] v2api/reverse: implement Lookup for longname --- internal/fusefrontend_reverse/node.go | 46 +++------- internal/fusefrontend_reverse/node_helpers.go | 84 ++++++++++++++++++- internal/fusefrontend_reverse/root_node.go | 41 +++++++++ internal/fusefrontend_reverse/rpath.go | 13 ++- internal/nametransform/names.go | 4 + 5 files changed, 148 insertions(+), 40 deletions(-) diff --git a/internal/fusefrontend_reverse/node.go b/internal/fusefrontend_reverse/node.go index 0b2df12..713beee 100644 --- a/internal/fusefrontend_reverse/node.go +++ b/internal/fusefrontend_reverse/node.go @@ -10,7 +10,6 @@ import ( "github.com/hanwen/go-fuse/v2/fuse" "github.com/rfjakob/gocryptfs/internal/configfile" - "github.com/rfjakob/gocryptfs/internal/pathiv" "github.com/rfjakob/gocryptfs/internal/syscallcompat" ) @@ -25,9 +24,16 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch dirfd := int(-1) pName := "" t := n.lookupFileType(name) - // gocryptfs.conf - if t == typeConfig { + if t == typeDiriv { + // gocryptfs.diriv + return n.lookupDiriv(ctx, out) + } else if t == typeName { + // gocryptfs.longname.*.name + return n.lookupLongnameName(ctx, name, out) + } else if t == typeConfig { + // gocryptfs.conf var err error + pName = configfile.ConfReverseName rn := n.rootNode() dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, "") if err != nil { @@ -35,34 +41,6 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch return } defer syscall.Close(dirfd) - pName = configfile.ConfReverseName - } else if t == typeDiriv { - // gocryptfs.diriv - dirfd, pName, errno = n.prepareAtSyscall("") - if errno != 0 { - return - } - defer syscall.Close(dirfd) - st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) - if err != nil { - errno = fs.ToErrno(err) - return - } - content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV) - var vf *virtualFile - vf, errno = n.newVirtualFile(content, st, inoTagDirIV) - if errno != 0 { - return nil, errno - } - out.Attr = vf.attr - // Create child node - id := fs.StableAttr{Mode: uint32(vf.attr.Mode), Gen: 1, Ino: vf.attr.Ino} - ch = n.NewInode(ctx, vf, id) - return - } else if t == typeName { - // gocryptfs.longname.*.name - - // TODO } else if t == typeReal { // real file dirfd, pName, errno = n.prepareAtSyscall(name) @@ -71,21 +49,17 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch } defer syscall.Close(dirfd) } - // Get device number and inode number into `st` st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) if err != nil { return nil, fs.ToErrno(err) } - // Create new inode and fill `out` ch = n.newChild(ctx, st, out) - + // Translate ciphertext size in `out.Attr.Size` to plaintext size if t == typeReal { - // Translate ciphertext size in `out.Attr.Size` to plaintext size n.translateSize(dirfd, pName, &out.Attr) } - return ch, 0 } diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go index 24cdbd1..df9de9b 100644 --- a/internal/fusefrontend_reverse/node_helpers.go +++ b/internal/fusefrontend_reverse/node_helpers.go @@ -5,8 +5,24 @@ import ( "path/filepath" "syscall" + "golang.org/x/sys/unix" + "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" + + "github.com/rfjakob/gocryptfs/internal/pathiv" + "github.com/rfjakob/gocryptfs/internal/syscallcompat" +) + +const ( + // File names are padded to 16-byte multiples, encrypted and + // base64-encoded. We can encode at most 176 bytes to stay below the 255 + // bytes limit: + // * base64(176 bytes) = 235 bytes + // * base64(192 bytes) = 256 bytes (over 255!) + // But the PKCS#7 padding is at least one byte. This means we can only use + // 175 bytes for the file name. + shortNameMax = 175 ) // translateSize translates the ciphertext size in `out` into plaintext size. @@ -36,13 +52,13 @@ func (n *Node) rootNode() *RootNode { // If you pass a `child` file name, the (dirfd, cName) pair will refer to // a child of this node. // If `child` is empty, the (dirfd, cName) pair refers to this node itself. -func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno syscall.Errno) { +func (n *Node) prepareAtSyscall(child string) (dirfd int, pName string, errno syscall.Errno) { p := n.Path() if child != "" { p = filepath.Join(p, child) } rn := n.rootNode() - dirfd, cName, err := rn.openBackingDir(p) + dirfd, pName, err := rn.openBackingDir(p) if err != nil { errno = fs.ToErrno(err) } @@ -71,3 +87,67 @@ func (n *Node) isRoot() bool { rn := n.rootNode() return &rn.Node == n } + +func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { + dirfd, pName1, errno := n.prepareAtSyscall("") + if errno != 0 { + return + } + defer syscall.Close(dirfd) + + // Find the file the gocryptfs.longname.XYZ.name file belongs to in the + // directory listing + fd, err := syscallcompat.Openat(dirfd, pName1, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) + if err != nil { + errno = fs.ToErrno(err) + return + } + diriv := pathiv.Derive(n.Path(), pathiv.PurposeDirIV) + rn := n.rootNode() + pName, cFullname, errno := rn.findLongnameParent(fd, diriv, nameFile) + if errno != 0 { + return + } + // Get attrs from parent file + st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) + if err != nil { + errno = fs.ToErrno(err) + return + } + var vf *virtualFile + vf, errno = n.newVirtualFile([]byte(cFullname), st, inoTagNameFile) + if errno != 0 { + return nil, errno + } + out.Attr = vf.attr + // Create child node + id := fs.StableAttr{Mode: uint32(vf.attr.Mode), Gen: 1, Ino: vf.attr.Ino} + ch = n.NewInode(ctx, vf, id) + return + +} + +// lookupDiriv returns a new Inode for a gocryptfs.diriv file inside `n`. +func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { + dirfd, pName, errno := n.prepareAtSyscall("") + if errno != 0 { + return + } + defer syscall.Close(dirfd) + st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) + if err != nil { + errno = fs.ToErrno(err) + return + } + content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV) + var vf *virtualFile + vf, errno = n.newVirtualFile(content, st, inoTagDirIV) + if errno != 0 { + return nil, errno + } + out.Attr = vf.attr + // Create child node + id := fs.StableAttr{Mode: uint32(vf.attr.Mode), Gen: 1, Ino: vf.attr.Ino} + ch = n.NewInode(ctx, vf, id) + return +} diff --git a/internal/fusefrontend_reverse/root_node.go b/internal/fusefrontend_reverse/root_node.go index 726380f..4297ecf 100644 --- a/internal/fusefrontend_reverse/root_node.go +++ b/internal/fusefrontend_reverse/root_node.go @@ -1,10 +1,19 @@ package fusefrontend_reverse import ( + "log" + "strings" + "syscall" + + "golang.org/x/sys/unix" + + "github.com/hanwen/go-fuse/v2/fs" + "github.com/rfjakob/gocryptfs/internal/contentenc" "github.com/rfjakob/gocryptfs/internal/fusefrontend" "github.com/rfjakob/gocryptfs/internal/inomap" "github.com/rfjakob/gocryptfs/internal/nametransform" + "github.com/rfjakob/gocryptfs/internal/syscallcompat" "github.com/sabhiram/go-gitignore" ) @@ -37,3 +46,35 @@ func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransfo excluder: prepareExcluder(args), } } + +// You can pass either gocryptfs.longname.XYZ.name or gocryptfs.longname.XYZ. +func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (pName string, cFullName string, errno syscall.Errno) { + if strings.HasSuffix(longname, nametransform.LongNameSuffix) { + longname = nametransform.RemoveLongNameSuffix(longname) + } + entries, err := syscallcompat.Getdents(fd) + if err != nil { + errno = fs.ToErrno(err) + return + } + for _, entry := range entries { + if len(entry.Name) <= shortNameMax { + continue + } + cFullName = rn.nameTransform.EncryptName(entry.Name, diriv) + if len(cFullName) <= unix.NAME_MAX { + // Entry should have been skipped by the "continue" above + log.Panic("logic error or wrong shortNameMax constant?") + } + hName := rn.nameTransform.HashLongName(cFullName) + if longname == hName { + pName = entry.Name + break + } + } + if pName == "" { + errno = syscall.ENOENT + return + } + return +} diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go index 35b9361..d212dfc 100644 --- a/internal/fusefrontend_reverse/rpath.go +++ b/internal/fusefrontend_reverse/rpath.go @@ -47,11 +47,20 @@ func (rfs *RootNode) rDecryptName(cName string, dirIV []byte, pDir string) (pNam return "", err } } else if nameType == nametransform.LongNameContent { - panic("todo") - //pName, err = rfs.findLongnameParent(pDir, dirIV, cName) + dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, filepath.Dir(pDir)) if err != nil { return "", err } + defer syscall.Close(dirfd) + fd, err := syscallcompat.Openat(dirfd, filepath.Base(pDir), syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) + if err != nil { + return "", err + } + var errno syscall.Errno + pName, _, errno = rfs.findLongnameParent(fd, dirIV, cName) + if errno != 0 { + return "", errno + } } else { // It makes no sense to decrypt a ".name" file. This is a virtual file // that has no representation in the plaintext filesystem. ".name" diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index a659f0a..119d592 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -23,6 +23,10 @@ type NameTransformer interface { DecryptName(cipherName string, iv []byte) (string, error) EncryptName(plainName string, iv []byte) string EncryptAndHashName(name string, iv []byte) (string, error) + // HashLongName - take the hash of a long string "name" and return + // "gocryptfs.longname.[sha256]" + // + // This function does not do any I/O. HashLongName(name string) string WriteLongNameAt(dirfd int, hashName string, plainName string) error B64EncodeToString(src []byte) string