ctlsock: add initial limited implementation

At the moment, in forward mode you can only encrypt paths
and in reverse mode you can only decrypt paths.
This commit is contained in:
Jakob Unterwurzacher 2016-11-10 00:27:08 +01:00
parent df28fc5a11
commit 75ebb28a62
8 changed files with 228 additions and 7 deletions

View File

@ -18,7 +18,7 @@ type argContainer struct {
plaintextnames, quiet, nosyslog, wpanic, plaintextnames, quiet, nosyslog, wpanic,
longnames, allow_other, ro, reverse, aessiv, nonempty, raw64 bool longnames, allow_other, ro, reverse, aessiv, nonempty, raw64 bool
masterkey, mountpoint, cipherdir, cpuprofile, extpass, masterkey, mountpoint, cipherdir, cpuprofile, extpass,
memprofile, ko, passfile string memprofile, ko, passfile, ctlsock string
// Configuration file name override // Configuration file name override
config string config string
notifypid, scryptn int notifypid, scryptn int
@ -112,6 +112,7 @@ func parseCliOpts() (args argContainer) {
flagSet.StringVar(&args.extpass, "extpass", "", "Use external program for the password prompt") flagSet.StringVar(&args.extpass, "extpass", "", "Use external program for the password prompt")
flagSet.StringVar(&args.passfile, "passfile", "", "Read password from file") flagSet.StringVar(&args.passfile, "passfile", "", "Read password from file")
flagSet.StringVar(&args.ko, "ko", "", "Pass additional options directly to the kernel, comma-separated list") flagSet.StringVar(&args.ko, "ko", "", "Pass additional options directly to the kernel, comma-separated list")
flagSet.StringVar(&args.ctlsock, "ctlsock", "", "Create control socket at specified path")
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+ flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
"successful mount - used internally for daemonization") "successful mount - used internally for daemonization")
flagSet.IntVar(&args.scryptn, "scryptn", configfile.ScryptDefaultLogN, "scrypt cost parameter logN. "+ flagSet.IntVar(&args.scryptn, "scryptn", configfile.ScryptDefaultLogN, "scrypt cost parameter logN. "+

View File

@ -0,0 +1,137 @@
// Package ctlsock implementes the control socket interface that can be
// activated by passing "-ctlsock" on the command line.
package ctlsock
import (
"encoding/json"
"errors"
"io"
"net"
"os"
"syscall"
"github.com/rfjakob/gocryptfs/internal/tlog"
)
// Interface should be implemented by fusefrontend[_reverse]
type Interface interface {
EncryptPath(string) (string, error)
DecryptPath(string) (string, error)
}
// RequestStruct is sent by a client
type RequestStruct struct {
EncryptPath string
DecryptPath string
}
// ResponseStruct is sent by us as response to a request
type ResponseStruct struct {
// Result is the resulting decrypted or encrypted path. Empty on error.
Result string
// ErrNo is the error number as defined in errno.h.
// 0 means success and -1 means that the error number is not known
// (look at ErrText in this case).
ErrNo int32
// ErrText is a detailed error message.
ErrText string
}
type ctlSockHandler struct {
fs Interface
socket *net.UnixListener
}
// CreateAndServe creates an unix socket at "path" and serves incoming
// connections in a new goroutine.
func CreateAndServe(path string, fs Interface) error {
sock, err := net.Listen("unix", path)
if err != nil {
return err
}
handler := ctlSockHandler{
fs: fs,
socket: sock.(*net.UnixListener),
}
go handler.acceptLoop()
return nil
}
func (ch *ctlSockHandler) acceptLoop() {
for {
conn, err := ch.socket.Accept()
if err != nil {
tlog.Warn.Printf("ctlsock: Accept error: %v", err)
break
}
go ch.handleConnection(conn.(*net.UnixConn))
}
}
func (ch *ctlSockHandler) handleConnection(conn *net.UnixConn) {
// 2*PATH_MAX is definitely big enough for requests to decrypt or
// encrypt paths.
buf := make([]byte, 2*syscall.PathMax)
for {
n, err := conn.Read(buf)
if err == io.EOF {
conn.Close()
return
} else if err != nil {
tlog.Warn.Printf("ctlsock: Read error: %#v", err)
conn.Close()
return
}
buf = buf[:n]
var in RequestStruct
err = json.Unmarshal(buf, &in)
if err != nil {
tlog.Warn.Printf("ctlsock: Unmarshal error: %#v", err)
errorMsg := ResponseStruct{
ErrNo: int32(syscall.EINVAL),
ErrText: err.Error(),
}
sendResponse(&errorMsg, conn)
}
ch.handleRequest(&in, conn)
// Restore original size.
buf = buf[:cap(buf)]
}
}
func (ch *ctlSockHandler) handleRequest(in *RequestStruct, conn *net.UnixConn) {
var err error
var out ResponseStruct
if in.DecryptPath != "" && in.EncryptPath != "" {
err = errors.New("Ambigous")
} else if in.DecryptPath == "" && in.EncryptPath == "" {
err = errors.New("No operation")
} else if in.DecryptPath != "" {
out.Result, err = ch.fs.DecryptPath(in.DecryptPath)
} else if in.EncryptPath != "" {
out.Result, err = ch.fs.EncryptPath(in.EncryptPath)
}
if err != nil {
out.ErrText = err.Error()
out.ErrNo = -1
// Try to extract the actual error number
if pe, ok := err.(*os.PathError); ok {
if se, ok := pe.Err.(syscall.Errno); ok {
out.ErrNo = int32(se)
}
}
}
sendResponse(&out, conn)
}
func sendResponse(msg *ResponseStruct, conn *net.UnixConn) {
jsonMsg, err := json.Marshal(msg)
if err != nil {
tlog.Warn.Printf("ctlsock: Marshal failed: %v", err)
return
}
_, err = conn.Write(jsonMsg)
if err != nil {
tlog.Warn.Printf("ctlsock: Write failed: %v", err)
}
}

View File

@ -0,0 +1,19 @@
package fusefrontend
import (
"errors"
"github.com/rfjakob/gocryptfs/internal/ctlsock"
)
var _ ctlsock.Interface = &FS{} // Verify that interface is implemented.
// EncryptPath implements ctlsock.Backend
func (fs *FS) EncryptPath(plainPath string) (string, error) {
return fs.encryptPath(plainPath)
}
// DecryptPath implements ctlsock.Backend
func (fs *FS) DecryptPath(plainPath string) (string, error) {
return "", errors.New("Not implemented")
}

View File

@ -21,7 +21,7 @@ var wlock wlockMap
// 2) lock ... unlock ... // 2) lock ... unlock ...
// 3) unregister // 3) unregister
type wlockMap struct { type wlockMap struct {
// Counts lock() calls. As every operation that modifies a file should // opCount counts lock() calls. As every operation that modifies a file should
// call it, this effectively serves as a write-operation counter. // call it, this effectively serves as a write-operation counter.
// The variable is accessed without holding any locks so atomic operations // The variable is accessed without holding any locks so atomic operations
// must be used. It must be the first element of the struct to guarantee // must be used. It must be the first element of the struct to guarantee

View File

@ -0,0 +1,19 @@
package fusefrontend_reverse
import (
"errors"
"github.com/rfjakob/gocryptfs/internal/ctlsock"
)
var _ ctlsock.Interface = &reverseFS{} // Verify that interface is implemented.
// EncryptPath implements ctlsock.Backend
func (rfs *reverseFS) EncryptPath(plainPath string) (string, error) {
return "", errors.New("Not implemented")
}
// DecryptPath implements ctlsock.Backend
func (rfs *reverseFS) DecryptPath(plainPath string) (string, error) {
return rfs.decryptPath(plainPath)
}

View File

@ -49,7 +49,7 @@ var _ pathfs.FileSystem = &reverseFS{}
// NewFS returns an encrypted FUSE overlay filesystem. // NewFS returns an encrypted FUSE overlay filesystem.
// In this case (reverse mode) the backing directory is plain-text and // In this case (reverse mode) the backing directory is plain-text and
// reverseFS provides an encrypted view. // reverseFS provides an encrypted view.
func NewFS(args fusefrontend.Args) pathfs.FileSystem { func NewFS(args fusefrontend.Args) *reverseFS {
if args.CryptoBackend != cryptocore.BackendAESSIV { if args.CryptoBackend != cryptocore.BackendAESSIV {
panic("reverse mode must use AES-SIV, everything else is insecure") panic("reverse mode must use AES-SIV, everything else is insecure")
} }

View File

@ -18,6 +18,7 @@ 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/ctlsock"
"github.com/rfjakob/gocryptfs/internal/fusefrontend" "github.com/rfjakob/gocryptfs/internal/fusefrontend"
"github.com/rfjakob/gocryptfs/internal/fusefrontend_reverse" "github.com/rfjakob/gocryptfs/internal/fusefrontend_reverse"
"github.com/rfjakob/gocryptfs/internal/tlog" "github.com/rfjakob/gocryptfs/internal/tlog"
@ -165,10 +166,18 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF
jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t") jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t")
tlog.Debug.Printf("frontendArgs: %s", string(jsonBytes)) tlog.Debug.Printf("frontendArgs: %s", string(jsonBytes))
var finalFs pathfs.FileSystem var finalFs pathfs.FileSystem
var ctlSockBackend ctlsock.Interface
if args.reverse { if args.reverse {
finalFs = fusefrontend_reverse.NewFS(frontendArgs) fs := fusefrontend_reverse.NewFS(frontendArgs)
finalFs = fs
ctlSockBackend = fs
} else { } else {
finalFs = fusefrontend.NewFS(frontendArgs) fs := fusefrontend.NewFS(frontendArgs)
finalFs = fs
ctlSockBackend = fs
}
if args.ctlsock != "" {
ctlsock.CreateAndServe(args.ctlsock, ctlSockBackend)
} }
pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true} pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true}
pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts) pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts)
@ -200,9 +209,8 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF
if args.reverse { if args.reverse {
mOpts.Name += "-reverse" mOpts.Name += "-reverse"
} }
// The kernel enforces read-only operation, we just have to pass "ro". // The kernel enforces read-only operation, we just have to pass "ro".
// Reverse mounts are always read-only // Reverse mounts are always read-only.
if args.ro || args.reverse { if args.ro || args.reverse {
mOpts.Options = append(mOpts.Options, "ro") mOpts.Options = append(mOpts.Options, "ro")
} }

View File

@ -2,10 +2,16 @@
package defaults package defaults
import ( import (
"encoding/json"
"fmt"
"net"
"os" "os"
"os/exec" "os/exec"
"syscall"
"testing" "testing"
"time"
"github.com/rfjakob/gocryptfs/internal/ctlsock"
"github.com/rfjakob/gocryptfs/tests/test_helpers" "github.com/rfjakob/gocryptfs/tests/test_helpers"
) )
@ -35,3 +41,34 @@ func Test1980Tar(t *testing.T) {
t.Errorf("Wrong mtime: %d", m) t.Errorf("Wrong mtime: %d", m)
} }
} }
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)
conn, err := net.DialTimeout("unix", sock, 1*time.Second)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(time.Second))
msg := []byte(`{"EncryptPath": "foobar"}`)
_, err = conn.Write(msg)
if err != nil {
t.Fatal(err)
}
buf := make([]byte, 2*syscall.PathMax)
n, err := conn.Read(buf)
if err != nil {
t.Fatal(err)
}
buf = buf[:n]
var response ctlsock.ResponseStruct
json.Unmarshal(buf, &response)
if response.Result == "" || response.ErrNo != 0 {
fmt.Printf("%s\n", string(buf))
t.Errorf("got an error reply")
}
}