-deterministic-names: implement for reverse mode, too
This commit is contained in:
parent
14bf80301b
commit
fbccb16043
@ -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
|
||||||
|
@ -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,21 +22,17 @@ 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(nil), 0
|
|
||||||
} else {
|
|
||||||
// An "empty" directory still has a gocryptfs.diriv file!
|
|
||||||
return fs.NewListDirStream(virtualFiles), 0
|
return fs.NewListDirStream(virtualFiles), 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
d, errno := n.prepareAtSyscall("")
|
d, errno := n.prepareAtSyscall("")
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -43,10 +43,12 @@ 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 {
|
||||||
|
if !rn.args.DeterministicNames {
|
||||||
// Is it a gocryptfs.diriv file?
|
// Is it a gocryptfs.diriv file?
|
||||||
if cName == nametransform.DirIVFilename {
|
if cName == nametransform.DirIVFilename {
|
||||||
return typeDiriv
|
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 {
|
||||||
return typeName
|
return typeName
|
||||||
|
@ -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")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
|
@ -130,9 +130,11 @@ 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 !deterministic_names { // no diriv files with -deterministic-names
|
||||||
if origInos.parent&mask != cipherInos.diriv&mask {
|
if origInos.parent&mask != cipherInos.diriv&mask {
|
||||||
t.Errorf("diriv ino mismatch: %#x vs %#x", origInos.parent, cipherInos.diriv)
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) })
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user