diff options
author | Jakob Unterwurzacher | 2021-06-21 12:08:18 +0200 |
---|---|---|
committer | Jakob Unterwurzacher | 2021-06-21 12:10:04 +0200 |
commit | 689b74835bd38ebaf87ba0e205c10b9594e51863 (patch) | |
tree | 25bc1ae8aebecaba55feba630745d4d2af4facaf /internal/nametransform/badname.go | |
parent | 2efef1e270a0e374c479326ab2c296b5e9fdc34d (diff) |
nametransform: gather badname functions in badname.go
Diffstat (limited to 'internal/nametransform/badname.go')
-rw-r--r-- | internal/nametransform/badname.go | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/internal/nametransform/badname.go b/internal/nametransform/badname.go new file mode 100644 index 0000000..5cc9799 --- /dev/null +++ b/internal/nametransform/badname.go @@ -0,0 +1,91 @@ +package nametransform + +import ( + "crypto/aes" + "path/filepath" + "strings" + "syscall" + + "golang.org/x/sys/unix" + + "github.com/rfjakob/gocryptfs/internal/syscallcompat" +) + +const ( + // BadNameFlag is appended to filenames in plaintext view if a corrupt + // ciphername is shown due to a matching `-badname` pattern + BadNameFlag = " GOCRYPTFS_BAD_NAME" +) + +// 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 +} + +func (n *NameTransform) decryptBadname(cipherName string, iv []byte) (string, error) { + for _, pattern := range n.badnamePatterns { + match, err := filepath.Match(pattern, cipherName) + // Pattern should have been validated already + if err == nil && match { + // Find longest decryptable substring + // At least 16 bytes due to AES --> at least 22 characters in base64 + nameMin := n.B64.EncodedLen(aes.BlockSize) + for charpos := len(cipherName) - 1; charpos >= nameMin; charpos-- { + res, err := n.decryptName(cipherName[:charpos], iv) + if err == nil { + return res + cipherName[charpos:] + BadNameFlag, nil + } + } + return cipherName + BadNameFlag, nil + } + } + return "", syscall.EBADMSG +} + +// HaveBadnamePatterns returns true if `-badname` patterns were provided +func (n *NameTransform) HaveBadnamePatterns() bool { + return len(n.badnamePatterns) > 0 +} |