amneziawg-obfuscation.test.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { describe, expect, it } from 'vitest';
  2. import { generateAwgObfuscation } from '@/lib/xray/amneziawg-obfuscation';
  3. import { AmneziawgServerSchema } from '@/schemas/protocols/inbound/amneziawg';
  4. import { ServerSettingsSchema } from '@/generated/zod';
  5. /*
  6. * Parses "lo-hi" and asserts min <= lo <= hi <= max; mirrors the bounds the
  7. * Go generator's own test pins (internal/amneziawg/params_test.go), so the
  8. * two generators cannot drift apart silently.
  9. */
  10. function expectRangeWithin(value: string, min: number, max: number): [number, number] {
  11. const m = /^(\d+)-(\d+)$/.exec(value);
  12. expect(m, `${value} is not a lo-hi range`).not.toBeNull();
  13. const lo = Number(m![1]);
  14. const hi = Number(m![2]);
  15. expect(lo).toBeGreaterThanOrEqual(min);
  16. expect(hi).toBeLessThanOrEqual(max);
  17. expect(lo).toBeLessThanOrEqual(hi);
  18. return [lo, hi];
  19. }
  20. /* Parses a plain integer and asserts min <= n <= max (see expectRangeWithin above for the range form). */
  21. function expectIntWithin(value: string, min: number, max: number): number {
  22. const m = /^(\d+)$/.exec(value);
  23. expect(m, `${value} is not a plain integer`).not.toBeNull();
  24. const n = Number(m![1]);
  25. expect(n).toBeGreaterThanOrEqual(min);
  26. expect(n).toBeLessThanOrEqual(max);
  27. return n;
  28. }
  29. describe('generateAwgObfuscation', () => {
  30. it('stays inside the Go generator ranges and invariants', () => {
  31. for (let i = 0; i < 200; i++) {
  32. const o = generateAwgObfuscation();
  33. expect(o.jc).toBeGreaterThanOrEqual(3);
  34. expect(o.jc).toBeLessThanOrEqual(6);
  35. expect(o.jmin).toBeGreaterThanOrEqual(40);
  36. expect(o.jmin).toBeLessThanOrEqual(89);
  37. expect(o.jmax - o.jmin).toBeGreaterThanOrEqual(50);
  38. expect(o.jmax - o.jmin).toBeLessThanOrEqual(250);
  39. expect(o.s1 + 56).not.toBe(o.s2);
  40. expect(o.s3).toBeGreaterThanOrEqual(12);
  41. expect(o.s3).toBeLessThanOrEqual(55);
  42. expect(o.s4).toBeGreaterThanOrEqual(12);
  43. expect(o.s4).toBeLessThanOrEqual(27);
  44. const hValues = [o.h1, o.h2, o.h3, o.h4].map((h) => expectIntWithin(h, 5, 2147483647));
  45. for (let j = 1; j < 4; j++) {
  46. expect(hValues[j], 'H values must be strictly increasing across bands').toBeGreaterThan(
  47. hValues[j - 1],
  48. );
  49. }
  50. expect(o.i1).toMatch(/^<r \d+>$/);
  51. expect(o.i2).toBe('');
  52. expect(o.i5).toBe('');
  53. const key = atob(o.headerProtectionKey);
  54. expect(key.length, 'headerProtectionKey must decode to 32 bytes').toBe(32);
  55. expectRangeWithin(o.contentPaddingAddition, 8, 64);
  56. const [, rekeyHi] = expectRangeWithin(o.rekeyAfterTime, 100, 160);
  57. const [rejectLo] = expectRangeWithin(o.rejectAfterTime, 130, 310);
  58. expect(
  59. rejectLo,
  60. 'reject window must start >= 30s above the rekey window',
  61. ).toBeGreaterThanOrEqual(rekeyHi + 30);
  62. expectRangeWithin(o.rekeyTimeout, 3, 10);
  63. expectRangeWithin(o.keepaliveTimeout, 8, 20);
  64. expectRangeWithin(o.maxHandshakeAttempts, 15, 50);
  65. expect(o.randomTrailers).toBe(true);
  66. expect(o.disableCookies).toBe(true);
  67. }
  68. });
  69. it('produces values the hand-written schema accepts unchanged', () => {
  70. const parsed = AmneziawgServerSchema.parse({
  71. ...generateAwgObfuscation(),
  72. privateKey: 'p',
  73. publicKey: 'P',
  74. });
  75. expect(parsed.headerProtectionKey).not.toBe('');
  76. });
  77. });
  78. /*
  79. * Drift guard for the three-way mirror: the hand-written AmneziawgServerSchema,
  80. * the Go ServerSettings struct, and the openapigen output must agree on the
  81. * field set. Comparing hand-written vs generated keys catches a field added on
  82. * one side but forgotten on the other before it silently drops from configs.
  83. */
  84. describe('AmneziawgServerSchema parity with generated ServerSettings', () => {
  85. it('declares exactly the generated key set', () => {
  86. const handwritten = Object.keys(AmneziawgServerSchema.shape).sort();
  87. const generated = Object.keys(ServerSettingsSchema.shape).sort();
  88. expect(handwritten).toEqual(generated);
  89. });
  90. });