encryption_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. // Copyright (C) 2019 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package protocol
  7. import (
  8. "bytes"
  9. "fmt"
  10. "reflect"
  11. "regexp"
  12. "runtime"
  13. "strings"
  14. "testing"
  15. "github.com/syncthing/syncthing/lib/build"
  16. "github.com/syncthing/syncthing/lib/rand"
  17. )
  18. var (
  19. testKeyGen = NewKeyGenerator()
  20. // https://github.com/syncthing/syncthing/issues/8799
  21. cryptoIsBrokenUnderRaceDetector = (build.IsLinux || build.IsDarwin) && strings.HasPrefix(runtime.Version(), "go1.20")
  22. )
  23. func TestEnDecryptName(t *testing.T) {
  24. if cryptoIsBrokenUnderRaceDetector {
  25. t.Skip("cannot test")
  26. }
  27. pattern := regexp.MustCompile(
  28. fmt.Sprintf("^[0-9A-V]%s/[0-9A-V]{2}/([0-9A-V]{%d}/)*[0-9A-V]{1,%d}$",
  29. regexp.QuoteMeta(encryptedDirExtension),
  30. maxPathComponent, maxPathComponent-1))
  31. makeName := func(n int) string {
  32. b := make([]byte, n)
  33. for i := range b {
  34. b[i] = byte('a' + i%26)
  35. }
  36. return string(b)
  37. }
  38. var key [32]byte
  39. cases := []string{
  40. "",
  41. "foo",
  42. "a longer name/with/slashes and spaces",
  43. makeName(maxPathComponent),
  44. makeName(1 + maxPathComponent),
  45. makeName(2 * maxPathComponent),
  46. makeName(1 + 2*maxPathComponent),
  47. }
  48. for _, tc := range cases {
  49. var prev string
  50. for i := 0; i < 5; i++ {
  51. enc := encryptName(tc, &key)
  52. if prev != "" && prev != enc {
  53. t.Error("name should always encrypt the same")
  54. }
  55. prev = enc
  56. if tc != "" && strings.Contains(enc, tc) {
  57. t.Error("shouldn't contain plaintext")
  58. }
  59. if !pattern.MatchString(enc) {
  60. t.Fatalf("encrypted name %s doesn't match %s",
  61. enc, pattern)
  62. }
  63. dec, err := decryptName(enc, &key)
  64. if err != nil {
  65. t.Error(err)
  66. }
  67. if dec != tc {
  68. t.Error("mismatch after decryption")
  69. }
  70. t.Logf("%q encrypts as %q", tc, enc)
  71. }
  72. }
  73. }
  74. func TestKeyDerivation(t *testing.T) {
  75. folderKey := testKeyGen.KeyFromPassword("my folder", "my password")
  76. encryptedName := encryptDeterministic([]byte("filename.txt"), folderKey, nil)
  77. if base32Hex.EncodeToString(encryptedName) != "3T5957I4IOA20VEIEER6JSQG0PEPIRV862II3K7LOF75Q" {
  78. t.Error("encrypted name mismatch")
  79. }
  80. fileKey := testKeyGen.FileKey("filename.txt", folderKey)
  81. // fmt.Println(base32Hex.EncodeToString(encryptBytes([]byte("hello world"), fileKey))) => A1IPD...
  82. const encrypted = `A1IPD28ISL7VNPRSSSQM2L31L3IJPC08283RO89J5UG0TI9P38DO9RFGK12DK0KD7PKQP6U51UL2B6H96O`
  83. bs, _ := base32Hex.DecodeString(encrypted)
  84. dec, err := DecryptBytes(bs, fileKey)
  85. if err != nil {
  86. t.Error(err)
  87. }
  88. if string(dec) != "hello world" {
  89. t.Error("decryption mismatch")
  90. }
  91. }
  92. func TestDecryptNameInvalid(t *testing.T) {
  93. key := new([32]byte)
  94. for _, c := range []string{
  95. "T.syncthing-enc/OD",
  96. "T.syncthing-enc/OD/",
  97. "T.wrong-extension/OD/PHVDD67S7FI2K5QQMPSOFSK",
  98. "OD/PHVDD67S7FI2K5QQMPSOFSK",
  99. } {
  100. if _, err := decryptName(c, key); err == nil {
  101. t.Errorf("no error for %q", c)
  102. }
  103. }
  104. }
  105. func TestEnDecryptBytes(t *testing.T) {
  106. var key [32]byte
  107. cases := [][]byte{
  108. {},
  109. {1, 2, 3, 4, 5},
  110. }
  111. for _, tc := range cases {
  112. var prev []byte
  113. for i := 0; i < 5; i++ {
  114. enc := encryptBytes(tc, &key)
  115. if bytes.Equal(enc, prev) {
  116. t.Error("encryption should not repeat")
  117. }
  118. prev = enc
  119. if len(tc) > 0 && bytes.Contains(enc, tc) {
  120. t.Error("shouldn't contain plaintext")
  121. }
  122. dec, err := DecryptBytes(enc, &key)
  123. if err != nil {
  124. t.Error(err)
  125. }
  126. if !bytes.Equal(dec, tc) {
  127. t.Error("mismatch after decryption")
  128. }
  129. }
  130. }
  131. }
  132. func encFileInfo() FileInfo {
  133. return FileInfo{
  134. Name: "hello",
  135. Size: 45,
  136. Permissions: 0o755,
  137. ModifiedS: 8080,
  138. Sequence: 1000,
  139. Blocks: []BlockInfo{
  140. {
  141. Offset: 0,
  142. Size: 45,
  143. Hash: []byte{1, 2, 3},
  144. },
  145. {
  146. Offset: 45,
  147. Size: 45,
  148. Hash: []byte{1, 2, 3},
  149. },
  150. },
  151. }
  152. }
  153. func TestEnDecryptFileInfo(t *testing.T) {
  154. if cryptoIsBrokenUnderRaceDetector {
  155. t.Skip("cannot test")
  156. }
  157. var key [32]byte
  158. fi := encFileInfo()
  159. enc := encryptFileInfo(testKeyGen, fi, &key)
  160. if bytes.Equal(enc.Blocks[0].Hash, enc.Blocks[1].Hash) {
  161. t.Error("block hashes should not repeat when on different offsets")
  162. }
  163. if enc.RawBlockSize < MinBlockSize {
  164. t.Error("Too small raw block size:", enc.RawBlockSize)
  165. }
  166. if enc.Sequence != fi.Sequence {
  167. t.Error("encrypted fileinfo didn't maintain sequence number")
  168. }
  169. again := encryptFileInfo(testKeyGen, fi, &key)
  170. if !bytes.Equal(enc.Blocks[0].Hash, again.Blocks[0].Hash) {
  171. t.Error("block hashes should remain stable (0)")
  172. }
  173. if !bytes.Equal(enc.Blocks[1].Hash, again.Blocks[1].Hash) {
  174. t.Error("block hashes should remain stable (1)")
  175. }
  176. // Simulate the remote setting the sequence number when writing to db
  177. enc.Sequence = 10
  178. dec, err := DecryptFileInfo(testKeyGen, enc, &key)
  179. if err != nil {
  180. t.Error(err)
  181. }
  182. if dec.Sequence != enc.Sequence {
  183. t.Error("decrypted fileinfo didn't maintain sequence number")
  184. }
  185. dec.Sequence = fi.Sequence
  186. if !reflect.DeepEqual(fi, dec) {
  187. t.Error("mismatch after decryption")
  188. }
  189. }
  190. func TestEncryptedFileInfoConsistency(t *testing.T) {
  191. if cryptoIsBrokenUnderRaceDetector {
  192. t.Skip("cannot test")
  193. }
  194. var key [32]byte
  195. files := []FileInfo{
  196. encFileInfo(),
  197. encFileInfo(),
  198. }
  199. files[1].SetIgnored()
  200. for i, f := range files {
  201. enc := encryptFileInfo(testKeyGen, f, &key)
  202. if err := checkFileInfoConsistency(enc); err != nil {
  203. t.Errorf("%v: %v", i, err)
  204. }
  205. }
  206. }
  207. func TestIsEncryptedParent(t *testing.T) {
  208. comp := rand.String(maxPathComponent)
  209. cases := []struct {
  210. path string
  211. is bool
  212. }{
  213. {"", false},
  214. {".", false},
  215. {"/", false},
  216. {"12" + encryptedDirExtension, false},
  217. {"1" + encryptedDirExtension, true},
  218. {"1" + encryptedDirExtension + "/b", false},
  219. {"1" + encryptedDirExtension + "/bc", true},
  220. {"1" + encryptedDirExtension + "/bcd", false},
  221. {"1" + encryptedDirExtension + "/bc/foo", false},
  222. {"1.12/22", false},
  223. {"1" + encryptedDirExtension + "/bc/" + comp, true},
  224. {"1" + encryptedDirExtension + "/bc/" + comp + "/" + comp, true},
  225. {"1" + encryptedDirExtension + "/bc/" + comp + "a", false},
  226. {"1" + encryptedDirExtension + "/bc/" + comp + "/a/" + comp, false},
  227. }
  228. for _, tc := range cases {
  229. if res := IsEncryptedParent(strings.Split(tc.path, "/")); res != tc.is {
  230. t.Errorf("%v: got %v, expected %v", tc.path, res, tc.is)
  231. }
  232. }
  233. }