diff options
Diffstat (limited to 'internal/nametransform')
| -rw-r--r-- | internal/nametransform/longnames.go | 78 | ||||
| -rw-r--r-- | internal/nametransform/longnames_test.go | 6 | ||||
| -rw-r--r-- | internal/nametransform/names_diriv.go | 58 | 
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 == "" { | 
