You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
91 lines
3.0 KiB
91 lines
3.0 KiB
package nametransform |
|
|
|
import ( |
|
"crypto/aes" |
|
"path/filepath" |
|
"strings" |
|
"syscall" |
|
|
|
"golang.org/x/sys/unix" |
|
|
|
"libgocryptfs/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 be.longNames && len(cName) > NameMax { |
|
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 |
|
}
|
|
|