diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/nametransform/badname.go | 91 | ||||
| -rw-r--r-- | internal/nametransform/diriv.go | 78 | ||||
| -rw-r--r-- | internal/nametransform/names.go | 48 | 
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  } | 
