diff --git a/internal/fusefrontend/ctlsock_interface.go b/internal/fusefrontend/ctlsock_interface.go index 8d8b096..964775b 100644 --- a/internal/fusefrontend/ctlsock_interface.go +++ b/internal/fusefrontend/ctlsock_interface.go @@ -1,9 +1,12 @@ package fusefrontend import ( - "errors" + "fmt" + "path" + "strings" "github.com/rfjakob/gocryptfs/internal/ctlsock" + "github.com/rfjakob/gocryptfs/internal/nametransform" ) var _ ctlsock.Interface = &FS{} // Verify that interface is implemented. @@ -15,5 +18,33 @@ func (fs *FS) EncryptPath(plainPath string) (string, error) { // DecryptPath implements ctlsock.Backend func (fs *FS) DecryptPath(cipherPath string) (string, error) { - return "", errors.New("Forward mode does not have path decryption implemented") + if fs.args.PlaintextNames || cipherPath == "" { + return cipherPath, nil + } + plainPath := "" + parts := strings.Split(cipherPath, "/") + wd := fs.args.Cipherdir + for _, part := range parts { + dirIV, err := nametransform.ReadDirIV(wd) + if err != nil { + fmt.Printf("ReadDirIV: %v\n", err) + return "", err + } + longPart := part + if nametransform.IsLongContent(part) { + longPart, err = nametransform.ReadLongName(wd + "/" + part) + if err != nil { + fmt.Printf("ReadLongName: %v\n", err) + return "", err + } + } + name, err := fs.nameTransform.DecryptName(longPart, dirIV) + if err != nil { + fmt.Printf("DecryptName: %v\n", err) + return "", err + } + plainPath = path.Join(plainPath, name) + wd = path.Join(wd, part) + } + return plainPath, nil } diff --git a/tests/defaults/ctlsock_test.go b/tests/defaults/ctlsock_test.go new file mode 100644 index 0000000..13e6912 --- /dev/null +++ b/tests/defaults/ctlsock_test.go @@ -0,0 +1,98 @@ +package defaults + +import ( + "os" + "syscall" + "testing" + + "github.com/rfjakob/gocryptfs/internal/ctlsock" + "github.com/rfjakob/gocryptfs/tests/test_helpers" +) + +func TestCtlSock(t *testing.T) { + cDir := test_helpers.InitFS(t) + pDir := cDir + ".mnt" + sock := cDir + ".sock" + test_helpers.MountOrFatal(t, cDir, pDir, "-ctlsock="+sock, "-extpass", "echo test") + defer test_helpers.UnmountPanic(pDir) + req := ctlsock.RequestStruct{ + EncryptPath: "foobar", + } + response := test_helpers.QueryCtlSock(t, sock, req) + if response.Result == "" || response.ErrNo != 0 { + t.Errorf("got an error reply: %+v", response) + } + req.EncryptPath = "not-existing-dir/xyz" + response = test_helpers.QueryCtlSock(t, sock, req) + if response.ErrNo != int32(syscall.ENOENT) || response.Result != "" { + t.Errorf("incorrect error handling: %+v", response) + } + // Strange paths should not cause a crash + crashers := []string{"/foo", "foo/", "/foo/", ".", "/////", "/../../."} + for _, c := range crashers { + req.EncryptPath = c + // QueryCtlSock calls t.Fatal if it gets EOF when gocryptfs panics + response = test_helpers.QueryCtlSock(t, sock, req) + if response.WarnText == "" { + t.Errorf("We should get a warning about non-canonical paths here") + } + } +} + +func TestCtlSockDecrypt(t *testing.T) { + cDir := test_helpers.InitFS(t) + pDir := cDir + ".mnt" + sock := cDir + ".sock" + test_helpers.MountOrFatal(t, cDir, pDir, "-ctlsock="+sock, "-extpass", "echo test") + defer test_helpers.UnmountPanic(pDir) + + paths := []string{ + "xxxxxxx123456789", + "foo/bar/baz", + test_helpers.X255, + "123/" + test_helpers.X255, + "123/" + test_helpers.X255 + "/456", + } + + for _, p := range paths { + // Create path + err := os.MkdirAll(pDir+"/"+p, 0700) + if err != nil { + t.Fatal(err) + } + // Encrypt the path through the ctlsock + req := ctlsock.RequestStruct{ + EncryptPath: p, + } + response := test_helpers.QueryCtlSock(t, sock, req) + if response.Result == "" || response.ErrNo != 0 { + t.Fatalf("got an error reply: %+v", response) + } + // Check if the encrypted path actually exists + cPath := response.Result + _, err = os.Stat(cDir + "/" + cPath) + if err != nil { + t.Fatal(err) + } + // Decrypt the path through the ctlsock and see if we get the original path + req = ctlsock.RequestStruct{ + DecryptPath: cPath, + } + response = test_helpers.QueryCtlSock(t, sock, req) + if response.Result == "" || response.ErrNo != 0 { + t.Errorf("query=%+v, response=%+v", req, response) + continue + } + if response.Result != p { + t.Errorf("want=%q got=%q", p, response.Result) + } + } +} + +func TestCtlSockDecryptCrash(t *testing.T) { + cDir := test_helpers.InitFS(t) + pDir := cDir + ".mnt" + sock := cDir + ".sock" + test_helpers.MountOrFatal(t, cDir, pDir, "-ctlsock="+sock, "-extpass", "echo test") + defer test_helpers.UnmountPanic(pDir) +} diff --git a/tests/defaults/main_test.go b/tests/defaults/main_test.go index 2ea9e12..ca11b43 100644 --- a/tests/defaults/main_test.go +++ b/tests/defaults/main_test.go @@ -8,10 +8,8 @@ import ( "os/exec" "runtime" "sync" - "syscall" "testing" - "github.com/rfjakob/gocryptfs/internal/ctlsock" "github.com/rfjakob/gocryptfs/tests/test_helpers" ) @@ -42,36 +40,6 @@ func Test1980Tar(t *testing.T) { } } -func TestCtlSock(t *testing.T) { - cDir := test_helpers.InitFS(t) - pDir := cDir + ".mnt" - sock := cDir + ".sock" - test_helpers.MountOrFatal(t, cDir, pDir, "-ctlsock="+sock, "-extpass", "echo test") - defer test_helpers.UnmountPanic(pDir) - req := ctlsock.RequestStruct{ - EncryptPath: "foobar", - } - response := test_helpers.QueryCtlSock(t, sock, req) - if response.Result == "" || response.ErrNo != 0 { - t.Errorf("got an error reply: %+v", response) - } - req.EncryptPath = "not-existing-dir/xyz" - response = test_helpers.QueryCtlSock(t, sock, req) - if response.ErrNo != int32(syscall.ENOENT) || response.Result != "" { - t.Errorf("incorrect error handling: %+v", response) - } - // Strange paths should not cause a crash - crashers := []string{"/foo", "foo/", "/foo/", ".", "/////", "/../../."} - for _, c := range crashers { - req.EncryptPath = c - // QueryCtlSock calls t.Fatal if it gets EOF when gocryptfs panics - response = test_helpers.QueryCtlSock(t, sock, req) - if response.WarnText == "" { - t.Errorf("We should get a warning about non-canonical paths here") - } - } -} - // In gocryptfs before v1.2, the file header was only read once for each // open. But truncating a file to zero will generate a new random file ID. // The sequence below caused an I/O error to be returned. diff --git a/tests/test_helpers/helpers.go b/tests/test_helpers/helpers.go index 299067a..f681482 100644 --- a/tests/test_helpers/helpers.go +++ b/tests/test_helpers/helpers.go @@ -1,6 +1,7 @@ package test_helpers import ( + "bytes" "crypto/md5" "encoding/hex" "encoding/json" @@ -27,6 +28,9 @@ const GocryptfsBinary = "../../gocryptfs" // UnmountScript is the fusermount/umount compatibility wrapper script const UnmountScript = "../fuse-unmount.bash" +// X255 contains 255 uppercase "X". This can be used as a maximum-length filename. +var X255 string + // TmpDir is a unique temporary directory. "go test" runs package tests in parallel. We create a // unique TmpDir in init() so the tests do not interfere. var TmpDir string @@ -38,6 +42,8 @@ var DefaultPlainDir string var DefaultCipherDir string func init() { + X255 = string(bytes.Repeat([]byte("X"), 255)) + os.MkdirAll(testParentDir, 0700) var err error TmpDir, err = ioutil.TempDir(testParentDir, "")