summaryrefslogtreecommitdiff
path: root/internal/nametransform/badname.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/nametransform/badname.go')
-rw-r--r--internal/nametransform/badname.go91
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
+}