libgocryptfs/internal/nametransform/diriv.go

173 lines
5.5 KiB
Go
Raw Normal View History

Major refactoring: Split up "cryptfs" into several internal packages "git status" for reference: deleted: cryptfs/cryptfs.go deleted: cryptfs/names_core.go modified: integration_tests/cli_test.go modified: integration_tests/helpers.go renamed: cryptfs/config_file.go -> internal/configfile/config_file.go renamed: cryptfs/config_test.go -> internal/configfile/config_test.go renamed: cryptfs/config_test/.gitignore -> internal/configfile/config_test/.gitignore renamed: cryptfs/config_test/PlaintextNames.conf -> internal/configfile/config_test/PlaintextNames.conf renamed: cryptfs/config_test/StrangeFeature.conf -> internal/configfile/config_test/StrangeFeature.conf renamed: cryptfs/config_test/v1.conf -> internal/configfile/config_test/v1.conf renamed: cryptfs/config_test/v2.conf -> internal/configfile/config_test/v2.conf renamed: cryptfs/kdf.go -> internal/configfile/kdf.go renamed: cryptfs/kdf_test.go -> internal/configfile/kdf_test.go renamed: cryptfs/cryptfs_content.go -> internal/contentenc/content.go new file: internal/contentenc/content_api.go renamed: cryptfs/content_test.go -> internal/contentenc/content_test.go renamed: cryptfs/file_header.go -> internal/contentenc/file_header.go renamed: cryptfs/intrablock.go -> internal/contentenc/intrablock.go renamed: cryptfs/address_translation.go -> internal/contentenc/offsets.go new file: internal/cryptocore/crypto_api.go renamed: cryptfs/gcm_go1.4.go -> internal/cryptocore/gcm_go1.4.go renamed: cryptfs/gcm_go1.5.go -> internal/cryptocore/gcm_go1.5.go renamed: cryptfs/nonce.go -> internal/cryptocore/nonce.go renamed: cryptfs/openssl_aead.go -> internal/cryptocore/openssl_aead.go renamed: cryptfs/openssl_benchmark.bash -> internal/cryptocore/openssl_benchmark.bash renamed: cryptfs/openssl_test.go -> internal/cryptocore/openssl_test.go new file: internal/nametransform/name_api.go new file: internal/nametransform/names_core.go renamed: cryptfs/names_diriv.go -> internal/nametransform/names_diriv.go renamed: cryptfs/names_noiv.go -> internal/nametransform/names_noiv.go renamed: cryptfs/names_test.go -> internal/nametransform/names_test.go new file: internal/nametransform/pad16.go renamed: cryptfs/log.go -> internal/toggledlog/log.go renamed: cryptfs/log_go1.4.go -> internal/toggledlog/log_go1.4.go renamed: cryptfs/log_go1.5.go -> internal/toggledlog/log_go1.5.go modified: main.go modified: masterkey.go modified: pathfs_frontend/file.go modified: pathfs_frontend/file_holes.go modified: pathfs_frontend/fs.go modified: pathfs_frontend/fs_dir.go modified: pathfs_frontend/names.go modified: test.bash
2016-02-06 19:20:54 +01:00
package nametransform
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
Badname file content access This proposal is the counterpart of the modifications from the `-badname` parameter. It modifies the plain -> cipher mapping for filenames when using `-badname` parameter. The new function `EncryptAndHashBadName` tries to find a cipher filename for the given plain name with the following steps: 1. If `badname` is disabled or direct mapping is successful: Map directly (default and current behaviour) 2. If a file with badname flag has a valid cipher file, this is returned (=File just ends with the badname flag) 3. If a file with a badname flag exists where only the badname flag was added, this is returned (=File cipher name could not be decrypted by function `DecryptName` and just the badname flag was added) 4. Search for all files which cipher file name extists when cropping more and more characters from the end. If only 1 file is found, return this 5. Return an error otherwise This allows file access in the file browsers but most important it allows that you rename files with undecryptable cipher names in the plain directories. Renaming those files will then generate a proper cipher filename One backdraft: When mounting the cipher dir with -badname parameter, you can never create (or rename to) files whose file name ends with the badname file flag (at the moment this is " GOCRYPTFS_BAD_NAME"). This will cause an error. I modified the CLI test function to cover additional test cases. Test [Case 7](https://github.com/DerDonut/gocryptfs/blob/badnamecontent/tests/cli/cli_test.go#L712) cannot be performed since the cli tests are executed in panic mode. The testing is stopped on error. Since the function`DecryptName` produces internal errors when hitting non-decryptable file names, this test was omitted. This implementation is a proposal where I tried to change the minimum amount of existing code. Another possibility would be instead of creating the new function `EncryptAndHashBadName` to modify the signature of the existing function `EncryptAndHashName(name string, iv []byte)` to `EncryptAndHashName(name string, iv []byte, dirfd int)` and integrate the functionality into this function directly. You may allow calling with dirfd=-1 or other invalid values an then performing the current functionality.
2021-06-17 08:11:33 +02:00
"strings"
"syscall"
Major refactoring: Split up "cryptfs" into several internal packages "git status" for reference: deleted: cryptfs/cryptfs.go deleted: cryptfs/names_core.go modified: integration_tests/cli_test.go modified: integration_tests/helpers.go renamed: cryptfs/config_file.go -> internal/configfile/config_file.go renamed: cryptfs/config_test.go -> internal/configfile/config_test.go renamed: cryptfs/config_test/.gitignore -> internal/configfile/config_test/.gitignore renamed: cryptfs/config_test/PlaintextNames.conf -> internal/configfile/config_test/PlaintextNames.conf renamed: cryptfs/config_test/StrangeFeature.conf -> internal/configfile/config_test/StrangeFeature.conf renamed: cryptfs/config_test/v1.conf -> internal/configfile/config_test/v1.conf renamed: cryptfs/config_test/v2.conf -> internal/configfile/config_test/v2.conf renamed: cryptfs/kdf.go -> internal/configfile/kdf.go renamed: cryptfs/kdf_test.go -> internal/configfile/kdf_test.go renamed: cryptfs/cryptfs_content.go -> internal/contentenc/content.go new file: internal/contentenc/content_api.go renamed: cryptfs/content_test.go -> internal/contentenc/content_test.go renamed: cryptfs/file_header.go -> internal/contentenc/file_header.go renamed: cryptfs/intrablock.go -> internal/contentenc/intrablock.go renamed: cryptfs/address_translation.go -> internal/contentenc/offsets.go new file: internal/cryptocore/crypto_api.go renamed: cryptfs/gcm_go1.4.go -> internal/cryptocore/gcm_go1.4.go renamed: cryptfs/gcm_go1.5.go -> internal/cryptocore/gcm_go1.5.go renamed: cryptfs/nonce.go -> internal/cryptocore/nonce.go renamed: cryptfs/openssl_aead.go -> internal/cryptocore/openssl_aead.go renamed: cryptfs/openssl_benchmark.bash -> internal/cryptocore/openssl_benchmark.bash renamed: cryptfs/openssl_test.go -> internal/cryptocore/openssl_test.go new file: internal/nametransform/name_api.go new file: internal/nametransform/names_core.go renamed: cryptfs/names_diriv.go -> internal/nametransform/names_diriv.go renamed: cryptfs/names_noiv.go -> internal/nametransform/names_noiv.go renamed: cryptfs/names_test.go -> internal/nametransform/names_test.go new file: internal/nametransform/pad16.go renamed: cryptfs/log.go -> internal/toggledlog/log.go renamed: cryptfs/log_go1.4.go -> internal/toggledlog/log_go1.4.go renamed: cryptfs/log_go1.5.go -> internal/toggledlog/log_go1.5.go modified: main.go modified: masterkey.go modified: pathfs_frontend/file.go modified: pathfs_frontend/file_holes.go modified: pathfs_frontend/fs.go modified: pathfs_frontend/fs_dir.go modified: pathfs_frontend/names.go modified: test.bash
2016-02-06 19:20:54 +01:00
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/internal/tlog"
Badname file content access This proposal is the counterpart of the modifications from the `-badname` parameter. It modifies the plain -> cipher mapping for filenames when using `-badname` parameter. The new function `EncryptAndHashBadName` tries to find a cipher filename for the given plain name with the following steps: 1. If `badname` is disabled or direct mapping is successful: Map directly (default and current behaviour) 2. If a file with badname flag has a valid cipher file, this is returned (=File just ends with the badname flag) 3. If a file with a badname flag exists where only the badname flag was added, this is returned (=File cipher name could not be decrypted by function `DecryptName` and just the badname flag was added) 4. Search for all files which cipher file name extists when cropping more and more characters from the end. If only 1 file is found, return this 5. Return an error otherwise This allows file access in the file browsers but most important it allows that you rename files with undecryptable cipher names in the plain directories. Renaming those files will then generate a proper cipher filename One backdraft: When mounting the cipher dir with -badname parameter, you can never create (or rename to) files whose file name ends with the badname file flag (at the moment this is " GOCRYPTFS_BAD_NAME"). This will cause an error. I modified the CLI test function to cover additional test cases. Test [Case 7](https://github.com/DerDonut/gocryptfs/blob/badnamecontent/tests/cli/cli_test.go#L712) cannot be performed since the cli tests are executed in panic mode. The testing is stopped on error. Since the function`DecryptName` produces internal errors when hitting non-decryptable file names, this test was omitted. This implementation is a proposal where I tried to change the minimum amount of existing code. Another possibility would be instead of creating the new function `EncryptAndHashBadName` to modify the signature of the existing function `EncryptAndHashName(name string, iv []byte)` to `EncryptAndHashName(name string, iv []byte, dirfd int)` and integrate the functionality into this function directly. You may allow calling with dirfd=-1 or other invalid values an then performing the current functionality.
2021-06-17 08:11:33 +02:00
"golang.org/x/sys/unix"
Major refactoring: Split up "cryptfs" into several internal packages "git status" for reference: deleted: cryptfs/cryptfs.go deleted: cryptfs/names_core.go modified: integration_tests/cli_test.go modified: integration_tests/helpers.go renamed: cryptfs/config_file.go -> internal/configfile/config_file.go renamed: cryptfs/config_test.go -> internal/configfile/config_test.go renamed: cryptfs/config_test/.gitignore -> internal/configfile/config_test/.gitignore renamed: cryptfs/config_test/PlaintextNames.conf -> internal/configfile/config_test/PlaintextNames.conf renamed: cryptfs/config_test/StrangeFeature.conf -> internal/configfile/config_test/StrangeFeature.conf renamed: cryptfs/config_test/v1.conf -> internal/configfile/config_test/v1.conf renamed: cryptfs/config_test/v2.conf -> internal/configfile/config_test/v2.conf renamed: cryptfs/kdf.go -> internal/configfile/kdf.go renamed: cryptfs/kdf_test.go -> internal/configfile/kdf_test.go renamed: cryptfs/cryptfs_content.go -> internal/contentenc/content.go new file: internal/contentenc/content_api.go renamed: cryptfs/content_test.go -> internal/contentenc/content_test.go renamed: cryptfs/file_header.go -> internal/contentenc/file_header.go renamed: cryptfs/intrablock.go -> internal/contentenc/intrablock.go renamed: cryptfs/address_translation.go -> internal/contentenc/offsets.go new file: internal/cryptocore/crypto_api.go renamed: cryptfs/gcm_go1.4.go -> internal/cryptocore/gcm_go1.4.go renamed: cryptfs/gcm_go1.5.go -> internal/cryptocore/gcm_go1.5.go renamed: cryptfs/nonce.go -> internal/cryptocore/nonce.go renamed: cryptfs/openssl_aead.go -> internal/cryptocore/openssl_aead.go renamed: cryptfs/openssl_benchmark.bash -> internal/cryptocore/openssl_benchmark.bash renamed: cryptfs/openssl_test.go -> internal/cryptocore/openssl_test.go new file: internal/nametransform/name_api.go new file: internal/nametransform/names_core.go renamed: cryptfs/names_diriv.go -> internal/nametransform/names_diriv.go renamed: cryptfs/names_noiv.go -> internal/nametransform/names_noiv.go renamed: cryptfs/names_test.go -> internal/nametransform/names_test.go new file: internal/nametransform/pad16.go renamed: cryptfs/log.go -> internal/toggledlog/log.go renamed: cryptfs/log_go1.4.go -> internal/toggledlog/log_go1.4.go renamed: cryptfs/log_go1.5.go -> internal/toggledlog/log_go1.5.go modified: main.go modified: masterkey.go modified: pathfs_frontend/file.go modified: pathfs_frontend/file_holes.go modified: pathfs_frontend/fs.go modified: pathfs_frontend/fs_dir.go modified: pathfs_frontend/names.go modified: test.bash
2016-02-06 19:20:54 +01:00
)
const (
2016-10-02 06:14:18 +02:00
// DirIVLen is identical to AES block size
DirIVLen = 16
2016-10-02 06:14:18 +02:00
// DirIVFilename is the filename used to store directory IV.
// Exported because we have to ignore this name in directory listing.
2016-02-06 20:23:36 +01:00
DirIVFilename = "gocryptfs.diriv"
)
// ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd".
// Using the dirfd makes it immune to concurrent renames of the directory.
// Retries on EINTR.
func ReadDirIVAt(dirfd int) (iv []byte, err error) {
fdRaw, err := syscallcompat.Openat(dirfd, DirIVFilename,
syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
if err != nil {
return nil, err
}
fd := os.NewFile(uintptr(fdRaw), DirIVFilename)
defer fd.Close()
return fdReadDirIV(fd)
}
// allZeroDirIV is preallocated to quickly check if the data read from disk is all zero
var allZeroDirIV = make([]byte, DirIVLen)
// fdReadDirIV reads and verifies the DirIV from an opened gocryptfs.diriv file.
func fdReadDirIV(fd *os.File) (iv []byte, err error) {
// We want to detect if the file is bigger than DirIVLen, so
// make the buffer 1 byte bigger than necessary.
iv = make([]byte, DirIVLen+1)
n, err := fd.Read(iv)
if err != nil && err != io.EOF {
return nil, fmt.Errorf("read failed: %v", err)
}
iv = iv[0:n]
if len(iv) != DirIVLen {
return nil, fmt.Errorf("wanted %d bytes, got %d", DirIVLen, len(iv))
2015-11-27 22:20:01 +01:00
}
if bytes.Equal(iv, allZeroDirIV) {
return nil, fmt.Errorf("diriv is all-zero")
}
2015-11-27 22:20:01 +01:00
return iv, nil
}
// WriteDirIVAt - create a new gocryptfs.diriv file in the directory opened at
// "dirfd". On error we try to delete the incomplete file.
// This function is exported because it is used from fusefrontend, main,
// and also the automated tests.
func WriteDirIVAt(dirfd int) error {
iv := cryptocore.RandBytes(DirIVLen)
// 0400 permissions: gocryptfs.diriv should never be modified after creation.
// Don't use "ioutil.WriteFile", it causes trouble on NFS:
// https://github.com/rfjakob/gocryptfs/commit/7d38f80a78644c8ec4900cc990bfb894387112ed
fd, err := syscallcompat.Openat(dirfd, DirIVFilename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, dirivPerms)
if err != nil {
tlog.Warn.Printf("WriteDirIV: Openat: %v", err)
return err
}
// Wrap the fd in an os.File - we need the write retry logic.
f := os.NewFile(uintptr(fd), DirIVFilename)
_, err = f.Write(iv)
if err != nil {
f.Close()
// It is normal to get ENOSPC here
if !syscallcompat.IsENOSPC(err) {
tlog.Warn.Printf("WriteDirIV: Write: %v", err)
}
// Delete incomplete gocryptfs.diriv file
syscallcompat.Unlinkat(dirfd, DirIVFilename, 0)
return err
}
err = f.Close()
if err != nil {
tlog.Warn.Printf("WriteDirIV: Close: %v", err)
// Delete incomplete gocryptfs.diriv file
syscallcompat.Unlinkat(dirfd, DirIVFilename, 0)
return err
}
return nil
}
// encryptAndHashName encrypts "name" and hashes it to a longname if it is
// too long.
// Returns ENAMETOOLONG if "name" is longer than 255 bytes.
func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, error) {
// Prevent the user from creating files longer than 255 chars.
if len(name) > NameMax {
return "", syscall.ENAMETOOLONG
}
cName, err := be.EncryptName(name, iv)
if err != nil {
return "", err
}
if be.longNames && len(cName) > NameMax {
return be.HashLongName(cName), nil
}
return cName, nil
}
Badname file content access This proposal is the counterpart of the modifications from the `-badname` parameter. It modifies the plain -> cipher mapping for filenames when using `-badname` parameter. The new function `EncryptAndHashBadName` tries to find a cipher filename for the given plain name with the following steps: 1. If `badname` is disabled or direct mapping is successful: Map directly (default and current behaviour) 2. If a file with badname flag has a valid cipher file, this is returned (=File just ends with the badname flag) 3. If a file with a badname flag exists where only the badname flag was added, this is returned (=File cipher name could not be decrypted by function `DecryptName` and just the badname flag was added) 4. Search for all files which cipher file name extists when cropping more and more characters from the end. If only 1 file is found, return this 5. Return an error otherwise This allows file access in the file browsers but most important it allows that you rename files with undecryptable cipher names in the plain directories. Renaming those files will then generate a proper cipher filename One backdraft: When mounting the cipher dir with -badname parameter, you can never create (or rename to) files whose file name ends with the badname file flag (at the moment this is " GOCRYPTFS_BAD_NAME"). This will cause an error. I modified the CLI test function to cover additional test cases. Test [Case 7](https://github.com/DerDonut/gocryptfs/blob/badnamecontent/tests/cli/cli_test.go#L712) cannot be performed since the cli tests are executed in panic mode. The testing is stopped on error. Since the function`DecryptName` produces internal errors when hitting non-decryptable file names, this test was omitted. This implementation is a proposal where I tried to change the minimum amount of existing code. Another possibility would be instead of creating the new function `EncryptAndHashBadName` to modify the signature of the existing function `EncryptAndHashName(name string, iv []byte)` to `EncryptAndHashName(name string, iv []byte, dirfd int)` and integrate the functionality into this function directly. You may allow calling with dirfd=-1 or other invalid values an then performing the current functionality.
2021-06-17 08:11:33 +02:00
// EncryptAndHashBadName tries to find the "name" substring, which (encrypted and hashed)
// leads to an unique existing file
// Returns ENOENT if cipher file does not exist or is not unique
func (be *NameTransform) EncryptAndHashBadName(name string, iv []byte, dirfd int) (cName string, err error) {
var st unix.Stat_t
var filesFound int
lastFoundName, err := be.EncryptAndHashName(name, iv)
if !strings.HasSuffix(name, BadNameFlag) || err != nil {
//Default mode: same behaviour on error or no BadNameFlag on "name"
return lastFoundName, err
}
//Default mode: Check if File extists without modifications
err = syscallcompat.Fstatat(dirfd, lastFoundName, &st, unix.AT_SYMLINK_NOFOLLOW)
if err == nil {
//file found, return result
return lastFoundName, nil
}
//BadName Mode: check if the name was tranformed without change (badname suffix and undecryptable cipher name)
err = syscallcompat.Fstatat(dirfd, name[:len(name)-len(BadNameFlag)], &st, unix.AT_SYMLINK_NOFOLLOW)
if err == nil {
filesFound++
lastFoundName = name[:len(name)-len(BadNameFlag)]
}
// search for the longest badname pattern match
for charpos := len(name) - len(BadNameFlag); charpos > 0; charpos-- {
//only use original cipher name and append assumed suffix (without badname flag)
cNamePart, err := be.EncryptName(name[:charpos], iv)
if err != nil {
//expand suffix on error
continue
}
if be.longNames && len(cName) > NameMax {
cNamePart = be.HashLongName(cName)
}
cNameBadReverse := cNamePart + name[charpos:len(name)-len(BadNameFlag)]
err = syscallcompat.Fstatat(dirfd, cNameBadReverse, &st, unix.AT_SYMLINK_NOFOLLOW)
if err == nil {
filesFound++
lastFoundName = cNameBadReverse
}
}
if filesFound == 1 {
return lastFoundName, nil
}
// more than 1 possible file found, ignore
return "", syscall.ENOENT
}
// Dir is like filepath.Dir but returns "" instead of ".".
func Dir(path string) string {
d := filepath.Dir(path)
if d == "." {
return ""
}
return d
}