diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | cryptfs/file.go | 12 | ||||
-rw-r--r-- | cryptfs/fs.go | 26 | ||||
-rw-r--r-- | frontend/dir.go | 10 | ||||
-rw-r--r-- | frontend/file.go | 4 | ||||
-rw-r--r-- | frontend/fs.go | 29 | ||||
-rw-r--r-- | frontend/node.go | 108 | ||||
-rw-r--r-- | main.go | 394 |
8 files changed, 64 insertions, 521 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d65432 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Binary +/gocryptfs diff --git a/cryptfs/file.go b/cryptfs/file.go index 77262d4..5645f3c 100644 --- a/cryptfs/file.go +++ b/cryptfs/file.go @@ -8,7 +8,7 @@ import ( "crypto/cipher" ) -type File struct { +type CryptFile struct { file *os.File gcm cipher.AEAD plainBS int64 @@ -17,7 +17,7 @@ type File struct { // readCipherBlock - Read ciphertext block number "blockNo", decrypt, // return plaintext -func (be *File) readCipherBlock(blockNo int64) ([]byte, error) { +func (be *CryptFile) readCipherBlock(blockNo int64) ([]byte, error) { off := blockNo * int64(be.cipherBS) buf := make([]byte, be.cipherBS) @@ -64,7 +64,7 @@ type intraBlock struct { } // Split a plaintext byte range into (possible partial) blocks -func (be *File) splitRange(offset int64, length int64) []intraBlock { +func (be *CryptFile) splitRange(offset int64, length int64) []intraBlock { var b intraBlock var parts []intraBlock @@ -79,7 +79,7 @@ func (be *File) splitRange(offset int64, length int64) []intraBlock { return parts } -func (be *File) min64(x int64, y int64) int64 { +func (be *CryptFile) min64(x int64, y int64) int64 { if x < y { return x } @@ -87,7 +87,7 @@ func (be *File) min64(x int64, y int64) int64 { } // writeCipherBlock - Encrypt plaintext and write it to file block "blockNo" -func (be *File) writeCipherBlock(blockNo int64, plain []byte) error { +func (be *CryptFile) writeCipherBlock(blockNo int64, plain []byte) error { if int64(len(plain)) > be.plainBS { panic("writeCipherBlock: Cannot write block that is larger than plainBS") @@ -109,7 +109,7 @@ func (be *File) writeCipherBlock(blockNo int64, plain []byte) error { // Perform RMW cycle on block // Write "data" into file location specified in "b" -func (be *File) rmwWrite(b intraBlock, data []byte, f *os.File) error { +func (be *CryptFile) rmwWrite(b intraBlock, data []byte, f *os.File) error { if b.length != int64(len(data)) { panic("Length mismatch") } diff --git a/cryptfs/fs.go b/cryptfs/fs.go index 9050b30..f5781e2 100644 --- a/cryptfs/fs.go +++ b/cryptfs/fs.go @@ -19,14 +19,14 @@ const ( DECRYPT = false ) -type FS struct { +type CryptFS struct { blockCipher cipher.Block gcm cipher.AEAD plainBS int64 cipherBS int64 } -func NewFS(key [16]byte) *FS { +func NewCryptFS(key [16]byte) *CryptFS { b, err := aes.NewCipher(key[:]) if err != nil { @@ -38,7 +38,7 @@ func NewFS(key [16]byte) *FS { panic(err) } - return &FS{ + return &CryptFS{ blockCipher: b, gcm: g, plainBS: DEFAULT_PLAINBS, @@ -46,8 +46,8 @@ func NewFS(key [16]byte) *FS { } } -func (fs *FS) NewFile(f *os.File) *File { - return &File { +func (fs *CryptFS) NewFile(f *os.File) *CryptFile { + return &CryptFile { file: f, gcm: fs.gcm, plainBS: fs.plainBS, @@ -56,7 +56,7 @@ func (fs *FS) NewFile(f *os.File) *File { } // DecryptName - decrypt filename -func (be *FS) decryptName(cipherName string) (string, error) { +func (be *CryptFS) decryptName(cipherName string) (string, error) { bin, err := base64.URLEncoding.DecodeString(cipherName) if err != nil { @@ -81,7 +81,7 @@ func (be *FS) decryptName(cipherName string) (string, error) { } // EncryptName - encrypt filename -func (be *FS) encryptName(plainName string) string { +func (be *CryptFS) encryptName(plainName string) string { bin := []byte(plainName) bin = be.pad16(bin) @@ -97,7 +97,7 @@ func (be *FS) encryptName(plainName string) string { // TranslatePath - encrypt or decrypt path. Just splits the string on "/" // and hands the parts to EncryptName() / DecryptName() -func (be *FS) translatePath(path string, op bool) (string, error) { +func (be *CryptFS) translatePath(path string, op bool) (string, error) { var err error // Empty string means root directory @@ -125,18 +125,18 @@ func (be *FS) translatePath(path string, op bool) (string, error) { } // EncryptPath - encrypt filename or path. Just hands it to TranslatePath(). -func (be *FS) EncryptPath(path string) string { +func (be *CryptFS) EncryptPath(path string) string { newPath, _ := be.translatePath(path, ENCRYPT) return newPath } // DecryptPath - decrypt filename or path. Just hands it to TranslatePath(). -func (be *FS) DecryptPath(path string) (string, error) { +func (be *CryptFS) DecryptPath(path string) (string, error) { return be.translatePath(path, DECRYPT) } // plainSize - calculate plaintext size from ciphertext size -func (be *FS) PlainSize(s int64) int64 { +func (be *CryptFS) PlainSize(s int64) int64 { // Zero sized files stay zero-sized if s > 0 { // Number of blocks @@ -149,7 +149,7 @@ func (be *FS) PlainSize(s int64) int64 { // pad16 - pad filename to 16 byte blocks using standard PKCS#7 padding // https://tools.ietf.org/html/rfc5652#section-6.3 -func (be *FS) pad16(orig []byte) (padded []byte) { +func (be *CryptFS) pad16(orig []byte) (padded []byte) { oldLen := len(orig) if oldLen == 0 { panic("Padding zero-length string makes no sense") @@ -169,7 +169,7 @@ func (be *FS) pad16(orig []byte) (padded []byte) { } // unPad16 - remove padding -func (be *FS) unPad16(orig []byte) ([]byte, error) { +func (be *CryptFS) unPad16(orig []byte) ([]byte, error) { oldLen := len(orig) if oldLen % aes.BlockSize != 0 { return nil, errors.New("Unaligned size") diff --git a/frontend/dir.go b/frontend/dir.go new file mode 100644 index 0000000..4703df9 --- /dev/null +++ b/frontend/dir.go @@ -0,0 +1,10 @@ +package frontend + +import ( + //"github.com/rfjakob/gocryptfs/cryptfs" + "github.com/rfjakob/cluefs/lib/cluefs" +) + +type Dir struct { + *cluefs.Dir +} diff --git a/frontend/file.go b/frontend/file.go index 7b292bd..81590e8 100644 --- a/frontend/file.go +++ b/frontend/file.go @@ -2,8 +2,10 @@ package frontend import ( "github.com/rfjakob/gocryptfs/cryptfs" + "github.com/rfjakob/cluefs/lib/cluefs" ) type File struct { - cryptfs.File + *cryptfs.CryptFile + *cluefs.File } diff --git a/frontend/fs.go b/frontend/fs.go index 637d134..ba6ad09 100644 --- a/frontend/fs.go +++ b/frontend/fs.go @@ -2,25 +2,26 @@ package frontend import ( "github.com/rfjakob/gocryptfs/cryptfs" - "bazil.org/fuse/fs" + "github.com/rfjakob/cluefs/lib/cluefs" ) type FS struct { - *cryptfs.FS - backing string + *cryptfs.CryptFS + *cluefs.ClueFS } -func New(key [16]byte, b string) *FS { - return &FS { - FS: cryptfs.NewFS(key), - backing: b, - } -} +type nullTracer struct {} -func (fs *FS) Root() (fs.Node, error) { - n := Node{ - backing: "", - parentFS: fs, +func (nullTracer) Trace(op cluefs.FsOperTracer) {} + +func NewFS(key [16]byte, backing string) *FS { + var nt nullTracer + clfs, err := cluefs.NewClueFS(backing, nt) + if err != nil { + panic(err) + } + return &FS { + CryptFS: cryptfs.NewCryptFS(key), + ClueFS: clfs, } - return n, nil } diff --git a/frontend/node.go b/frontend/node.go index 833be2e..7218d54 100644 --- a/frontend/node.go +++ b/frontend/node.go @@ -1,113 +1,9 @@ package frontend import ( - "fmt" - "os" - "time" - "syscall" - "io/ioutil" - "path" - - "golang.org/x/net/context" - - //"github.com/rfjakob/gocryptfs/cryptfs" - "bazil.org/fuse" - "bazil.org/fuse/fs" + "github.com/rfjakob/cluefs/lib/cluefs" ) - type Node struct { - fs.NodeRef - backing string - parentFS *FS -} - -// FileModeFromStat - create os.FileMode from stat value -// For some reason, they use different constants. -// Adapted from https://golang.org/src/os/stat_linux.go -func FileModeFromStat(st *syscall.Stat_t) os.FileMode { - fileMode := os.FileMode(st.Mode & 0777) - switch st.Mode & syscall.S_IFMT { - case syscall.S_IFBLK: - fileMode |= os.ModeDevice - case syscall.S_IFCHR: - fileMode |= os.ModeDevice | os.ModeCharDevice - case syscall.S_IFDIR: - fileMode |= os.ModeDir - case syscall.S_IFIFO: - fileMode |= os.ModeNamedPipe - case syscall.S_IFLNK: - fileMode |= os.ModeSymlink - case syscall.S_IFREG: - // nothing to do - case syscall.S_IFSOCK: - fileMode |= os.ModeSocket - } - if st.Mode & syscall.S_ISGID != 0 { - fileMode |= os.ModeSetgid - } - if st.Mode & syscall.S_ISUID != 0 { - fileMode |= os.ModeSetuid - } - if st.Mode & syscall.S_ISVTX != 0 { - fileMode |= os.ModeSticky - } - return fileMode -} - - -func StatToAttr(s *syscall.Stat_t, a *fuse.Attr) { - a.Inode = s.Ino - a.Size = uint64(s.Size) - a.Blocks = uint64(s.Blocks) - a.Atime = time.Unix(s.Atim.Sec, s.Atim.Nsec) - a.Mtime = time.Unix(s.Mtim.Sec, s.Mtim.Nsec) - a.Ctime = time.Unix(s.Ctim.Sec, s.Ctim.Nsec) - a.Mode = FileModeFromStat(s) - a.Nlink = uint32(s.Nlink) - a.Uid = uint32(s.Uid) - a.Gid = uint32(s.Gid) - a.Rdev = uint32(s.Rdev) -} - -func (n Node) Attr(ctx context.Context, attr *fuse.Attr) error { - var err error - var st syscall.Stat_t - if n.backing == "" { - // When GetAttr is called for the toplevel directory, we always want - // to look through symlinks. - fmt.Printf("Attr %s\n", n.parentFS.backing) - //err = syscall.Stat(n.parentFS.backing, &st) - err = syscall.Stat("/", &st) - } else { - fmt.Printf("Attr %s\n", path.Join(n.parentFS.backing, n.backing)) - p := path.Join(n.parentFS.backing, n.backing) - err = syscall.Lstat(p, &st) - } - if err != nil { - return err - } - StatToAttr(&st, attr) - return nil -} - -func (n *Node) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { - entries, err := ioutil.ReadDir(n.backing) - if err != nil { - return nil, err - } - var fuseEntries []fuse.Dirent - for _, e := range entries { - var d fuse.Dirent - d.Name = e.Name() - fuseEntries = append(fuseEntries, d) - } - return fuseEntries, err -} - -func (n *Node) Lookup(ctx context.Context, name string) (fs.Node, error) { - if name == "hello" { - return Node{}, nil - } - return nil, fuse.ENOENT + *cluefs.Node } @@ -1,396 +1,28 @@ -// Memfs implements an in-memory file system. package main import ( - "flag" - "fmt" - "log" - "os" - "sync" - "sync/atomic" - "time" - - "bazil.org/fuse" - "bazil.org/fuse/fs" - "golang.org/x/net/context" - + "github.com/rfjakob/cluefs/lib/cluefs" "github.com/rfjakob/gocryptfs/frontend" + "os" ) -// debug flag enables logging of debug messages to stderr. -var debug = flag.Bool("debug", true, "enable debug log messages to stderr") - -func usage() { - fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) - fmt.Fprintf(os.Stderr, " %s BACKING MOUNTPOINT\n", os.Args[0]) - flag.PrintDefaults() -} - -func debugLog(msg interface{}) { - fmt.Fprintf(os.Stderr, "%v\n", msg) -} - func main() { - flag.Usage = usage - flag.Parse() - - if flag.NArg() != 2 { - usage() - os.Exit(2) - } - - backing := flag.Arg(0) - mountpoint := flag.Arg(1) - c, err := fuse.Mount( - mountpoint, - fuse.FSName("gocryptfs"), - fuse.Subtype("gocryptfs"), - fuse.LocalVolume(), - fuse.VolumeName("gocryptfs"), - ) + // Parse command line arguments + conf, err := cluefs.ParseArguments() if err != nil { - log.Fatal(err) - } - defer c.Close() - - cfg := &fs.Config{} - if *debug { - cfg.Debug = debugLog + os.Exit(1) } - srv := fs.New(c, cfg) + // Create the file system object var key [16]byte - filesys := frontend.New(key, backing) - - if err := srv.Serve(filesys); err != nil { - log.Fatal(err) - } - - // Check if the mount process has an error to report. - <-c.Ready - if err := c.MountError; err != nil { - log.Fatal(err) - } -} - -type MemFS struct { - root *Dir - nodeId uint64 - - nodeCount uint64 - size int64 -} - -// Compile-time interface checks. -var _ fs.FS = (*MemFS)(nil) -var _ fs.FSStatfser = (*MemFS)(nil) - -var _ fs.Node = (*Dir)(nil) -var _ fs.NodeCreater = (*Dir)(nil) -var _ fs.NodeMkdirer = (*Dir)(nil) -var _ fs.NodeRemover = (*Dir)(nil) -var _ fs.NodeRenamer = (*Dir)(nil) -var _ fs.NodeStringLookuper = (*Dir)(nil) - -var _ fs.HandleReadAller = (*File)(nil) -var _ fs.HandleWriter = (*File)(nil) -var _ fs.Node = (*File)(nil) -var _ fs.NodeOpener = (*File)(nil) -var _ fs.NodeSetattrer = (*File)(nil) - -func NewMemFS() *MemFS { - fs := &MemFS{ - nodeCount: 1, - } - fs.root = fs.newDir(os.ModeDir | 0777) - if fs.root.attr.Inode != 1 { - panic("Root node should have been assigned id 1") - } - return fs -} - -func (m *MemFS) nextId() uint64 { - return atomic.AddUint64(&m.nodeId, 1) -} - -func (m *MemFS) newDir(mode os.FileMode) *Dir { - n := time.Now() - return &Dir{ - attr: fuse.Attr{ - Inode: m.nextId(), - Atime: n, - Mtime: n, - Ctime: n, - Crtime: n, - Mode: os.ModeDir | mode, - }, - fs: m, - nodes: make(map[string]fs.Node), - } -} - -func (m *MemFS) newFile(mode os.FileMode) *File { - n := time.Now() - return &File{ - attr: fuse.Attr{ - Inode: m.nextId(), - Atime: n, - Mtime: n, - Ctime: n, - Crtime: n, - Mode: mode, - }, - data: make([]byte, 0), - } -} - -type Dir struct { - sync.RWMutex - attr fuse.Attr - - fs *MemFS - parent *Dir - nodes map[string]fs.Node -} - -type File struct { - sync.RWMutex - attr fuse.Attr - - fs *MemFS - data []byte -} - -func (f *MemFS) Root() (fs.Node, error) { - return f.root, nil -} - -func (f *MemFS) Statfs(ctx context.Context, req *fuse.StatfsRequest, - resp *fuse.StatfsResponse) error { - resp.Blocks = uint64((atomic.LoadInt64(&f.size) + 511) / 512) - resp.Bsize = 512 - resp.Files = atomic.LoadUint64(&f.nodeCount) - return nil -} - -func (f *File) Attr(ctx context.Context, o *fuse.Attr) error { - f.RLock() - *o = f.attr - f.RUnlock() - return nil -} - -func (d *Dir) Attr(ctx context.Context, o *fuse.Attr) error { - d.RLock() - *o = d.attr - d.RUnlock() - return nil -} - -func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { - d.RLock() - n, exist := d.nodes[name] - d.RUnlock() - - if !exist { - return nil, fuse.ENOENT - } - return n, nil -} - -func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { - d.RLock() - dirs := make([]fuse.Dirent, len(d.nodes)+2) + cfs := frontend.NewFS(key, conf.GetShadowDir()) - // Add special references. - dirs[0] = fuse.Dirent{ - Name: ".", - Inode: d.attr.Inode, - Type: fuse.DT_Dir, + // Mount and serve file system requests + if err = cfs.MountAndServe(conf.GetMountPoint(), conf.GetReadOnly()); err != nil { + cluefs.ErrlogMain.Printf("could not mount file system [%s]", err) + os.Exit(3) } - dirs[1] = fuse.Dirent{ - Name: "..", - Type: fuse.DT_Dir, - } - if d.parent != nil { - dirs[1].Inode = d.parent.attr.Inode - } else { - dirs[1].Inode = d.attr.Inode - } - - // Add remaining files. - idx := 2 - for name, node := range d.nodes { - ent := fuse.Dirent{ - Name: name, - } - switch n := node.(type) { - case *File: - ent.Inode = n.attr.Inode - ent.Type = fuse.DT_File - case *Dir: - ent.Inode = n.attr.Inode - ent.Type = fuse.DT_Dir - } - dirs[idx] = ent - idx++ - } - d.RUnlock() - return dirs, nil -} - -func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) { - d.Lock() - defer d.Unlock() - - if _, exists := d.nodes[req.Name]; exists { - return nil, fuse.EEXIST - } - - n := d.fs.newDir(req.Mode) - d.nodes[req.Name] = n - atomic.AddUint64(&d.fs.nodeCount, 1) - - return n, nil -} - -func (d *Dir) Create(ctx context.Context, req *fuse.CreateRequest, - resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { - d.Lock() - defer d.Unlock() - - if _, exists := d.nodes[req.Name]; exists { - return nil, nil, fuse.EEXIST - } - - n := d.fs.newFile(req.Mode) - n.fs = d.fs - d.nodes[req.Name] = n - atomic.AddUint64(&d.fs.nodeCount, 1) - - resp.Attr = n.attr - - return n, n, nil -} - -func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error { - nd := newDir.(*Dir) - if d.attr.Inode == nd.attr.Inode { - d.Lock() - defer d.Unlock() - } else if d.attr.Inode < nd.attr.Inode { - d.Lock() - defer d.Unlock() - nd.Lock() - defer nd.Unlock() - } else { - nd.Lock() - defer nd.Unlock() - d.Lock() - defer d.Unlock() - } - - if _, exists := d.nodes[req.OldName]; !exists { - return fuse.ENOENT - } - - // Rename can be used as an atomic replace, override an existing file. - if old, exists := nd.nodes[req.NewName]; exists { - atomic.AddUint64(&d.fs.nodeCount, ^uint64(0)) // decrement by one - if oldFile, ok := old.(*File); !ok { - atomic.AddInt64(&d.fs.size, -int64(oldFile.attr.Size)) - } - } - - nd.nodes[req.NewName] = d.nodes[req.OldName] - delete(d.nodes, req.OldName) - return nil -} - -func (d *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error { - d.Lock() - defer d.Unlock() - - if n, exists := d.nodes[req.Name]; !exists { - return fuse.ENOENT - } else if req.Dir && len(n.(*Dir).nodes) > 0 { - return fuse.ENOTEMPTY - } - - delete(d.nodes, req.Name) - atomic.AddUint64(&d.fs.nodeCount, ^uint64(0)) // decrement by one - return nil -} - -func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, - error) { - return f, nil -} - -func (f *File) ReadAll(ctx context.Context) ([]byte, error) { - f.RLock() - out := make([]byte, len(f.data)) - copy(out, f.data) - f.RUnlock() - - return out, nil -} - -func (f *File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { - f.Lock() - - l := len(req.Data) - end := int(req.Offset) + l - if end > len(f.data) { - delta := end - len(f.data) - f.data = append(f.data, make([]byte, delta)...) - f.attr.Size = uint64(len(f.data)) - atomic.AddInt64(&f.fs.size, int64(delta)) - } - copy(f.data[req.Offset:end], req.Data) - resp.Size = l - - f.Unlock() - return nil -} - -func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, - resp *fuse.SetattrResponse) error { - f.Lock() - - if req.Valid.Size() { - delta := int(req.Size) - len(f.data) - if delta > 0 { - f.data = append(f.data, make([]byte, delta)...) - } else { - f.data = f.data[0:req.Size] - } - f.attr.Size = req.Size - atomic.AddInt64(&f.fs.size, int64(delta)) - } - - if req.Valid.Mode() { - f.attr.Mode = req.Mode - } - - if req.Valid.Atime() { - f.attr.Atime = req.Atime - } - - if req.Valid.AtimeNow() { - f.attr.Atime = time.Now() - } - - if req.Valid.Mtime() { - f.attr.Mtime = req.Mtime - } - - if req.Valid.MtimeNow() { - f.attr.Mtime = time.Now() - } - - resp.Attr = f.attr - f.Unlock() - return nil + // We are done + os.Exit(0) } |