lib/protocol: Optimize encrypted filename handling + make it more strict (#7408)
This commit is contained in:
parent
0ffd80f380
commit
ffcaffa32f
|
@ -356,19 +356,23 @@ func DecryptFileInfo(fi FileInfo, folderKey *[keySize]byte) (FileInfo, error) {
|
||||||
return decFI, nil
|
return decFI, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var base32Hex = base32.HexEncoding.WithPadding(base32.NoPadding)
|
||||||
|
|
||||||
// encryptName encrypts the given string in a deterministic manner (the
|
// encryptName encrypts the given string in a deterministic manner (the
|
||||||
// result is always the same for any given string) and encodes it in a
|
// result is always the same for any given string) and encodes it in a
|
||||||
// filesystem-friendly manner.
|
// filesystem-friendly manner.
|
||||||
func encryptName(name string, key *[keySize]byte) string {
|
func encryptName(name string, key *[keySize]byte) string {
|
||||||
enc := encryptDeterministic([]byte(name), key, nil)
|
enc := encryptDeterministic([]byte(name), key, nil)
|
||||||
b32enc := base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(enc)
|
return slashify(base32Hex.EncodeToString(enc))
|
||||||
return slashify(b32enc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// decryptName decrypts a string from encryptName
|
// decryptName decrypts a string from encryptName
|
||||||
func decryptName(name string, key *[keySize]byte) (string, error) {
|
func decryptName(name string, key *[keySize]byte) (string, error) {
|
||||||
name = deslashify(name)
|
name, err := deslashify(name)
|
||||||
bs, err := base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(name)
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
bs, err := base32Hex.DecodeString(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -524,9 +528,12 @@ func slashify(s string) string {
|
||||||
|
|
||||||
// deslashify removes slashes and encrypted file extensions from the string.
|
// deslashify removes slashes and encrypted file extensions from the string.
|
||||||
// This is the inverse of slashify().
|
// This is the inverse of slashify().
|
||||||
func deslashify(s string) string {
|
func deslashify(s string) (string, error) {
|
||||||
s = strings.ReplaceAll(s, encryptedDirExtension, "")
|
if len(s) == 0 || !strings.HasPrefix(s[1:], encryptedDirExtension) {
|
||||||
return strings.ReplaceAll(s, "/", "")
|
return "", fmt.Errorf("invalid encrypted path: %q", s)
|
||||||
|
}
|
||||||
|
s = s[:1] + s[1+len(encryptedDirExtension):]
|
||||||
|
return strings.ReplaceAll(s, "/", ""), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawResponse struct {
|
type rawResponse struct {
|
||||||
|
|
|
@ -8,7 +8,9 @@ package protocol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -16,11 +18,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEnDecryptName(t *testing.T) {
|
func TestEnDecryptName(t *testing.T) {
|
||||||
|
pattern := regexp.MustCompile(
|
||||||
|
fmt.Sprintf("^[0-9A-V]%s/[0-9A-V]{2}/([0-9A-V]{%d}/)*[0-9A-V]{1,%d}$",
|
||||||
|
regexp.QuoteMeta(encryptedDirExtension),
|
||||||
|
maxPathComponent, maxPathComponent-1))
|
||||||
|
|
||||||
|
makeName := func(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = byte('a' + i%26)
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
var key [32]byte
|
var key [32]byte
|
||||||
cases := []string{
|
cases := []string{
|
||||||
"",
|
"",
|
||||||
"foo",
|
"foo",
|
||||||
"a longer name/with/slashes and spaces",
|
"a longer name/with/slashes and spaces",
|
||||||
|
makeName(maxPathComponent),
|
||||||
|
makeName(1 + maxPathComponent),
|
||||||
|
makeName(2 * maxPathComponent),
|
||||||
|
makeName(1 + 2*maxPathComponent),
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
var prev string
|
var prev string
|
||||||
|
@ -33,6 +52,11 @@ func TestEnDecryptName(t *testing.T) {
|
||||||
if tc != "" && strings.Contains(enc, tc) {
|
if tc != "" && strings.Contains(enc, tc) {
|
||||||
t.Error("shouldn't contain plaintext")
|
t.Error("shouldn't contain plaintext")
|
||||||
}
|
}
|
||||||
|
if !pattern.MatchString(enc) {
|
||||||
|
t.Fatalf("encrypted name %s doesn't match %s",
|
||||||
|
enc, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
dec, err := decryptName(enc, &key)
|
dec, err := decryptName(enc, &key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -40,7 +64,21 @@ func TestEnDecryptName(t *testing.T) {
|
||||||
if dec != tc {
|
if dec != tc {
|
||||||
t.Error("mismatch after decryption")
|
t.Error("mismatch after decryption")
|
||||||
}
|
}
|
||||||
t.Log(enc)
|
t.Logf("%q encrypts as %q", tc, enc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecryptNameInvalid(t *testing.T) {
|
||||||
|
key := new([32]byte)
|
||||||
|
for _, c := range []string{
|
||||||
|
"T.syncthing-enc/OD",
|
||||||
|
"T.syncthing-enc/OD/",
|
||||||
|
"T.wrong-extension/OD/PHVDD67S7FI2K5QQMPSOFSK",
|
||||||
|
"OD/PHVDD67S7FI2K5QQMPSOFSK",
|
||||||
|
} {
|
||||||
|
if _, err := decryptName(c, key); err == nil {
|
||||||
|
t.Errorf("no error for %q", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue