aboutsummaryrefslogtreecommitdiff
path: root/internal/nametransform
diff options
context:
space:
mode:
Diffstat (limited to 'internal/nametransform')
-rw-r--r--internal/nametransform/longnames.go78
-rw-r--r--internal/nametransform/longnames_test.go6
-rw-r--r--internal/nametransform/names_diriv.go58
3 files changed, 83 insertions, 59 deletions
diff --git a/internal/nametransform/longnames.go b/internal/nametransform/longnames.go
index d048f95..0746cd6 100644
--- a/internal/nametransform/longnames.go
+++ b/internal/nametransform/longnames.go
@@ -4,6 +4,7 @@ import (
"crypto/sha256"
"encoding/base64"
"io/ioutil"
+ "os"
"path/filepath"
"strings"
"syscall"
@@ -11,11 +12,13 @@ import (
"github.com/rfjakob/gocryptfs/internal/toggledlog"
)
-// Files with long names are stored in two files:
-// gocryptfs.longname.[sha256] <--- File content
-// gocryptfs.longname.[sha256].name <--- File name
-const longNamePrefix = "gocryptfs.longname."
-const longNameSuffix = ".name"
+const (
+ // Files with long names are stored in two files:
+ // gocryptfs.longname.[sha256] <--- File content, prefix = gocryptfs.longname.
+ // gocryptfs.longname.[sha256].name <--- File name, suffix = .name
+ LongNameSuffix = ".name"
+ longNamePrefix = "gocryptfs.longname."
+)
// HashLongName - take the hash of a long string "name" and return
// "gocryptfs.longname.[sha256]"
@@ -32,63 +35,68 @@ const (
LongNameNone = iota
)
-// IsLongName - detect if cName is
+// NameType - detect if cName is
// gocryptfs.longname.[sha256] ........ LongNameContent (content of a long name file)
// gocryptfs.longname.[sha256].name .... LongNameFilename (full file name of a long name file)
// else ................................ LongNameNone (normal file)
-func IsLongName(cName string) int {
+func NameType(cName string) int {
if !strings.HasPrefix(cName, longNamePrefix) {
return LongNameNone
}
- if strings.HasSuffix(cName, longNameSuffix) {
+ if strings.HasSuffix(cName, LongNameSuffix) {
return LongNameFilename
}
return LongNameContent
}
+// IsLongContent returns true if "cName" is the content store of a long name file.
+func IsLongContent(cName string) bool {
+ return NameType(cName) == LongNameContent
+}
+
// ReadLongName - read path.name
func ReadLongName(path string) (string, error) {
- content, err := ioutil.ReadFile(path + longNameSuffix)
+ content, err := ioutil.ReadFile(path + LongNameSuffix)
if err != nil {
toggledlog.Warn.Printf("ReadLongName: %v", err)
}
return string(content), err
}
-// DeleteLongName - if cPath ends in "gocryptfs.longname.[sha256]",
-// delete the "gocryptfs.longname.[sha256].name" file
-func DeleteLongName(cPath string) error {
- if IsLongName(filepath.Base(cPath)) == LongNameContent {
- err := syscall.Unlink(cPath + longNameSuffix)
- if err != nil {
- toggledlog.Warn.Printf("DeleteLongName: %v", err)
- }
- return err
+// DeleteLongName deletes "hashName.name".
+func DeleteLongName(dirfd *os.File, hashName string) error {
+ err := syscall.Unlinkat(int(dirfd.Fd()), hashName+LongNameSuffix)
+ if err != nil {
+ toggledlog.Warn.Printf("DeleteLongName: %v", err)
}
- return nil
+ return err
}
-// WriteLongName - if cPath ends in "gocryptfs.longname.[sha256]", write the
-// "gocryptfs.longname.[sha256].name" file
-func (n *NameTransform) WriteLongName(cPath string, plainPath string) (err error) {
- cHashedName := filepath.Base(cPath)
- if IsLongName(cHashedName) != LongNameContent {
- // This is not a hashed file name, nothing to do
- return nil
- }
- // Encrypt (but do not hash) the plaintext name
- cDir := filepath.Dir(cPath)
- dirIV, err := ReadDirIV(cDir)
+// WriteLongName encrypts plainName and writes it into "hashName.name".
+// For the convenience of the caller, plainName may also be a path and will be
+// converted internally.
+func (n *NameTransform) WriteLongName(dirfd *os.File, hashName string, plainName string) (err error) {
+ plainName = filepath.Base(plainName)
+
+ // Encrypt the basename
+ dirIV, err := ReadDirIVAt(dirfd)
if err != nil {
- toggledlog.Warn.Printf("WriteLongName: %v", err)
return err
}
- plainName := filepath.Base(plainPath)
cName := n.EncryptName(plainName, dirIV)
- // Write the encrypted name into gocryptfs.longname.[sha256].name
- err = ioutil.WriteFile(filepath.Join(cDir, cHashedName+longNameSuffix), []byte(cName), 0600)
+
+ // Write the encrypted name into hashName.name
+ fdRaw, err := syscall.Openat(int(dirfd.Fd()), hashName+LongNameSuffix,
+ syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL, 0600)
+ if err != nil {
+ toggledlog.Warn.Printf("WriteLongName: Openat: %v", err)
+ return err
+ }
+ fd := os.NewFile(uintptr(fdRaw), hashName+LongNameSuffix)
+ defer fd.Close()
+ _, err = fd.Write([]byte(cName))
if err != nil {
- toggledlog.Warn.Printf("WriteLongName: %v", err)
+ toggledlog.Warn.Printf("WriteLongName: Write: %v", err)
}
return err
}
diff --git a/internal/nametransform/longnames_test.go b/internal/nametransform/longnames_test.go
index 62073ec..8fa19fe 100644
--- a/internal/nametransform/longnames_test.go
+++ b/internal/nametransform/longnames_test.go
@@ -6,17 +6,17 @@ import (
func TestIsLongName(t *testing.T) {
n := "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=.name"
- if IsLongName(n) != LongNameFilename {
+ if NameType(n) != LongNameFilename {
t.Errorf("False negative")
}
n = "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU="
- if IsLongName(n) != LongNameContent {
+ if NameType(n) != LongNameContent {
t.Errorf("False negative")
}
n = "LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU="
- if IsLongName(n) != LongNameNone {
+ if NameType(n) != LongNameNone {
t.Errorf("False positive")
}
}
diff --git a/internal/nametransform/names_diriv.go b/internal/nametransform/names_diriv.go
index 9336f5d..1beda3f 100644
--- a/internal/nametransform/names_diriv.go
+++ b/internal/nametransform/names_diriv.go
@@ -1,7 +1,7 @@
package nametransform
import (
- "fmt"
+ "errors"
"io/ioutil"
"os"
"path/filepath"
@@ -21,25 +21,38 @@ const (
)
// ReadDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path)
-// This function is exported because it allows for an efficient readdir implementation
-func ReadDirIV(dir string) (iv []byte, readErr error) {
- ivfile := filepath.Join(dir, DirIVFilename)
- toggledlog.Debug.Printf("ReadDirIV: reading %s\n", ivfile)
- iv, readErr = ioutil.ReadFile(ivfile)
- if readErr != nil {
- // The directory may have been concurrently deleted or moved. Failure to
- // read the diriv is not an error in that case.
- _, statErr := os.Stat(dir)
- if os.IsNotExist(statErr) {
- toggledlog.Debug.Printf("ReadDirIV: Dir %s was deleted under our feet", dir)
- } else {
- // This should not happen
- toggledlog.Warn.Printf("ReadDirIV: Dir exists but diriv does not: %v\n", readErr)
- }
- return nil, readErr
+// This function is exported because it allows for an efficient readdir implementation.
+func ReadDirIV(dir string) (iv []byte, err error) {
+ dirfd, err := os.Open(dir)
+ if err != nil {
+ return nil, err
+ }
+ defer dirfd.Close()
+
+ return ReadDirIVAt(dirfd)
+}
+
+// ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd".
+// Using the dirfd makes it immune to concurrent renames of the directory.
+func ReadDirIVAt(dirfd *os.File) (iv []byte, err error) {
+ fdRaw, err := syscall.Openat(int(dirfd.Fd()), DirIVFilename, syscall.O_RDONLY, 0)
+ if err != nil {
+ toggledlog.Warn.Printf("ReadDirIVAt: %v", err)
+ return nil, err
+ }
+ fd := os.NewFile(uintptr(fdRaw), DirIVFilename)
+ defer fd.Close()
+
+ iv = make([]byte, dirIVLen+1)
+ n, err := fd.Read(iv)
+ if err != nil {
+ toggledlog.Warn.Printf("ReadDirIVAt: %v", err)
+ return nil, err
}
+ iv = iv[0:n]
if len(iv) != dirIVLen {
- return nil, fmt.Errorf("ReadDirIV: Invalid length %d\n", len(iv))
+ toggledlog.Warn.Printf("ReadDirIVAt: wanted %d bytes, got %d", dirIVLen, len(iv))
+ return nil, errors.New("invalid iv length")
}
return iv, nil
}
@@ -50,12 +63,15 @@ func ReadDirIV(dir string) (iv []byte, readErr error) {
func WriteDirIV(dir string) error {
iv := cryptocore.RandBytes(dirIVLen)
file := filepath.Join(dir, DirIVFilename)
- // 0444 permissions: the file is not secret but should not be written to
- return ioutil.WriteFile(file, iv, 0444)
+ err := ioutil.WriteFile(file, iv, 0400)
+ if err != nil {
+ toggledlog.Warn.Printf("WriteDirIV: %v", err)
+ }
+ return err
}
// EncryptPathDirIV - encrypt relative plaintext path using EME with DirIV.
-// Components that are longer than 255 bytes are hashed.
+// Components that are longer than 255 bytes are hashed if be.longnames == true.
func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cipherPath string, err error) {
// Empty string means root directory
if plainPath == "" {