-deterministic-names: implement for reverse mode, too

This commit is contained in:
Jakob Unterwurzacher 2021-08-20 17:06:18 +02:00
parent 14bf80301b
commit fbccb16043
10 changed files with 77 additions and 46 deletions

View File

@ -7,7 +7,6 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/rfjakob/gocryptfs/internal/ctlsocksrv" "github.com/rfjakob/gocryptfs/internal/ctlsocksrv"
"github.com/rfjakob/gocryptfs/internal/pathiv"
) )
// Verify that the interface is implemented. // Verify that the interface is implemented.
@ -22,7 +21,7 @@ func (rn *RootNode) EncryptPath(plainPath string) (string, error) {
cipherPath := "" cipherPath := ""
parts := strings.Split(plainPath, "/") parts := strings.Split(plainPath, "/")
for _, part := range parts { for _, part := range parts {
dirIV := pathiv.Derive(cipherPath, pathiv.PurposeDirIV) dirIV := rn.deriveDirIV(cipherPath)
encryptedPart, err := rn.nameTransform.EncryptName(part, dirIV) encryptedPart, err := rn.nameTransform.EncryptName(part, dirIV)
if err != nil { if err != nil {
return "", err return "", err

View File

@ -13,7 +13,6 @@ import (
"github.com/rfjakob/gocryptfs/internal/configfile" "github.com/rfjakob/gocryptfs/internal/configfile"
"github.com/rfjakob/gocryptfs/internal/cryptocore" "github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/nametransform" "github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/pathiv"
"github.com/rfjakob/gocryptfs/internal/syscallcompat" "github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/internal/tlog" "github.com/rfjakob/gocryptfs/internal/tlog"
) )
@ -23,20 +22,16 @@ import (
// This function is symlink-safe through use of openBackingDir() and // This function is symlink-safe through use of openBackingDir() and
// ReadDirIVAt(). // ReadDirIVAt().
func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.Errno) { func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.Errno) {
// Virtual files: at least one gocryptfs.diriv file
virtualFiles := []fuse.DirEntry{
{Mode: virtualFileMode, Name: nametransform.DirIVFilename},
}
rn := n.rootNode() rn := n.rootNode()
// Should we present a virtual gocryptfs.diriv?
var virtualFiles []fuse.DirEntry
if !rn.args.PlaintextNames && !rn.args.DeterministicNames {
virtualFiles = append(virtualFiles, fuse.DirEntry{Mode: virtualFileMode, Name: nametransform.DirIVFilename})
}
// This directory is a mountpoint. Present it as empty. // This directory is a mountpoint. Present it as empty.
if rn.args.OneFileSystem && n.isOtherFilesystem { if rn.args.OneFileSystem && n.isOtherFilesystem {
if rn.args.PlaintextNames { return fs.NewListDirStream(virtualFiles), 0
return fs.NewListDirStream(nil), 0
} else {
// An "empty" directory still has a gocryptfs.diriv file!
return fs.NewListDirStream(virtualFiles), 0
}
} }
d, errno := n.prepareAtSyscall("") d, errno := n.prepareAtSyscall("")
@ -64,7 +59,7 @@ func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.
return n.readdirPlaintextnames(entries) return n.readdirPlaintextnames(entries)
} }
dirIV := pathiv.Derive(d.cPath, pathiv.PurposeDirIV) dirIV := rn.deriveDirIV(d.cPath)
// Encrypt names // Encrypt names
for i := range entries { for i := range entries {
var cName string var cName string

View File

@ -2,6 +2,7 @@ package fusefrontend_reverse
import ( import (
"context" "context"
"log"
"path/filepath" "path/filepath"
"syscall" "syscall"
@ -129,8 +130,8 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus
return return
} }
defer syscall.Close(fd) defer syscall.Close(fd)
diriv := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
rn := n.rootNode() rn := n.rootNode()
diriv := rn.deriveDirIV(d.cPath)
pName, cFullname, errno := rn.findLongnameParent(fd, diriv, nameFile) pName, cFullname, errno := rn.findLongnameParent(fd, diriv, nameFile)
if errno != 0 { if errno != 0 {
return return
@ -160,6 +161,10 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus
// lookupDiriv returns a new Inode for a gocryptfs.diriv file inside `n`. // 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) { func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
if rn := n.rootNode(); rn.args.DeterministicNames {
log.Panic("BUG: lookupDiriv called but DeterministicNames is set")
}
d, errno := n.prepareAtSyscall("") d, errno := n.prepareAtSyscall("")
if errno != 0 { if errno != 0 {
return return

View File

@ -2,6 +2,7 @@ package fusefrontend_reverse
import ( import (
"encoding/base64" "encoding/base64"
"log"
"path/filepath" "path/filepath"
"strings" "strings"
"syscall" "syscall"
@ -72,7 +73,7 @@ func (rn *RootNode) decryptPath(cPath string) (string, error) {
// Start at the top and recurse // Start at the top and recurse
currentCipherDir := filepath.Join(parts[:i]...) currentCipherDir := filepath.Join(parts[:i]...)
currentPlainDir := filepath.Join(transformedParts[:i]...) currentPlainDir := filepath.Join(transformedParts[:i]...)
dirIV := pathiv.Derive(currentCipherDir, pathiv.PurposeDirIV) dirIV := rn.deriveDirIV(currentCipherDir)
transformedPart, err := rn.rDecryptName(parts[i], dirIV, currentPlainDir) transformedPart, err := rn.rDecryptName(parts[i], dirIV, currentPlainDir)
if err != nil { if err != nil {
return "", err return "", err
@ -83,6 +84,17 @@ func (rn *RootNode) decryptPath(cPath string) (string, error) {
return pRelPath, nil return pRelPath, nil
} }
// deriveDirIV wraps pathiv.Derive but takes DeterministicNames into account.
func (rn *RootNode) deriveDirIV(cPath string) []byte {
if rn.args.PlaintextNames {
log.Panic("BUG: deriveDirIV called but PlaintextNames is set")
}
if rn.args.DeterministicNames {
return make([]byte, nametransform.DirIVLen)
}
return pathiv.Derive(cPath, pathiv.PurposeDirIV)
}
// openBackingDir receives an already decrypted relative path // openBackingDir receives an already decrypted relative path
// "pRelPath", opens the directory that contains the target file/dir // "pRelPath", opens the directory that contains the target file/dir
// and returns the fd to the directory and the decrypted name of the // and returns the fd to the directory and the decrypted name of the

View File

@ -43,9 +43,11 @@ func (n *Node) lookupFileType(cName string) fileType {
rn := n.rootNode() rn := n.rootNode()
// In -plaintextname mode, neither diriv nor longname files exist. // In -plaintextname mode, neither diriv nor longname files exist.
if !rn.args.PlaintextNames { if !rn.args.PlaintextNames {
// Is it a gocryptfs.diriv file? if !rn.args.DeterministicNames {
if cName == nametransform.DirIVFilename { // Is it a gocryptfs.diriv file?
return typeDiriv if cName == nametransform.DirIVFilename {
return typeDiriv
}
} }
// Is it a gocryptfs.longname.*.name file? // Is it a gocryptfs.longname.*.name file?
if t := nametransform.NameType(cName); t == nametransform.LongNameFilename { if t := nametransform.NameType(cName); t == nametransform.LongNameFilename {

View File

@ -995,9 +995,3 @@ func TestMountCreat(t *testing.T) {
test_helpers.UnmountPanic(mnt) test_helpers.UnmountPanic(mnt)
} }
} }
// Test -init -deterministic-names
func TestInitDeterministicNames(t *testing.T) {
dir := test_helpers.InitFS(t, "-deterministic-names")
}

View File

@ -119,10 +119,10 @@ func TestConfigMapping(t *testing.T) {
} }
} }
// Check that the access() syscall works on virtual files // Check that the access() syscall works on virtual gocryptfs.diriv files
func TestAccessVirtual(t *testing.T) { func TestAccessVirtualDirIV(t *testing.T) {
if plaintextnames { if plaintextnames || deterministic_names {
t.Skip("test makes no sense for plaintextnames") t.Skip("test makes no sense for plaintextnames or deterministic_names")
} }
var R_OK uint32 = 4 var R_OK uint32 = 4
var W_OK uint32 = 2 var W_OK uint32 = 2

View File

@ -130,8 +130,10 @@ func TestVirtualFileIno(t *testing.T) {
} }
// Lower 48 bits should come from the backing file // Lower 48 bits should come from the backing file
const mask = 0xffffffffffff const mask = 0xffffffffffff
if origInos.parent&mask != cipherInos.diriv&mask { if !deterministic_names { // no diriv files with -deterministic-names
t.Errorf("diriv ino mismatch: %#x vs %#x", origInos.parent, cipherInos.diriv) if origInos.parent&mask != cipherInos.diriv&mask {
t.Errorf("diriv ino mismatch: %#x vs %#x", origInos.parent, cipherInos.diriv)
}
} }
if origInos.child != cipherInos.child { if origInos.child != cipherInos.child {
t.Errorf("child ino mismatch: %d vs %d", origInos.child, cipherInos.child) t.Errorf("child ino mismatch: %d vs %d", origInos.child, cipherInos.child)

View File

@ -2,6 +2,7 @@ package reverse_test
import ( import (
"bytes" "bytes"
"fmt"
"os" "os"
"testing" "testing"
@ -9,8 +10,13 @@ import (
) )
var x240 = string(bytes.Repeat([]byte("x"), 240)) var x240 = string(bytes.Repeat([]byte("x"), 240))
// plaintextnames is true when the currently running test has -plaintextnames active
var plaintextnames bool var plaintextnames bool
// deterministic_names is true when the currently running test has -deterministic-names active
var deterministic_names bool
// dirA is a normal directory // dirA is a normal directory
var dirA string var dirA string
@ -24,10 +30,22 @@ var dirC string
// to "dirC". // to "dirC".
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
var r int var r int
for _, plaintextnames = range []bool{false, true} {
testcases := []struct {
plaintextnames bool
deterministic_names bool
}{
{false, false},
{true, false},
{false, true},
}
for i, tc := range testcases {
argsA := []string{"-reverse"} argsA := []string{"-reverse"}
if plaintextnames { plaintextnames, deterministic_names = tc.plaintextnames, tc.deterministic_names
if tc.plaintextnames {
argsA = append(argsA, "-plaintextnames") argsA = append(argsA, "-plaintextnames")
} else if tc.deterministic_names {
argsA = append(argsA, "-deterministic-names")
} }
dirA = test_helpers.InitFS(nil, argsA...) dirA = test_helpers.InitFS(nil, argsA...)
dirB = test_helpers.TmpDir + "/b" dirB = test_helpers.TmpDir + "/b"
@ -49,6 +67,7 @@ func TestMain(m *testing.M) {
os.RemoveAll(dirC) os.RemoveAll(dirC)
if r != 0 { if r != 0 {
fmt.Printf("testcases[%d] = %#v failed\n", i, tc)
os.Exit(r) os.Exit(r)
} }
} }

View File

@ -1,8 +1,9 @@
package reverse package reverse_test
import ( import (
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"os"
"runtime" "runtime"
"syscall" "syscall"
"testing" "testing"
@ -10,7 +11,10 @@ import (
"github.com/rfjakob/gocryptfs/tests/test_helpers" "github.com/rfjakob/gocryptfs/tests/test_helpers"
) )
func doTestOneFileSystem(t *testing.T, plaintextnames bool) { func TestOneFileSystem(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("only works on linux")
}
// Let's not explode with "TempDir: pattern contains path separator" // Let's not explode with "TempDir: pattern contains path separator"
myEscapedName := url.PathEscape(t.Name()) myEscapedName := url.PathEscape(t.Name())
mnt, err := ioutil.TempDir(test_helpers.TmpDir, myEscapedName) mnt, err := ioutil.TempDir(test_helpers.TmpDir, myEscapedName)
@ -20,6 +24,8 @@ func doTestOneFileSystem(t *testing.T, plaintextnames bool) {
cliArgs := []string{"-reverse", "-zerokey", "-one-file-system"} cliArgs := []string{"-reverse", "-zerokey", "-one-file-system"}
if plaintextnames { if plaintextnames {
cliArgs = append(cliArgs, "-plaintextnames") cliArgs = append(cliArgs, "-plaintextnames")
} else if deterministic_names {
cliArgs = append(cliArgs, "-deterministic-names")
} }
test_helpers.MountOrFatal(t, "/", mnt, cliArgs...) test_helpers.MountOrFatal(t, "/", mnt, cliArgs...)
defer test_helpers.UnmountErr(mnt) defer test_helpers.UnmountErr(mnt)
@ -48,25 +54,22 @@ func doTestOneFileSystem(t *testing.T, plaintextnames bool) {
t.Skip("no mountpoints found, nothing to test") t.Skip("no mountpoints found, nothing to test")
} }
for _, m := range mountpoints { for _, m := range mountpoints {
e, err := ioutil.ReadDir(mnt + "/" + m) dir, err := os.Open(mnt + "/" + m)
if err != nil {
t.Error(err)
}
defer dir.Close()
e, err := dir.Readdirnames(-1)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
expected := 1 expected := 1
if plaintextnames { if plaintextnames || deterministic_names {
expected = 0 expected = 0
} }
if len(e) != expected { if len(e) != expected {
t.Errorf("mountpoint %q does not look empty: %v", m, e) t.Errorf("mountpoint %q should have %d entries, actually has: %v", m, expected, e)
} }
} }
t.Logf("tested %d mountpoints: %v", len(mountpoints), mountpoints) t.Logf("tested %d mountpoints: %v", len(mountpoints), mountpoints)
} }
func TestOneFileSystem(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("only works on linux")
}
t.Run("normal", func(t *testing.T) { doTestOneFileSystem(t, false) })
t.Run("plaintextnames", func(t *testing.T) { doTestOneFileSystem(t, true) })
}