diff options
Diffstat (limited to 'internal/nametransform')
-rw-r--r-- | internal/nametransform/names.go | 34 | ||||
-rw-r--r-- | internal/nametransform/names_test.go | 23 | ||||
-rw-r--r-- | internal/nametransform/xattr.go | 47 |
3 files changed, 94 insertions, 10 deletions
diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index e07ccfb..ebe0fb6 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -66,7 +66,14 @@ func New(e *eme.EMECipher, longNames bool, longNameMax uint8, raw64 bool, badnam func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) { res, err := n.decryptName(cipherName, iv) if err != nil && n.HaveBadnamePatterns() { - return n.decryptBadname(cipherName, iv) + res, err = n.decryptBadname(cipherName, iv) + } + if err != nil { + return "", err + } + if err := IsValidName(res); err != nil { + tlog.Warn.Printf("DecryptName %q: invalid name after decryption: %v", cipherName, err) + return "", syscall.EBADMSG } return res, err } @@ -79,30 +86,29 @@ func (n *NameTransform) decryptName(cipherName string, iv []byte) (string, error return "", err } if len(bin) == 0 { - tlog.Warn.Printf("DecryptName: empty input") + tlog.Warn.Printf("decryptName: empty input") return "", syscall.EBADMSG } if len(bin)%aes.BlockSize != 0 { - tlog.Debug.Printf("DecryptName %q: decoded length %d is not a multiple of 16", cipherName, len(bin)) + tlog.Debug.Printf("decryptName %q: decoded length %d is not a multiple of 16", cipherName, len(bin)) return "", syscall.EBADMSG } bin = n.emeCipher.Decrypt(iv, bin) bin, err = unPad16(bin) if err != nil { - tlog.Warn.Printf("DecryptName %q: unPad16 error: %v", cipherName, err) + tlog.Warn.Printf("decryptName %q: unPad16 error: %v", cipherName, err) return "", syscall.EBADMSG } plain := string(bin) - if err := IsValidName(plain); err != nil { - tlog.Warn.Printf("DecryptName %q: invalid name after decryption: %v", cipherName, err) - return "", syscall.EBADMSG - } return plain, err } -// EncryptName encrypts "plainName", returns a base64-encoded "cipherName64", +// EncryptName encrypts a file name "plainName" and returns a base64-encoded "cipherName64", // encrypted using EME (https://github.com/rfjakob/eme). // +// plainName is checked for null bytes, slashes etc. and such names are rejected +// with an error. +// // This function is exported because in some cases, fusefrontend needs access // to the full (not hashed) name if longname is used. func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 string, err error) { @@ -110,11 +116,19 @@ func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 s tlog.Warn.Printf("EncryptName %q: invalid plainName: %v", plainName, err) return "", syscall.EBADMSG } + return n.encryptName(plainName, iv), nil +} + +// encryptName encrypts "plainName" and returns a base64-encoded "cipherName64", +// encrypted using EME (https://github.com/rfjakob/eme). +// +// No checks for null bytes etc are performed against plainName. +func (n *NameTransform) encryptName(plainName string, iv []byte) (cipherName64 string) { bin := []byte(plainName) bin = pad16(bin) bin = n.emeCipher.Encrypt(iv, bin) cipherName64 = n.B64.EncodeToString(bin) - return cipherName64, nil + return cipherName64 } // EncryptAndHashName encrypts "name" and hashes it to a longname if it is diff --git a/internal/nametransform/names_test.go b/internal/nametransform/names_test.go index b4e98d4..3c26c43 100644 --- a/internal/nametransform/names_test.go +++ b/internal/nametransform/names_test.go @@ -75,3 +75,26 @@ func TestIsValidName(t *testing.T) { } } } + +func TestIsValidXattrName(t *testing.T) { + cases := []struct { + in string + want bool + }{ + {"", false}, + {".", true}, + {"..", true}, + {"...", true}, + {"asdasd/asdasd", true}, + {"asdasd\000asdasd", false}, + {"hello", true}, + {strings.Repeat("x", 255), true}, + {strings.Repeat("x", 256), true}, + } + for _, c := range cases { + have := isValidXattrName(c.in) + if (have == nil) != c.want { + t.Errorf("isValidXattrName(%q): want %v have %v", c.in, c.want, have) + } + } +} diff --git a/internal/nametransform/xattr.go b/internal/nametransform/xattr.go new file mode 100644 index 0000000..0aa0fd8 --- /dev/null +++ b/internal/nametransform/xattr.go @@ -0,0 +1,47 @@ +package nametransform + +import ( + "fmt" + "strings" + "syscall" + + "github.com/rfjakob/gocryptfs/v2/internal/tlog" +) + +// xattr names are encrypted like file names, but with a fixed IV. +// Padded with "_xx" for length 16. +var xattrNameIV = []byte("xattr_name_iv_xx") + +func isValidXattrName(name string) error { + if name == "" { + return fmt.Errorf("empty input") + } + if strings.Contains(name, "\000") { + return fmt.Errorf("contains forbidden null byte") + } + return nil +} + +// EncryptXattrName encrypts an extended attribute (xattr) name. +// xattr names are encrypted like file names, but with a fixed IV, and fewer +// naming restriction. +func (n *NameTransform) EncryptXattrName(plainName string) (cipherName64 string, err error) { + if err := isValidXattrName(plainName); err != nil { + tlog.Warn.Printf("EncryptXattrName %q: invalid plainName: %v", plainName, err) + return "", syscall.EBADMSG + } + return n.encryptName(plainName, xattrNameIV), nil +} + +// DecryptName calls decryptName to try and decrypt a base64-encoded encrypted +// filename "cipherName", and failing that checks if it can be bypassed +func (n *NameTransform) DecryptXattrName(cipherName string) (plainName string, err error) { + if plainName, err = n.decryptName(cipherName, xattrNameIV); err != nil { + return "", err + } + if err := isValidXattrName(plainName); err != nil { + tlog.Warn.Printf("DecryptXattrName %q: invalid name after decryption: %v", cipherName, err) + return "", syscall.EBADMSG + } + return plainName, err +} |