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 --- Documentation/MANPAGE.md | 3 + 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 +- integration_tests/example_filesystems_test.go | 4 +- main.go | 20 +++- pathfs_frontend/args.go | 1 + pathfs_frontend/fs.go | 28 ++--- pathfs_frontend/names.go | 35 +++++- 16 files changed, 309 insertions(+), 229 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 diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index 481e6e3..446ce37 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -42,6 +42,9 @@ Options: **-diriv** : Use per-directory file name IV (default true) +**-emenames** +: Use EME filename encryption (default true). This option implies diriv. + **-extpass string** : Use an external program (like ssh-askpass) for the password prompt. The program should return the password on stdout, a trailing newline is 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) } } diff --git a/integration_tests/example_filesystems_test.go b/integration_tests/example_filesystems_test.go index 8081feb..a04e67e 100644 --- a/integration_tests/example_filesystems_test.go +++ b/integration_tests/example_filesystems_test.go @@ -57,7 +57,7 @@ func TestExampleFsV04(t *testing.T) { checkExampleContent(t, pDir) unmount(pDir) mount(cDir, pDir, "-masterkey", "74676e34-0b47c145-00dac61a-17a92316-"+ - "bb57044c-e205b71f-65f4fdca-7cabd4b3", "-diriv=false") + "bb57044c-e205b71f-65f4fdca-7cabd4b3", "-diriv=false", "-emenames=false") checkExampleContent(t, pDir) unmount(pDir) err = os.Remove(pDir) @@ -79,7 +79,7 @@ func TestExampleFsV05(t *testing.T) { checkExampleContent(t, pDir) unmount(pDir) mount(cDir, pDir, "-masterkey", "199eae55-36bff4af-83b9a3a2-4fa16f65-"+ - "1549ccdb-2d08d1f0-b1b26965-1b61f896") + "1549ccdb-2d08d1f0-b1b26965-1b61f896", "-emenames=false") checkExampleContent(t, pDir) unmount(pDir) err = os.Remove(pDir) diff --git a/main.go b/main.go index 9da89ab..06864d5 100644 --- a/main.go +++ b/main.go @@ -35,7 +35,7 @@ const ( type argContainer struct { debug, init, zerokey, fusedebug, openssl, passwd, foreground, version, - plaintextnames, quiet, diriv bool + plaintextnames, quiet, diriv, emenames bool masterkey, mountpoint, cipherdir, cpuprofile, config, extpass string notifypid, scryptn int } @@ -55,7 +55,7 @@ func initDir(args *argContainer) { // Create gocryptfs.conf cryptfs.Info.Printf("Choose a password for protecting your files.\n") password := readPasswordTwice(args.extpass) - err = cryptfs.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn) + err = cryptfs.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, args.emenames) if err != nil { fmt.Println(err) os.Exit(ERREXIT_INIT) @@ -146,6 +146,7 @@ func main() { "file names") flagSet.BoolVar(&args.quiet, "q", false, "Quiet - silence informational messages") flagSet.BoolVar(&args.diriv, "diriv", true, "Use per-directory file name IV") + flagSet.BoolVar(&args.emenames, "emenames", true, "Use EME filename encryption. This option implies diriv.") flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") flagSet.StringVar(&args.config, "config", "", "Use specified config file instead of CIPHERDIR/gocryptfs.conf") @@ -262,7 +263,7 @@ func main() { printMasterKey(masterkey) } // Initialize FUSE server - cryptfs.Debug.Printf("args: %v\n", args) + cryptfs.Debug.Printf("cli args: %v\n", args) srv := pathfsFrontend(masterkey, args, confFile) cryptfs.Info.Println("Filesystem ready.") // We are ready - send USR1 signal to our parent @@ -289,13 +290,26 @@ func pathfsFrontend(key []byte, args argContainer, confFile *cryptfs.ConfFile) * OpenSSL: args.openssl, PlaintextNames: args.plaintextnames, DirIV: args.diriv, + EMENames: args.emenames, } // confFile is nil when "-zerokey" or "-masterkey" was used if confFile != nil { // Settings from the config file override command line args frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(cryptfs.FlagPlaintextNames) frontendArgs.DirIV = confFile.IsFeatureFlagSet(cryptfs.FlagDirIV) + frontendArgs.EMENames = confFile.IsFeatureFlagSet(cryptfs.FlagEMENames) } + // EMENames implies DirIV, both on the command line and in the config file. + if frontendArgs.EMENames { + frontendArgs.DirIV = true + } + // PlainTexnames disables both EMENames and DirIV + if frontendArgs.PlaintextNames { + frontendArgs.DirIV = false + frontendArgs.EMENames = false + } + cryptfs.Debug.Printf("frontendArgs: ") + cryptfs.Debug.JSONDump(frontendArgs) finalFs := pathfs_frontend.NewFS(frontendArgs) pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true} diff --git a/pathfs_frontend/args.go b/pathfs_frontend/args.go index 86a907d..fb0b81f 100644 --- a/pathfs_frontend/args.go +++ b/pathfs_frontend/args.go @@ -7,4 +7,5 @@ type Args struct { OpenSSL bool PlaintextNames bool DirIV bool + EMENames bool } diff --git a/pathfs_frontend/fs.go b/pathfs_frontend/fs.go index 40e0d1d..0f462b0 100644 --- a/pathfs_frontend/fs.go +++ b/pathfs_frontend/fs.go @@ -48,7 +48,7 @@ func (fs *FS) getBackingPath(relPath string) (string, error) { func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) { cryptfs.Debug.Printf("FS.GetAttr('%s')\n", name) - if fs.CryptFS.IsFiltered(name) { + if fs.isFiltered(name) { return nil, fuse.EPERM } cName, err := fs.encryptPath(name) @@ -103,7 +103,7 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f continue } var name string - name, err = fs.CryptFS.DecryptName(cName, cachedIV) + name, err = fs.CryptFS.DecryptName(cName, cachedIV, fs.args.EMENames) if err != nil { cryptfs.Warn.Printf("Invalid name \"%s\" in dir \"%s\": %s\n", cName, dirName, err) continue @@ -128,7 +128,7 @@ func (fs *FS) mangleOpenFlags(flags uint32) (newFlags int, writeOnly bool) { } func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { - if fs.CryptFS.IsFiltered(path) { + if fs.isFiltered(path) { return nil, fuse.EPERM } iflags, writeOnly := fs.mangleOpenFlags(flags) @@ -147,7 +147,7 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n } func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) { - if fs.CryptFS.IsFiltered(path) { + if fs.isFiltered(path) { return nil, fuse.EPERM } iflags, writeOnly := fs.mangleOpenFlags(flags) @@ -163,7 +163,7 @@ func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Conte } func (fs *FS) Chmod(path string, mode uint32, context *fuse.Context) (code fuse.Status) { - if fs.CryptFS.IsFiltered(path) { + if fs.isFiltered(path) { return fuse.EPERM } cPath, err := fs.encryptPath(path) @@ -174,7 +174,7 @@ func (fs *FS) Chmod(path string, mode uint32, context *fuse.Context) (code fuse. } func (fs *FS) Chown(path string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) { - if fs.CryptFS.IsFiltered(path) { + if fs.isFiltered(path) { return fuse.EPERM } cPath, err := fs.encryptPath(path) @@ -185,7 +185,7 @@ func (fs *FS) Chown(path string, uid uint32, gid uint32, context *fuse.Context) } func (fs *FS) Mknod(path string, mode uint32, dev uint32, context *fuse.Context) (code fuse.Status) { - if fs.CryptFS.IsFiltered(path) { + if fs.isFiltered(path) { return fuse.EPERM } cPath, err := fs.encryptPath(path) @@ -201,7 +201,7 @@ func (fs *FS) Truncate(path string, offset uint64, context *fuse.Context) (code } func (fs *FS) Utimens(path string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) (code fuse.Status) { - if fs.CryptFS.IsFiltered(path) { + if fs.isFiltered(path) { return fuse.EPERM } cPath, err := fs.encryptPath(path) @@ -244,7 +244,7 @@ func (fs *FS) Readlink(path string, context *fuse.Context) (out string, status f } func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fuse.Status) { - if fs.CryptFS.IsFiltered(relPath) { + if fs.isFiltered(relPath) { return fuse.EPERM } encPath, err := fs.getBackingPath(relPath) @@ -275,7 +275,7 @@ func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fu } func (fs *FS) Unlink(path string, context *fuse.Context) (code fuse.Status) { - if fs.CryptFS.IsFiltered(path) { + if fs.isFiltered(path) { return fuse.EPERM } cPath, err := fs.getBackingPath(path) @@ -349,7 +349,7 @@ func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) { func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (code fuse.Status) { cryptfs.Debug.Printf("Symlink(\"%s\", \"%s\")\n", target, linkName) - if fs.CryptFS.IsFiltered(linkName) { + if fs.isFiltered(linkName) { return fuse.EPERM } cPath, err := fs.getBackingPath(linkName) @@ -376,7 +376,7 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co } func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) { - if fs.CryptFS.IsFiltered(newPath) { + if fs.isFiltered(newPath) { return fuse.EPERM } cOldPath, err := fs.getBackingPath(oldPath) @@ -396,7 +396,7 @@ func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (cod } func (fs *FS) Link(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) { - if fs.CryptFS.IsFiltered(newPath) { + if fs.isFiltered(newPath) { return fuse.EPERM } cOldPath, err := fs.getBackingPath(oldPath) @@ -411,7 +411,7 @@ func (fs *FS) Link(oldPath string, newPath string, context *fuse.Context) (code } func (fs *FS) Access(path string, mode uint32, context *fuse.Context) (code fuse.Status) { - if fs.CryptFS.IsFiltered(path) { + if fs.isFiltered(path) { return fuse.EPERM } cPath, err := fs.getBackingPath(path) diff --git a/pathfs_frontend/names.go b/pathfs_frontend/names.go index bb73ff4..e1783a6 100644 --- a/pathfs_frontend/names.go +++ b/pathfs_frontend/names.go @@ -6,20 +6,47 @@ import ( "github.com/rfjakob/gocryptfs/cryptfs" ) +// isFiltered - check if plaintext "path" should be forbidden +// +// Prevents name clashes with internal files when file names are not encrypted +func (fs *FS) isFiltered(path string) bool { + if !fs.args.PlaintextNames { + return false + } + // gocryptfs.conf in the root directory is forbidden + if path == cryptfs.ConfDefaultName { + cryptfs.Warn.Printf("The name /%s is reserved when -plaintextnames is used\n", + cryptfs.ConfDefaultName) + return true + } + // Note: gocryptfs.diriv is NOT forbidden because diriv and plaintextnames + // are exclusive + return false +} + + +// encryptPath - encrypt relative plaintext path func (fs *FS) encryptPath(plainPath string) (string, error) { + if fs.args.PlaintextNames { + return plainPath, nil + } if !fs.args.DirIV { - return fs.CryptFS.TranslatePathZeroIV(plainPath, cryptfs.OpEncrypt) + return fs.CryptFS.EncryptPathNoIV(plainPath), nil } fs.dirIVLock.RLock() defer fs.dirIVLock.RUnlock() - return fs.CryptFS.EncryptPathDirIV(plainPath, fs.args.Cipherdir) + return fs.CryptFS.EncryptPathDirIV(plainPath, fs.args.Cipherdir, fs.args.EMENames) } +// decryptPath - decrypt relative ciphertext path func (fs *FS) decryptPath(cipherPath string) (string, error) { + if fs.args.PlaintextNames { + return cipherPath, nil + } if !fs.args.DirIV { - return fs.CryptFS.TranslatePathZeroIV(cipherPath, cryptfs.OpDecrypt) + return fs.CryptFS.DecryptPathNoIV(cipherPath) } fs.dirIVLock.RLock() defer fs.dirIVLock.RUnlock() - return fs.CryptFS.DecryptPathDirIV(cipherPath, fs.args.Cipherdir) + return fs.CryptFS.DecryptPathDirIV(cipherPath, fs.args.Cipherdir, fs.args.EMENames) } -- cgit v1.2.3