dc32710045
Determines when to start hashing long names instead of hardcoded 255. Will be used to alleviate "name too long" issues some users see on cloud storage. https://github.com/rfjakob/gocryptfs/issues/499
92 lines
3.0 KiB
Go
92 lines
3.0 KiB
Go
package nametransform
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
|
|
)
|
|
|
|
const (
|
|
// BadnameSuffix is appended to filenames in plaintext view if a corrupt
|
|
// ciphername is shown due to a matching `-badname` pattern
|
|
BadnameSuffix = " 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, BadnameSuffix) || 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(BadnameSuffix)], &st, unix.AT_SYMLINK_NOFOLLOW)
|
|
if err == nil {
|
|
filesFound++
|
|
lastFoundName = name[:len(name)-len(BadnameSuffix)]
|
|
}
|
|
// search for the longest badname pattern match
|
|
for charpos := len(name) - len(BadnameSuffix); 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 len(cName) > be.longNameMax {
|
|
cNamePart = be.HashLongName(cName)
|
|
}
|
|
cNameBadReverse := cNamePart + name[charpos:len(name)-len(BadnameSuffix)]
|
|
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:] + BadnameSuffix, nil
|
|
}
|
|
}
|
|
return cipherName + BadnameSuffix, nil
|
|
}
|
|
}
|
|
return "", syscall.EBADMSG
|
|
}
|
|
|
|
// HaveBadnamePatterns returns true if `-badname` patterns were provided
|
|
func (n *NameTransform) HaveBadnamePatterns() bool {
|
|
return len(n.badnamePatterns) > 0
|
|
}
|