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

View File

@ -13,7 +13,6 @@ import (
"github.com/rfjakob/gocryptfs/internal/configfile"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/pathiv"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/internal/tlog"
)
@ -23,21 +22,17 @@ import (
// This function is symlink-safe through use of openBackingDir() and
// ReadDirIVAt().
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()
// 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.
if rn.args.OneFileSystem && n.isOtherFilesystem {
if rn.args.PlaintextNames {
return fs.NewListDirStream(nil), 0
} else {
// An "empty" directory still has a gocryptfs.diriv file!
return fs.NewListDirStream(virtualFiles), 0
}
}
d, errno := n.prepareAtSyscall("")
if errno != 0 {
@ -64,7 +59,7 @@ func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.
return n.readdirPlaintextnames(entries)
}
dirIV := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
dirIV := rn.deriveDirIV(d.cPath)
// Encrypt names
for i := range entries {
var cName string

View File

@ -2,6 +2,7 @@ package fusefrontend_reverse
import (
"context"
"log"
"path/filepath"
"syscall"
@ -129,8 +130,8 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus
return
}
defer syscall.Close(fd)
diriv := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
rn := n.rootNode()
diriv := rn.deriveDirIV(d.cPath)
pName, cFullname, errno := rn.findLongnameParent(fd, diriv, nameFile)
if errno != 0 {
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`.
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("")
if errno != 0 {
return

View File

@ -2,6 +2,7 @@ package fusefrontend_reverse
import (
"encoding/base64"
"log"
"path/filepath"
"strings"
"syscall"
@ -72,7 +73,7 @@ func (rn *RootNode) decryptPath(cPath string) (string, error) {
// Start at the top and recurse
currentCipherDir := filepath.Join(parts[:i]...)
currentPlainDir := filepath.Join(transformedParts[:i]...)
dirIV := pathiv.Derive(currentCipherDir, pathiv.PurposeDirIV)
dirIV := rn.deriveDirIV(currentCipherDir)
transformedPart, err := rn.rDecryptName(parts[i], dirIV, currentPlainDir)
if err != nil {
return "", err
@ -83,6 +84,17 @@ func (rn *RootNode) decryptPath(cPath string) (string, error) {
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
// "pRelPath", opens the directory that contains the target file/dir
// and returns the fd to the directory and the decrypted name of the

View File

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

View File

@ -995,9 +995,3 @@ func TestMountCreat(t *testing.T) {
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
func TestAccessVirtual(t *testing.T) {
if plaintextnames {
t.Skip("test makes no sense for plaintextnames")
// Check that the access() syscall works on virtual gocryptfs.diriv files
func TestAccessVirtualDirIV(t *testing.T) {
if plaintextnames || deterministic_names {
t.Skip("test makes no sense for plaintextnames or deterministic_names")
}
var R_OK uint32 = 4
var W_OK uint32 = 2

View File

@ -130,9 +130,11 @@ func TestVirtualFileIno(t *testing.T) {
}
// Lower 48 bits should come from the backing file
const mask = 0xffffffffffff
if !deterministic_names { // no diriv files with -deterministic-names
if origInos.parent&mask != cipherInos.diriv&mask {
t.Errorf("diriv ino mismatch: %#x vs %#x", origInos.parent, cipherInos.diriv)
}
}
if 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 (
"bytes"
"fmt"
"os"
"testing"
@ -9,8 +10,13 @@ import (
)
var x240 = string(bytes.Repeat([]byte("x"), 240))
// plaintextnames is true when the currently running test has -plaintextnames active
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
var dirA string
@ -24,10 +30,22 @@ var dirC string
// to "dirC".
func TestMain(m *testing.M) {
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"}
if plaintextnames {
plaintextnames, deterministic_names = tc.plaintextnames, tc.deterministic_names
if tc.plaintextnames {
argsA = append(argsA, "-plaintextnames")
} else if tc.deterministic_names {
argsA = append(argsA, "-deterministic-names")
}
dirA = test_helpers.InitFS(nil, argsA...)
dirB = test_helpers.TmpDir + "/b"
@ -49,6 +67,7 @@ func TestMain(m *testing.M) {
os.RemoveAll(dirC)
if r != 0 {
fmt.Printf("testcases[%d] = %#v failed\n", i, tc)
os.Exit(r)
}
}

View File

@ -1,8 +1,9 @@
package reverse
package reverse_test
import (
"io/ioutil"
"net/url"
"os"
"runtime"
"syscall"
"testing"
@ -10,7 +11,10 @@ import (
"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"
myEscapedName := url.PathEscape(t.Name())
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"}
if plaintextnames {
cliArgs = append(cliArgs, "-plaintextnames")
} else if deterministic_names {
cliArgs = append(cliArgs, "-deterministic-names")
}
test_helpers.MountOrFatal(t, "/", mnt, cliArgs...)
defer test_helpers.UnmountErr(mnt)
@ -48,25 +54,22 @@ func doTestOneFileSystem(t *testing.T, plaintextnames bool) {
t.Skip("no mountpoints found, nothing to test")
}
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 {
t.Error(err)
}
expected := 1
if plaintextnames {
if plaintextnames || deterministic_names {
expected = 0
}
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)
}
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) })
}