summaryrefslogtreecommitdiff
path: root/internal/nametransform
diff options
context:
space:
mode:
Diffstat (limited to 'internal/nametransform')
-rw-r--r--internal/nametransform/badname.go91
-rw-r--r--internal/nametransform/diriv.go78
-rw-r--r--internal/nametransform/names.go48
3 files changed, 118 insertions, 99 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
+}
diff --git a/internal/nametransform/diriv.go b/internal/nametransform/diriv.go
index d62b3fb..b10c899 100644
--- a/internal/nametransform/diriv.go
+++ b/internal/nametransform/diriv.go
@@ -5,14 +5,11 @@ import (
"fmt"
"io"
"os"
- "path/filepath"
- "strings"
"syscall"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/internal/tlog"
- "golang.org/x/sys/unix"
)
const (
@@ -95,78 +92,3 @@ func WriteDirIVAt(dirfd int) error {
}
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
-}
-
-// 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
-}
diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go
index 2ee52e4..566f0c7 100644
--- a/internal/nametransform/names.go
+++ b/internal/nametransform/names.go
@@ -15,8 +15,6 @@ import (
const (
// Like ext4, we allow at most 255 bytes for a file name.
NameMax = 255
- //BadNameFlag is appended to filenames in plain mode if a ciphername is inavlid but is shown
- BadNameFlag = " GOCRYPTFS_BAD_NAME"
)
// NameTransform is used to transform filenames.
@@ -51,22 +49,8 @@ func New(e *eme.EMECipher, longNames bool, raw64 bool, badname []string) *NameTr
// filename "cipherName", and failing that checks if it can be bypassed
func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
res, err := n.decryptName(cipherName, iv)
- if err != nil {
- for _, pattern := range n.badnamePatterns {
- match, err := filepath.Match(pattern, cipherName)
- if err == nil && match { // Pattern should have been validated already
- // 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
- }
- }
+ if err != nil && n.HaveBadnamePatterns() {
+ return n.decryptBadname(cipherName, iv)
}
return res, err
}
@@ -117,6 +101,24 @@ func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 s
return cipherName64, 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
+}
+
// B64EncodeToString returns a Base64-encoded string
func (n *NameTransform) B64EncodeToString(src []byte) string {
return n.B64.EncodeToString(src)
@@ -127,7 +129,11 @@ func (n *NameTransform) B64DecodeString(s string) ([]byte, error) {
return n.B64.DecodeString(s)
}
-// HaveBadnamePatterns returns true if BadName patterns were provided
-func (n *NameTransform) HaveBadnamePatterns() bool {
- return len(n.badnamePatterns) > 0
+// Dir is like filepath.Dir but returns "" instead of ".".
+func Dir(path string) string {
+ d := filepath.Dir(path)
+ if d == "." {
+ return ""
+ }
+ return d
}