From c6dacd6f913b4c6eb7a8917af49190dce32db108 Mon Sep 17 00:00:00 2001
From: Jakob Unterwurzacher
Date: Tue, 8 Dec 2015 16:13:29 +0100
Subject: Add EME filename encryption & enable it by default

---
 cryptfs/config_file.go   |  20 +++---
 cryptfs/config_test.go   |   2 +-
 cryptfs/cryptfs.go       |   2 -
 cryptfs/cryptfs_names.go | 156 -----------------------------------------------
 cryptfs/filter.go        |  15 -----
 cryptfs/log.go           |  13 ++++
 cryptfs/names_core.go    | 134 ++++++++++++++++++++++++++++++++++++++++
 cryptfs/names_diriv.go   |  31 ++++------
 cryptfs/names_noiv.go    |  63 +++++++++++++++++++
 cryptfs/names_test.go    |  11 ++--
 10 files changed, 241 insertions(+), 206 deletions(-)
 delete mode 100644 cryptfs/cryptfs_names.go
 delete mode 100644 cryptfs/filter.go
 create mode 100644 cryptfs/names_core.go
 create mode 100644 cryptfs/names_noiv.go

(limited to 'cryptfs')

diff --git a/cryptfs/config_file.go b/cryptfs/config_file.go
index be37c60..bf1f2a0 100644
--- a/cryptfs/config_file.go
+++ b/cryptfs/config_file.go
@@ -12,10 +12,6 @@ const (
 	// The dot "." is not used in base64url (RFC4648), hence
 	// we can never clash with an encrypted file.
 	ConfDefaultName = "gocryptfs.conf"
-	// Understood Feature Flags
-	// Also teach isFeatureFlagKnown() about any additions
-	FlagPlaintextNames = "PlaintextNames"
-	FlagDirIV          = "DirIV"
 )
 
 type ConfFile struct {
@@ -37,7 +33,7 @@ type ConfFile struct {
 // CreateConfFile - create a new config with a random key encrypted with
 // "password" and write it to "filename".
 // Uses scrypt with cost parameter logN.
-func CreateConfFile(filename string, password string, plaintextNames bool, logN int) error {
+func CreateConfFile(filename string, password string, plaintextNames bool, logN int, EMENames bool) error {
 	var cf ConfFile
 	cf.filename = filename
 
@@ -50,11 +46,13 @@ func CreateConfFile(filename string, password string, plaintextNames bool, logN
 
 	// Set defaults
 	cf.Version = HEADER_CURRENT_VERSION
-	cf.FeatureFlags = []string{FlagDirIV}
 
 	// Set values chosen by the user
 	if plaintextNames {
 		cf.FeatureFlags = append(cf.FeatureFlags, FlagPlaintextNames)
+	} else {
+		cf.FeatureFlags = append(cf.FeatureFlags, FlagDirIV)
+		cf.FeatureFlags = append(cf.FeatureFlags, FlagEMENames)
 	}
 
 	// Write file to disk
@@ -157,10 +155,18 @@ func (cf *ConfFile) WriteFile() error {
 	return nil
 }
 
+const (
+	// Understood Feature Flags.
+	// Also teach isFeatureFlagKnown() about any additions
+	FlagPlaintextNames = "PlaintextNames"
+	FlagDirIV          = "DirIV"
+	FlagEMENames       = "EMENames"
+)
+
 // Verify that we understand a feature flag
 func (cf *ConfFile) isFeatureFlagKnown(flag string) bool {
 	switch flag {
-	case FlagPlaintextNames, FlagDirIV:
+	case FlagPlaintextNames, FlagDirIV, FlagEMENames:
 		return true
 	default:
 		return false
diff --git a/cryptfs/config_test.go b/cryptfs/config_test.go
index 1c5a375..01c0d71 100644
--- a/cryptfs/config_test.go
+++ b/cryptfs/config_test.go
@@ -59,7 +59,7 @@ func TestLoadV2StrangeFeature(t *testing.T) {
 }
 
 func TestCreateConfFile(t *testing.T) {
-	err := CreateConfFile("config_test/tmp.conf", "test", false, 0)
+	err := CreateConfFile("config_test/tmp.conf", "test", false, 10, true)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/cryptfs/cryptfs.go b/cryptfs/cryptfs.go
index df04973..5832e36 100644
--- a/cryptfs/cryptfs.go
+++ b/cryptfs/cryptfs.go
@@ -25,7 +25,6 @@ type CryptFS struct {
 	cipherBS    uint64
 	// Stores an all-zero block of size cipherBS
 	allZeroBlock   []byte
-	plaintextNames bool
 	// DirIV cache for filename encryption
 	DirIVCacheEnc DirIVCache
 }
@@ -59,7 +58,6 @@ func NewCryptFS(key []byte, useOpenssl bool, plaintextNames bool) *CryptFS {
 		plainBS:        DEFAULT_PLAINBS,
 		cipherBS:       uint64(cipherBS),
 		allZeroBlock:   make([]byte, cipherBS),
-		plaintextNames: plaintextNames,
 	}
 }
 
diff --git a/cryptfs/cryptfs_names.go b/cryptfs/cryptfs_names.go
deleted file mode 100644
index 8f8486b..0000000
--- a/cryptfs/cryptfs_names.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package cryptfs
-
-// Filename encryption / decryption function
-
-import (
-	"crypto/aes"
-	"crypto/cipher"
-	"encoding/base64"
-	"errors"
-	"fmt"
-	"strings"
-)
-
-const (
-	OpEncrypt = iota
-	OpDecrypt
-)
-
-// DecryptName - decrypt base64-encoded encrypted filename "cipherName"
-func (be *CryptFS) DecryptName(cipherName string, iv []byte) (string, error) {
-
-	// Make sure relative symlinks still work after encryption
-	// by passing these through unchanged
-	if cipherName == "." || cipherName == ".." {
-		return cipherName, nil
-	}
-
-	bin, err := base64.URLEncoding.DecodeString(cipherName)
-	if err != nil {
-		return "", err
-	}
-
-	if len(bin)%aes.BlockSize != 0 {
-		return "", fmt.Errorf("Decoded length %d is not a multiple of the AES block size", len(bin))
-	}
-
-	cbc := cipher.NewCBCDecrypter(be.blockCipher, iv)
-	cbc.CryptBlocks(bin, bin)
-
-	bin, err = be.unPad16(bin)
-	if err != nil {
-		return "", err
-	}
-
-	plain := string(bin)
-	return plain, err
-}
-
-// EncryptName - encrypt filename
-func (be *CryptFS) encryptName(plainName string, iv []byte) string {
-
-	// Make sure relative symlinks still work after encryption
-	// by passing these trough unchanged
-	if plainName == "." || plainName == ".." {
-		return plainName
-	}
-
-	bin := []byte(plainName)
-	bin = be.pad16(bin)
-
-	cbc := cipher.NewCBCEncrypter(be.blockCipher, iv)
-	cbc.CryptBlocks(bin, bin)
-
-	cipherName64 := base64.URLEncoding.EncodeToString(bin)
-	return cipherName64
-}
-
-// TranslatePathZeroIV - encrypt or decrypt path using CBC with a constant all-zero IV.
-// Just splits the string on "/" and hands the parts to encryptName() / decryptName()
-func (be *CryptFS) TranslatePathZeroIV(path string, op int) (string, error) {
-	var err error
-
-	// Empty string means root directory
-	if path == "" {
-		return path, err
-	}
-
-	zeroIV := make([]byte, DIRIV_LEN)
-
-	// Run operation on each path component
-	var translatedParts []string
-	parts := strings.Split(path, "/")
-	for _, part := range parts {
-		if part == "" {
-			// This happens on "/foo/bar/" on the front and on the end.
-			// Don't panic.
-			translatedParts = append(translatedParts, "")
-			continue
-		}
-		var newPart string
-		if op == OpEncrypt {
-			newPart = be.encryptName(part, zeroIV)
-		} else {
-			newPart, err = be.DecryptName(part, zeroIV)
-			if err != nil {
-				return "", err
-			}
-		}
-		translatedParts = append(translatedParts, newPart)
-	}
-
-	return strings.Join(translatedParts, "/"), err
-}
-
-// pad16 - pad filename to 16 byte blocks using standard PKCS#7 padding
-// https://tools.ietf.org/html/rfc5652#section-6.3
-func (be *CryptFS) pad16(orig []byte) (padded []byte) {
-	oldLen := len(orig)
-	if oldLen == 0 {
-		panic("Padding zero-length string makes no sense")
-	}
-	padLen := aes.BlockSize - oldLen%aes.BlockSize
-	if padLen == 0 {
-		padLen = aes.BlockSize
-	}
-	newLen := oldLen + padLen
-	padded = make([]byte, newLen)
-	copy(padded, orig)
-	padByte := byte(padLen)
-	for i := oldLen; i < newLen; i++ {
-		padded[i] = padByte
-	}
-	return padded
-}
-
-// unPad16 - remove padding
-func (be *CryptFS) unPad16(orig []byte) ([]byte, error) {
-	oldLen := len(orig)
-	if oldLen%aes.BlockSize != 0 {
-		return nil, errors.New("Unaligned size")
-	}
-	// The last byte is always a padding byte
-	padByte := orig[oldLen-1]
-	// The padding byte's value is the padding length
-	padLen := int(padByte)
-	// Padding must be at least 1 byte
-	if padLen <= 0 {
-		return nil, errors.New("Padding cannot be zero-length")
-	}
-	// Larger paddings make no sense
-	if padLen > aes.BlockSize {
-		return nil, errors.New("Padding cannot be larger than 16")
-	}
-	// All padding bytes must be identical
-	for i := oldLen - padLen; i < oldLen; i++ {
-		if orig[i] != padByte {
-			return nil, errors.New(fmt.Sprintf("Padding byte at i=%d is invalid", i))
-		}
-	}
-	newLen := oldLen - padLen
-	// Padding an empty string makes no sense
-	if newLen == 0 {
-		return nil, errors.New("Unpadded length is zero")
-	}
-	return orig[0:newLen], nil
-}
diff --git a/cryptfs/filter.go b/cryptfs/filter.go
deleted file mode 100644
index f80889d..0000000
--- a/cryptfs/filter.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package cryptfs
-
-// IsFiltered - check if "path" should be forbidden
-//
-// Used to prevent name clashes with gocryptfs.conf
-// when file names are not encrypted
-func (be *CryptFS) IsFiltered(path string) bool {
-	// gocryptfs.conf in the root directory is forbidden
-	if be.plaintextNames == true && path == ConfDefaultName {
-		Warn.Printf("The name /%s is reserved when -plaintextnames is used\n",
-			ConfDefaultName)
-		return true
-	}
-	return false
-}
diff --git a/cryptfs/log.go b/cryptfs/log.go
index 64dc80e..a7fe579 100644
--- a/cryptfs/log.go
+++ b/cryptfs/log.go
@@ -3,6 +3,7 @@ package cryptfs
 import (
 	"fmt"
 	"strings"
+	"encoding/json"
 )
 
 type logChannel struct {
@@ -26,6 +27,18 @@ func (l *logChannel) Dump(d []byte) {
 	fmt.Println(strings.Replace(s, "\000", "\\0", -1))
 }
 
+func (l *logChannel) JSONDump(obj interface{}) {
+	if !l.enabled {
+		return
+	}
+	b, err := json.MarshalIndent(obj, "", "\t")
+	if err != nil {
+		fmt.Println(err)
+	} else {
+		fmt.Println(string(b))
+	}
+}
+
 func (l *logChannel) Enable() {
 	l.enabled = true
 }
diff --git a/cryptfs/names_core.go b/cryptfs/names_core.go
new file mode 100644
index 0000000..0f2e5b3
--- /dev/null
+++ b/cryptfs/names_core.go
@@ -0,0 +1,134 @@
+package cryptfs
+
+// Filename encryption / decryption functions
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"encoding/base64"
+	"errors"
+	"fmt"
+
+	"github.com/rfjakob/eme"
+)
+
+// DecryptName - decrypt base64-encoded encrypted filename "cipherName"
+// The used encryption is either CBC or EME, depending on the "EMENames" argument.
+//
+// This function is exported because it allows for a very efficient readdir
+// implementation (read IV once, decrypt all names using this function).
+func (be *CryptFS) DecryptName(cipherName string, iv []byte, EMENames bool) (string, error) {
+	return be.decryptName(cipherName, iv, EMENames)
+}
+
+// decryptName - decrypt base64-encoded encrypted filename "cipherName".
+// The used encryption is either CBC or EME, depending on the "EMENames" argument.
+func (be *CryptFS) decryptName(cipherName string, iv []byte, EMENames bool) (string, error) {
+
+	// Make sure relative symlinks still work after encryption
+	// by passing these through unchanged
+	if cipherName == "." || cipherName == ".." {
+		return cipherName, nil
+	}
+
+	bin, err := base64.URLEncoding.DecodeString(cipherName)
+	if err != nil {
+		return "", err
+	}
+
+	if len(bin)%aes.BlockSize != 0 {
+		return "", fmt.Errorf("Decoded length %d is not a multiple of the AES block size", len(bin))
+	}
+
+	if EMENames {
+		bin = eme.Transform(be.blockCipher, iv, bin, eme.DirectionDecrypt)
+	} else {
+		cbc := cipher.NewCBCDecrypter(be.blockCipher, iv)
+		cbc.CryptBlocks(bin, bin)
+	}
+
+	bin, err = be.unPad16(bin)
+	if err != nil {
+		return "", err
+	}
+
+	plain := string(bin)
+	return plain, err
+}
+
+// encryptName - encrypt "plainName", return base64-encoded "cipherName64"
+// The used encryption is either CBC or EME, depending on the "EMENames" argument.
+func (be *CryptFS) encryptName(plainName string, iv []byte, EMENames bool) (cipherName64 string) {
+
+	// Make sure relative symlinks still work after encryption
+	// by passing these trough unchanged
+	if plainName == "." || plainName == ".." {
+		return plainName
+	}
+
+	bin := []byte(plainName)
+	bin = be.pad16(bin)
+
+	if EMENames {
+		bin = eme.Transform(be.blockCipher, iv, bin, eme.DirectionEncrypt)
+	} else {
+		cbc := cipher.NewCBCEncrypter(be.blockCipher, iv)
+		cbc.CryptBlocks(bin, bin)
+	}
+
+	cipherName64 = base64.URLEncoding.EncodeToString(bin)
+	return cipherName64
+}
+
+// pad16 - pad filename to 16 byte blocks using standard PKCS#7 padding
+// https://tools.ietf.org/html/rfc5652#section-6.3
+func (be *CryptFS) pad16(orig []byte) (padded []byte) {
+	oldLen := len(orig)
+	if oldLen == 0 {
+		panic("Padding zero-length string makes no sense")
+	}
+	padLen := aes.BlockSize - oldLen%aes.BlockSize
+	if padLen == 0 {
+		padLen = aes.BlockSize
+	}
+	newLen := oldLen + padLen
+	padded = make([]byte, newLen)
+	copy(padded, orig)
+	padByte := byte(padLen)
+	for i := oldLen; i < newLen; i++ {
+		padded[i] = padByte
+	}
+	return padded
+}
+
+// unPad16 - remove padding
+func (be *CryptFS) unPad16(orig []byte) ([]byte, error) {
+	oldLen := len(orig)
+	if oldLen%aes.BlockSize != 0 {
+		return nil, errors.New("Unaligned size")
+	}
+	// The last byte is always a padding byte
+	padByte := orig[oldLen-1]
+	// The padding byte's value is the padding length
+	padLen := int(padByte)
+	// Padding must be at least 1 byte
+	if padLen <= 0 {
+		return nil, errors.New("Padding cannot be zero-length")
+	}
+	// Larger paddings make no sense
+	if padLen > aes.BlockSize {
+		return nil, errors.New("Padding cannot be larger than 16")
+	}
+	// All padding bytes must be identical
+	for i := oldLen - padLen; i < oldLen; i++ {
+		if orig[i] != padByte {
+			return nil, errors.New(fmt.Sprintf("Padding byte at i=%d is invalid", i))
+		}
+	}
+	newLen := oldLen - padLen
+	// Padding an empty string makes no sense
+	if newLen == 0 {
+		return nil, errors.New("Unpadded length is zero")
+	}
+	return orig[0:newLen], nil
+}
diff --git a/cryptfs/names_diriv.go b/cryptfs/names_diriv.go
index 035eac1..2e2429e 100644
--- a/cryptfs/names_diriv.go
+++ b/cryptfs/names_diriv.go
@@ -73,11 +73,8 @@ func WriteDirIV(dir string) error {
 	return ioutil.WriteFile(file, iv, 0444)
 }
 
-// EncryptPathDirIV - encrypt path using CBC with DirIV
-func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string) (string, error) {
-	if be.plaintextNames {
-		return plainPath, nil
-	}
+// EncryptPathDirIV - encrypt path using CBC or EME with DirIV
+func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool) (cipherPath string, err error) {
 	// Empty string means root directory
 	if plainPath == "" {
 		return plainPath, nil
@@ -88,36 +85,32 @@ func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string) (string, e
 	if found {
 		//fmt.Print("h")
 		baseName := filepath.Base(plainPath)
-		cBaseName := be.encryptName(baseName, iv)
-		cPath := cParentDir + "/" + cBaseName
-		return cPath, nil
+		cBaseName := be.encryptName(baseName, iv, eme)
+		cipherPath = cParentDir + "/" + cBaseName
+		return cipherPath, nil
 	}
 	// Walk the directory tree
 	var wd = rootDir
 	var encryptedNames []string
-	var err error
 	plainNames := strings.Split(plainPath, "/")
 	for _, plainName := range plainNames {
 		iv, err = be.ReadDirIV(wd)
 		if err != nil {
 			return "", err
 		}
-		encryptedName := be.encryptName(plainName, iv)
+		encryptedName := be.encryptName(plainName, iv, eme)
 		encryptedNames = append(encryptedNames, encryptedName)
 		wd = filepath.Join(wd, encryptedName)
 	}
 	// Cache the final DirIV
-	cPath := strings.Join(encryptedNames, "/")
-	cParentDir = filepath.Dir(cPath)
+	cipherPath = strings.Join(encryptedNames, "/")
+	cParentDir = filepath.Dir(cipherPath)
 	be.DirIVCacheEnc.store(parentDir, iv, cParentDir)
-	return cPath, nil
+	return cipherPath, nil
 }
 
-// DecryptPathDirIV - encrypt path using CBC with DirIV
-func (be *CryptFS) DecryptPathDirIV(encryptedPath string, rootDir string) (string, error) {
-	if be.plaintextNames {
-		return encryptedPath, nil
-	}
+// DecryptPathDirIV - encrypt path using CBC or EME with DirIV
+func (be *CryptFS) DecryptPathDirIV(encryptedPath string, rootDir string, eme bool) (string, error) {
 	var wd = rootDir
 	var plainNames []string
 	encryptedNames := strings.Split(encryptedPath, "/")
@@ -127,7 +120,7 @@ func (be *CryptFS) DecryptPathDirIV(encryptedPath string, rootDir string) (strin
 		if err != nil {
 			return "", err
 		}
-		plainName, err := be.DecryptName(encryptedName, iv)
+		plainName, err := be.decryptName(encryptedName, iv, eme)
 		if err != nil {
 			return "", err
 		}
diff --git a/cryptfs/names_noiv.go b/cryptfs/names_noiv.go
new file mode 100644
index 0000000..7eed4b8
--- /dev/null
+++ b/cryptfs/names_noiv.go
@@ -0,0 +1,63 @@
+package cryptfs
+
+import (
+	"strings"
+)
+
+const (
+	OpEncrypt = iota
+	OpDecrypt
+)
+
+// DecryptPathNoIV - decrypt path using CBC without any IV.
+// This function is deprecated by the the more secure DirIV variant and only retained
+// for compatability with old filesystems.
+func (be *CryptFS) DecryptPathNoIV(cipherPath string) (plainPath string, err error) {
+	plainPath, err = be.translatePathNoIV(cipherPath, OpDecrypt)
+	return plainPath, err
+}
+
+// EncryptPathNoIV - decrypt path using CBC without any IV.
+// This function is deprecated by the the more secure DirIV variant and only retained
+// for compatability with old filesystems.
+func (be *CryptFS) EncryptPathNoIV(plainPath string) (cipherPath string) {
+	cipherPath, _ = be.translatePathNoIV(plainPath, OpEncrypt)
+	return cipherPath
+}
+
+// translatePathZeroIV - encrypt or decrypt path using CBC with an all-zero IV.
+// Just splits the string on "/" and hands the parts to encryptName() / decryptName()
+func (be *CryptFS) translatePathNoIV(path string, op int) (string, error) {
+	var err error
+
+	// Empty string means root directory
+	if path == "" {
+		return path, err
+	}
+
+	zeroIV := make([]byte, DIRIV_LEN)
+
+	// Run operation on each path component
+	var translatedParts []string
+	parts := strings.Split(path, "/")
+	for _, part := range parts {
+		if part == "" {
+			// This happens on "/foo/bar/" on the front and on the end.
+			// Don't panic.
+			translatedParts = append(translatedParts, "")
+			continue
+		}
+		var newPart string
+		if op == OpEncrypt {
+			newPart = be.encryptName(part, zeroIV, false)
+		} else {
+			newPart, err = be.decryptName(part, zeroIV, false)
+			if err != nil {
+				return "", err
+			}
+		}
+		translatedParts = append(translatedParts, newPart)
+	}
+
+	return strings.Join(translatedParts, "/"), err
+}
diff --git a/cryptfs/names_test.go b/cryptfs/names_test.go
index 1ad3391..6dffae3 100644
--- a/cryptfs/names_test.go
+++ b/cryptfs/names_test.go
@@ -5,7 +5,7 @@ import (
 	"testing"
 )
 
-func TestTranslatePath(t *testing.T) {
+func TestEncryptPathNoIV(t *testing.T) {
 	var s []string
 	s = append(s, "foo")
 	s = append(s, "foo12312312312312312313123123123")
@@ -15,15 +15,14 @@ func TestTranslatePath(t *testing.T) {
 	fs := NewCryptFS(key, true, false)
 
 	for _, n := range s {
-		c, err := fs.TranslatePathZeroIV(n, OpEncrypt)
-		d, err := fs.TranslatePathZeroIV(c, OpDecrypt)
+		c := fs.EncryptPathNoIV(n)
+		d, err := fs.DecryptPathNoIV(c)
 		if err != nil {
-			t.Errorf("Got error from DecryptName: %s", err)
+			t.Errorf("Got error from DecryptPathNoIV: %s", err)
 		}
 		if d != n {
-			t.Errorf("Content mismatch, n=\"%s\" d=\"%s\"", n, d)
+			t.Errorf("Content mismatch, n != d: n=%s c=%s d=%s", n, c, d)
 		}
-		//fmt.Printf("n=%s c=%s d=%s\n", n, c, d)
 	}
 }
 
-- 
cgit v1.2.3