@push.rocks/smartproxy 12.0.0 → 13.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/certificate/acme/acme-factory.d.ts +17 -0
  3. package/dist_ts/certificate/acme/acme-factory.js +40 -0
  4. package/dist_ts/certificate/acme/challenge-handler.d.ts +44 -0
  5. package/dist_ts/certificate/acme/challenge-handler.js +92 -0
  6. package/dist_ts/certificate/acme/index.d.ts +4 -0
  7. package/dist_ts/certificate/acme/index.js +5 -0
  8. package/dist_ts/certificate/events/certificate-events.d.ts +33 -0
  9. package/dist_ts/certificate/events/certificate-events.js +38 -0
  10. package/dist_ts/certificate/index.d.ts +24 -0
  11. package/dist_ts/certificate/index.js +39 -0
  12. package/dist_ts/certificate/models/certificate-types.d.ts +77 -0
  13. package/dist_ts/certificate/models/certificate-types.js +2 -0
  14. package/dist_ts/certificate/providers/cert-provisioner.d.ts +93 -0
  15. package/dist_ts/certificate/providers/cert-provisioner.js +262 -0
  16. package/dist_ts/certificate/providers/index.d.ts +4 -0
  17. package/dist_ts/certificate/providers/index.js +5 -0
  18. package/dist_ts/certificate/storage/file-storage.d.ts +66 -0
  19. package/dist_ts/certificate/storage/file-storage.js +194 -0
  20. package/dist_ts/certificate/storage/index.d.ts +4 -0
  21. package/dist_ts/certificate/storage/index.js +5 -0
  22. package/dist_ts/certificate/utils/certificate-helpers.d.ts +17 -0
  23. package/dist_ts/certificate/utils/certificate-helpers.js +45 -0
  24. package/dist_ts/common/eventUtils.d.ts +1 -1
  25. package/dist_ts/common/port80-adapter.d.ts +1 -1
  26. package/dist_ts/core/events/index.d.ts +4 -0
  27. package/dist_ts/core/events/index.js +5 -0
  28. package/dist_ts/core/index.d.ts +6 -0
  29. package/dist_ts/core/index.js +8 -0
  30. package/dist_ts/core/models/common-types.d.ts +82 -0
  31. package/dist_ts/core/models/common-types.js +15 -0
  32. package/dist_ts/core/models/index.d.ts +4 -0
  33. package/dist_ts/core/models/index.js +5 -0
  34. package/dist_ts/core/utils/event-utils.d.ts +15 -0
  35. package/dist_ts/core/utils/event-utils.js +19 -0
  36. package/dist_ts/core/utils/index.d.ts +6 -0
  37. package/dist_ts/core/utils/index.js +7 -0
  38. package/dist_ts/core/utils/ip-utils.d.ts +53 -0
  39. package/dist_ts/core/utils/ip-utils.js +153 -0
  40. package/dist_ts/core/utils/validation-utils.d.ts +61 -0
  41. package/dist_ts/core/utils/validation-utils.js +149 -0
  42. package/dist_ts/forwarding/config/domain-config.d.ts +12 -0
  43. package/dist_ts/forwarding/config/domain-config.js +12 -0
  44. package/dist_ts/forwarding/config/domain-manager.d.ts +86 -0
  45. package/dist_ts/forwarding/config/domain-manager.js +242 -0
  46. package/dist_ts/forwarding/config/forwarding-types.d.ts +104 -0
  47. package/dist_ts/forwarding/config/forwarding-types.js +50 -0
  48. package/dist_ts/forwarding/config/index.d.ts +6 -0
  49. package/dist_ts/forwarding/config/index.js +7 -0
  50. package/dist_ts/forwarding/factory/forwarding-factory.d.ts +25 -0
  51. package/dist_ts/forwarding/factory/forwarding-factory.js +138 -0
  52. package/dist_ts/forwarding/factory/index.d.ts +4 -0
  53. package/dist_ts/forwarding/factory/index.js +5 -0
  54. package/dist_ts/forwarding/handlers/base-handler.d.ts +55 -0
  55. package/dist_ts/forwarding/handlers/base-handler.js +94 -0
  56. package/dist_ts/forwarding/handlers/http-handler.d.ts +30 -0
  57. package/dist_ts/forwarding/handlers/http-handler.js +131 -0
  58. package/dist_ts/forwarding/handlers/https-passthrough-handler.d.ts +29 -0
  59. package/dist_ts/forwarding/handlers/https-passthrough-handler.js +162 -0
  60. package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.d.ts +36 -0
  61. package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.js +229 -0
  62. package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.d.ts +35 -0
  63. package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.js +254 -0
  64. package/dist_ts/forwarding/handlers/index.d.ts +8 -0
  65. package/dist_ts/forwarding/handlers/index.js +9 -0
  66. package/dist_ts/forwarding/index.d.ts +19 -0
  67. package/dist_ts/forwarding/index.js +25 -0
  68. package/dist_ts/http/index.d.ts +15 -0
  69. package/dist_ts/http/index.js +20 -0
  70. package/dist_ts/http/models/http-types.d.ts +81 -0
  71. package/dist_ts/http/models/http-types.js +62 -0
  72. package/dist_ts/http/port80/acme-interfaces.d.ts +78 -0
  73. package/dist_ts/http/port80/acme-interfaces.js +6 -0
  74. package/dist_ts/http/port80/challenge-responder.d.ts +53 -0
  75. package/dist_ts/http/port80/challenge-responder.js +203 -0
  76. package/dist_ts/http/port80/index.d.ts +6 -0
  77. package/dist_ts/http/port80/index.js +9 -0
  78. package/dist_ts/http/port80/port80-handler.d.ts +121 -0
  79. package/dist_ts/http/port80/port80-handler.js +554 -0
  80. package/dist_ts/http/redirects/index.d.ts +4 -0
  81. package/dist_ts/http/redirects/index.js +5 -0
  82. package/dist_ts/http/router/index.d.ts +4 -0
  83. package/dist_ts/http/router/index.js +5 -0
  84. package/dist_ts/http/router/proxy-router.d.ts +115 -0
  85. package/dist_ts/http/router/proxy-router.js +325 -0
  86. package/dist_ts/index.d.ts +15 -8
  87. package/dist_ts/index.js +26 -10
  88. package/dist_ts/networkproxy/classes.np.certificatemanager.js +2 -2
  89. package/dist_ts/networkproxy/index.d.ts +1 -6
  90. package/dist_ts/networkproxy/index.js +4 -8
  91. package/dist_ts/plugins.d.ts +2 -1
  92. package/dist_ts/plugins.js +3 -2
  93. package/dist_ts/port80handler/classes.port80handler.d.ts +8 -136
  94. package/dist_ts/port80handler/classes.port80handler.js +14 -567
  95. package/dist_ts/proxies/index.d.ts +6 -0
  96. package/dist_ts/proxies/index.js +8 -0
  97. package/dist_ts/proxies/network-proxy/certificate-manager.d.ts +77 -0
  98. package/dist_ts/proxies/network-proxy/certificate-manager.js +373 -0
  99. package/dist_ts/proxies/network-proxy/connection-pool.d.ts +47 -0
  100. package/dist_ts/proxies/network-proxy/connection-pool.js +210 -0
  101. package/dist_ts/proxies/network-proxy/index.d.ts +10 -0
  102. package/dist_ts/proxies/network-proxy/index.js +12 -0
  103. package/dist_ts/proxies/network-proxy/models/index.d.ts +4 -0
  104. package/dist_ts/proxies/network-proxy/models/index.js +5 -0
  105. package/dist_ts/proxies/network-proxy/models/types.d.ts +80 -0
  106. package/dist_ts/proxies/network-proxy/models/types.js +35 -0
  107. package/dist_ts/proxies/network-proxy/network-proxy.d.ts +118 -0
  108. package/dist_ts/proxies/network-proxy/network-proxy.js +387 -0
  109. package/dist_ts/proxies/network-proxy/request-handler.d.ts +57 -0
  110. package/dist_ts/proxies/network-proxy/request-handler.js +394 -0
  111. package/dist_ts/proxies/network-proxy/websocket-handler.d.ts +38 -0
  112. package/dist_ts/proxies/network-proxy/websocket-handler.js +188 -0
  113. package/dist_ts/proxies/nftables-proxy/index.d.ts +5 -0
  114. package/dist_ts/proxies/nftables-proxy/index.js +6 -0
  115. package/dist_ts/proxies/nftables-proxy/models/errors.d.ts +15 -0
  116. package/dist_ts/proxies/nftables-proxy/models/errors.js +28 -0
  117. package/dist_ts/proxies/nftables-proxy/models/index.d.ts +5 -0
  118. package/dist_ts/proxies/nftables-proxy/models/index.js +6 -0
  119. package/dist_ts/proxies/nftables-proxy/models/interfaces.d.ts +75 -0
  120. package/dist_ts/proxies/nftables-proxy/models/interfaces.js +5 -0
  121. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +136 -0
  122. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +1516 -0
  123. package/dist_ts/proxies/smart-proxy/connection-handler.d.ts +39 -0
  124. package/dist_ts/proxies/smart-proxy/connection-handler.js +894 -0
  125. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +78 -0
  126. package/dist_ts/proxies/smart-proxy/connection-manager.js +378 -0
  127. package/dist_ts/proxies/smart-proxy/domain-config-manager.d.ts +95 -0
  128. package/dist_ts/proxies/smart-proxy/domain-config-manager.js +255 -0
  129. package/dist_ts/proxies/smart-proxy/index.d.ts +13 -0
  130. package/dist_ts/proxies/smart-proxy/index.js +17 -0
  131. package/dist_ts/proxies/smart-proxy/models/index.d.ts +4 -0
  132. package/dist_ts/proxies/smart-proxy/models/index.js +5 -0
  133. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +107 -0
  134. package/dist_ts/proxies/smart-proxy/models/interfaces.js +2 -0
  135. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +62 -0
  136. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +316 -0
  137. package/dist_ts/proxies/smart-proxy/port-range-manager.d.ts +56 -0
  138. package/dist_ts/proxies/smart-proxy/port-range-manager.js +176 -0
  139. package/dist_ts/proxies/smart-proxy/security-manager.d.ts +64 -0
  140. package/dist_ts/proxies/smart-proxy/security-manager.js +149 -0
  141. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +63 -0
  142. package/dist_ts/proxies/smart-proxy/smart-proxy.js +523 -0
  143. package/dist_ts/proxies/smart-proxy/timeout-manager.d.ts +47 -0
  144. package/dist_ts/proxies/smart-proxy/timeout-manager.js +154 -0
  145. package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +57 -0
  146. package/dist_ts/proxies/smart-proxy/tls-manager.js +132 -0
  147. package/dist_ts/smartproxy/classes.pp.networkproxybridge.d.ts +2 -2
  148. package/dist_ts/smartproxy/classes.pp.networkproxybridge.js +1 -1
  149. package/dist_ts/smartproxy/classes.pp.tlsmanager.js +2 -2
  150. package/dist_ts/smartproxy/classes.smartproxy.js +3 -3
  151. package/dist_ts/tls/alerts/index.d.ts +4 -0
  152. package/dist_ts/tls/alerts/index.js +5 -0
  153. package/dist_ts/tls/alerts/tls-alert.d.ts +150 -0
  154. package/dist_ts/tls/alerts/tls-alert.js +226 -0
  155. package/dist_ts/tls/index.d.ts +18 -0
  156. package/dist_ts/tls/index.js +27 -0
  157. package/dist_ts/tls/sni/client-hello-parser.d.ts +100 -0
  158. package/dist_ts/tls/sni/client-hello-parser.js +463 -0
  159. package/dist_ts/tls/sni/index.d.ts +4 -0
  160. package/dist_ts/tls/sni/index.js +5 -0
  161. package/dist_ts/tls/sni/sni-extraction.d.ts +58 -0
  162. package/dist_ts/tls/sni/sni-extraction.js +275 -0
  163. package/dist_ts/tls/sni/sni-handler.d.ts +154 -0
  164. package/dist_ts/tls/sni/sni-handler.js +191 -0
  165. package/dist_ts/tls/utils/index.d.ts +4 -0
  166. package/dist_ts/tls/utils/index.js +5 -0
  167. package/dist_ts/tls/utils/tls-utils.d.ts +158 -0
  168. package/dist_ts/tls/utils/tls-utils.js +187 -0
  169. package/package.json +1 -1
  170. package/readme.md +89 -21
  171. package/readme.plan.md +253 -469
  172. package/ts/00_commitinfo_data.ts +1 -1
  173. package/ts/certificate/acme/acme-factory.ts +48 -0
  174. package/ts/certificate/acme/challenge-handler.ts +110 -0
  175. package/ts/certificate/acme/index.ts +3 -0
  176. package/ts/certificate/events/certificate-events.ts +36 -0
  177. package/ts/certificate/index.ts +67 -0
  178. package/ts/certificate/models/certificate-types.ts +88 -0
  179. package/ts/certificate/providers/cert-provisioner.ts +326 -0
  180. package/ts/certificate/providers/index.ts +3 -0
  181. package/ts/certificate/storage/file-storage.ts +234 -0
  182. package/ts/certificate/storage/index.ts +3 -0
  183. package/ts/certificate/utils/certificate-helpers.ts +50 -0
  184. package/ts/common/eventUtils.ts +1 -1
  185. package/ts/common/port80-adapter.ts +1 -1
  186. package/ts/core/events/index.ts +3 -0
  187. package/ts/core/index.ts +8 -0
  188. package/ts/core/models/common-types.ts +91 -0
  189. package/ts/core/models/index.ts +5 -0
  190. package/ts/core/utils/event-utils.ts +34 -0
  191. package/ts/core/utils/index.ts +7 -0
  192. package/ts/core/utils/ip-utils.ts +175 -0
  193. package/ts/core/utils/validation-utils.ts +177 -0
  194. package/ts/{smartproxy/forwarding → forwarding/config}/domain-config.ts +1 -1
  195. package/ts/{smartproxy/forwarding → forwarding/config}/domain-manager.ts +8 -8
  196. package/ts/{smartproxy/types/forwarding.types.ts → forwarding/config/forwarding-types.ts} +6 -6
  197. package/ts/forwarding/config/index.ts +7 -0
  198. package/ts/{smartproxy/forwarding/forwarding.factory.ts → forwarding/factory/forwarding-factory.ts} +12 -11
  199. package/ts/forwarding/factory/index.ts +5 -0
  200. package/ts/{smartproxy/forwarding/forwarding.handler.ts → forwarding/handlers/base-handler.ts} +2 -2
  201. package/ts/{smartproxy/forwarding/http.handler.ts → forwarding/handlers/http-handler.ts} +13 -4
  202. package/ts/{smartproxy/forwarding/https-passthrough.handler.ts → forwarding/handlers/https-passthrough-handler.ts} +13 -4
  203. package/ts/{smartproxy/forwarding/https-terminate-to-http.handler.ts → forwarding/handlers/https-terminate-to-http-handler.ts} +3 -3
  204. package/ts/{smartproxy/forwarding/https-terminate-to-https.handler.ts → forwarding/handlers/https-terminate-to-https-handler.ts} +3 -3
  205. package/ts/forwarding/handlers/index.ts +9 -0
  206. package/ts/forwarding/index.ts +34 -0
  207. package/ts/http/index.ts +23 -0
  208. package/ts/http/models/http-types.ts +105 -0
  209. package/ts/http/port80/acme-interfaces.ts +85 -0
  210. package/ts/http/port80/challenge-responder.ts +246 -0
  211. package/ts/http/port80/index.ts +13 -0
  212. package/ts/{port80handler/classes.port80handler.ts → http/port80/port80-handler.ts} +164 -161
  213. package/ts/http/redirects/index.ts +3 -0
  214. package/ts/http/router/index.ts +5 -0
  215. package/ts/{classes.router.ts → http/router/proxy-router.ts} +27 -20
  216. package/ts/index.ts +32 -9
  217. package/ts/plugins.ts +2 -1
  218. package/ts/proxies/index.ts +8 -0
  219. package/ts/{networkproxy/classes.np.certificatemanager.ts → proxies/network-proxy/certificate-manager.ts} +17 -16
  220. package/ts/{networkproxy/classes.np.connectionpool.ts → proxies/network-proxy/connection-pool.ts} +3 -3
  221. package/ts/proxies/network-proxy/index.ts +13 -0
  222. package/ts/proxies/network-proxy/models/index.ts +4 -0
  223. package/ts/{networkproxy/classes.np.types.ts → proxies/network-proxy/models/types.ts} +7 -11
  224. package/ts/{networkproxy/classes.np.networkproxy.ts → proxies/network-proxy/network-proxy.ts} +31 -24
  225. package/ts/{networkproxy/classes.np.requesthandler.ts → proxies/network-proxy/request-handler.ts} +12 -7
  226. package/ts/{networkproxy/classes.np.websockethandler.ts → proxies/network-proxy/websocket-handler.ts} +6 -6
  227. package/ts/proxies/nftables-proxy/index.ts +5 -0
  228. package/ts/proxies/nftables-proxy/models/errors.ts +30 -0
  229. package/ts/proxies/nftables-proxy/models/index.ts +5 -0
  230. package/ts/proxies/nftables-proxy/models/interfaces.ts +94 -0
  231. package/ts/{nfttablesproxy/classes.nftablesproxy.ts → proxies/nftables-proxy/nftables-proxy.ts} +24 -126
  232. package/ts/{smartproxy/classes.pp.connectionhandler.ts → proxies/smart-proxy/connection-handler.ts} +12 -12
  233. package/ts/{smartproxy/classes.pp.connectionmanager.ts → proxies/smart-proxy/connection-manager.ts} +8 -8
  234. package/ts/{smartproxy/classes.pp.domainconfigmanager.ts → proxies/smart-proxy/domain-config-manager.ts} +15 -14
  235. package/ts/proxies/smart-proxy/index.ts +18 -0
  236. package/ts/proxies/smart-proxy/models/index.ts +4 -0
  237. package/ts/{smartproxy/classes.pp.interfaces.ts → proxies/smart-proxy/models/interfaces.ts} +12 -8
  238. package/ts/{smartproxy/classes.pp.networkproxybridge.ts → proxies/smart-proxy/network-proxy-bridge.ts} +14 -14
  239. package/ts/{smartproxy/classes.pp.portrangemanager.ts → proxies/smart-proxy/port-range-manager.ts} +1 -1
  240. package/ts/{smartproxy/classes.pp.securitymanager.ts → proxies/smart-proxy/security-manager.ts} +3 -3
  241. package/ts/{smartproxy/classes.smartproxy.ts → proxies/smart-proxy/smart-proxy.ts} +29 -24
  242. package/ts/{smartproxy/classes.pp.timeoutmanager.ts → proxies/smart-proxy/timeout-manager.ts} +3 -3
  243. package/ts/{smartproxy/classes.pp.tlsmanager.ts → proxies/smart-proxy/tls-manager.ts} +3 -3
  244. package/ts/tls/alerts/index.ts +3 -0
  245. package/ts/{smartproxy/classes.pp.tlsalert.ts → tls/alerts/tls-alert.ts} +44 -43
  246. package/ts/tls/index.ts +33 -0
  247. package/ts/tls/sni/client-hello-parser.ts +629 -0
  248. package/ts/tls/sni/index.ts +3 -0
  249. package/ts/tls/sni/sni-extraction.ts +353 -0
  250. package/ts/tls/sni/sni-handler.ts +264 -0
  251. package/ts/tls/utils/index.ts +3 -0
  252. package/ts/tls/utils/tls-utils.ts +201 -0
  253. package/ts/common/acmeFactory.ts +0 -23
  254. package/ts/helpers.certificates.ts +0 -30
  255. package/ts/networkproxy/index.ts +0 -7
  256. package/ts/smartproxy/classes.pp.certprovisioner.ts +0 -200
  257. package/ts/smartproxy/classes.pp.snihandler.ts +0 -1281
  258. package/ts/smartproxy/forwarding/index.ts +0 -52
@@ -1,1281 +0,0 @@
1
- import { Buffer } from 'buffer';
2
-
3
- /**
4
- * SNI (Server Name Indication) handler for TLS connections.
5
- * Provides robust extraction of SNI values from TLS ClientHello messages
6
- * with support for fragmented packets, TLS 1.3 resumption, Chrome-specific
7
- * connection behaviors, and tab hibernation/reactivation scenarios.
8
- */
9
- export class SniHandler {
10
- // TLS record types and constants
11
- private static readonly TLS_HANDSHAKE_RECORD_TYPE = 22;
12
- private static readonly TLS_APPLICATION_DATA_TYPE = 23; // TLS Application Data record type
13
- private static readonly TLS_CLIENT_HELLO_HANDSHAKE_TYPE = 1;
14
- private static readonly TLS_SNI_EXTENSION_TYPE = 0x0000;
15
- private static readonly TLS_SESSION_TICKET_EXTENSION_TYPE = 0x0023;
16
- private static readonly TLS_SNI_HOST_NAME_TYPE = 0;
17
- private static readonly TLS_PSK_EXTENSION_TYPE = 0x0029; // Pre-Shared Key extension type for TLS 1.3
18
- private static readonly TLS_PSK_KE_MODES_EXTENSION_TYPE = 0x002d; // PSK Key Exchange Modes
19
- private static readonly TLS_EARLY_DATA_EXTENSION_TYPE = 0x002a; // Early Data (0-RTT) extension
20
-
21
- // Buffer for handling fragmented ClientHello messages
22
- private static fragmentedBuffers: Map<string, Buffer> = new Map();
23
- private static fragmentTimeout: number = 1000; // ms to wait for fragments before cleanup
24
-
25
- /**
26
- * Checks if a buffer contains a TLS handshake message (record type 22)
27
- * @param buffer - The buffer to check
28
- * @returns true if the buffer starts with a TLS handshake record type
29
- */
30
- public static isTlsHandshake(buffer: Buffer): boolean {
31
- return buffer.length > 0 && buffer[0] === this.TLS_HANDSHAKE_RECORD_TYPE;
32
- }
33
-
34
- /**
35
- * Checks if a buffer contains TLS application data (record type 23)
36
- * @param buffer - The buffer to check
37
- * @returns true if the buffer starts with a TLS application data record type
38
- */
39
- public static isTlsApplicationData(buffer: Buffer): boolean {
40
- return buffer.length > 0 && buffer[0] === this.TLS_APPLICATION_DATA_TYPE;
41
- }
42
-
43
- /**
44
- * Creates a connection ID based on source/destination information
45
- * Used to track fragmented ClientHello messages across multiple packets
46
- *
47
- * @param connectionInfo - Object containing connection identifiers (IP/port)
48
- * @returns A string ID for the connection
49
- */
50
- public static createConnectionId(connectionInfo: {
51
- sourceIp?: string;
52
- sourcePort?: number;
53
- destIp?: string;
54
- destPort?: number;
55
- }): string {
56
- const { sourceIp, sourcePort, destIp, destPort } = connectionInfo;
57
- return `${sourceIp}:${sourcePort}-${destIp}:${destPort}`;
58
- }
59
-
60
- /**
61
- * Handles potential fragmented ClientHello messages by buffering and reassembling
62
- * TLS record fragments that might span multiple TCP packets.
63
- *
64
- * @param buffer - The current buffer fragment
65
- * @param connectionId - Unique identifier for the connection
66
- * @param enableLogging - Whether to enable logging
67
- * @returns A complete buffer if reassembly is successful, or undefined if more fragments are needed
68
- */
69
- public static handleFragmentedClientHello(
70
- buffer: Buffer,
71
- connectionId: string,
72
- enableLogging: boolean = false
73
- ): Buffer | undefined {
74
- const log = (message: string) => {
75
- if (enableLogging) {
76
- console.log(`[SNI Fragment] ${message}`);
77
- }
78
- };
79
-
80
- // Check if we've seen this connection before
81
- if (!this.fragmentedBuffers.has(connectionId)) {
82
- // New connection, start with this buffer
83
- this.fragmentedBuffers.set(connectionId, buffer);
84
-
85
- // Set timeout to clean up if we don't get a complete ClientHello
86
- setTimeout(() => {
87
- if (this.fragmentedBuffers.has(connectionId)) {
88
- this.fragmentedBuffers.delete(connectionId);
89
- log(`Connection ${connectionId} timed out waiting for complete ClientHello`);
90
- }
91
- }, this.fragmentTimeout);
92
-
93
- // Evaluate if this buffer already contains a complete ClientHello
94
- try {
95
- if (buffer.length >= 5) {
96
- // Get the record length from TLS header
97
- const recordLength = (buffer[3] << 8) + buffer[4] + 5; // +5 for the TLS record header itself
98
- log(`Initial buffer size: ${buffer.length}, expected record length: ${recordLength}`);
99
-
100
- // Check if this buffer already contains a complete TLS record
101
- if (buffer.length >= recordLength) {
102
- log(`Initial buffer contains complete ClientHello, length: ${buffer.length}`);
103
- return buffer;
104
- }
105
- } else {
106
- log(
107
- `Initial buffer too small (${buffer.length} bytes), needs at least 5 bytes for TLS header`
108
- );
109
- }
110
- } catch (e) {
111
- log(`Error checking initial buffer completeness: ${e}`);
112
- }
113
-
114
- log(`Started buffering connection ${connectionId}, initial size: ${buffer.length}`);
115
- return undefined; // Need more fragments
116
- } else {
117
- // Existing connection, append this buffer
118
- const existingBuffer = this.fragmentedBuffers.get(connectionId)!;
119
- const newBuffer = Buffer.concat([existingBuffer, buffer]);
120
- this.fragmentedBuffers.set(connectionId, newBuffer);
121
-
122
- log(`Appended to buffer for ${connectionId}, new size: ${newBuffer.length}`);
123
-
124
- // Check if we now have a complete ClientHello
125
- try {
126
- if (newBuffer.length >= 5) {
127
- // Get the record length from TLS header
128
- const recordLength = (newBuffer[3] << 8) + newBuffer[4] + 5; // +5 for the TLS record header itself
129
- log(
130
- `Reassembled buffer size: ${newBuffer.length}, expected record length: ${recordLength}`
131
- );
132
-
133
- // Check if we have a complete TLS record now
134
- if (newBuffer.length >= recordLength) {
135
- log(
136
- `Assembled complete ClientHello, length: ${newBuffer.length}, needed: ${recordLength}`
137
- );
138
-
139
- // Extract the complete TLS record (might be followed by more data)
140
- const completeRecord = newBuffer.slice(0, recordLength);
141
-
142
- // Check if this record is indeed a ClientHello (type 1) at position 5
143
- if (
144
- completeRecord.length > 5 &&
145
- completeRecord[5] === this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE
146
- ) {
147
- log(`Verified record is a ClientHello handshake message`);
148
-
149
- // Complete message received, remove from tracking
150
- this.fragmentedBuffers.delete(connectionId);
151
- return completeRecord;
152
- } else {
153
- log(`Record is complete but not a ClientHello handshake, continuing to buffer`);
154
- // This might be another TLS record type preceding the ClientHello
155
-
156
- // Try checking for a ClientHello starting at the end of this record
157
- if (newBuffer.length > recordLength + 5) {
158
- const nextRecordType = newBuffer[recordLength];
159
- log(
160
- `Next record type: ${nextRecordType} (looking for ${this.TLS_HANDSHAKE_RECORD_TYPE})`
161
- );
162
-
163
- if (nextRecordType === this.TLS_HANDSHAKE_RECORD_TYPE) {
164
- const handshakeType = newBuffer[recordLength + 5];
165
- log(
166
- `Next handshake type: ${handshakeType} (looking for ${this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE})`
167
- );
168
-
169
- if (handshakeType === this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE) {
170
- // Found a ClientHello in the next record, return the entire buffer
171
- log(`Found ClientHello in subsequent record, returning full buffer`);
172
- this.fragmentedBuffers.delete(connectionId);
173
- return newBuffer;
174
- }
175
- }
176
- }
177
- }
178
- }
179
- }
180
- } catch (e) {
181
- log(`Error checking reassembled buffer completeness: ${e}`);
182
- }
183
-
184
- return undefined; // Still need more fragments
185
- }
186
- }
187
-
188
- /**
189
- * Checks if a buffer contains a TLS ClientHello message
190
- * @param buffer - The buffer to check
191
- * @returns true if the buffer appears to be a ClientHello message
192
- */
193
- public static isClientHello(buffer: Buffer): boolean {
194
- // Minimum ClientHello size (TLS record header + handshake header)
195
- if (buffer.length < 9) {
196
- return false;
197
- }
198
-
199
- // Check record type (must be TLS_HANDSHAKE_RECORD_TYPE)
200
- if (buffer[0] !== this.TLS_HANDSHAKE_RECORD_TYPE) {
201
- return false;
202
- }
203
-
204
- // Skip version and length in TLS record header (5 bytes total)
205
- // Check handshake type at byte 5 (must be CLIENT_HELLO)
206
- return buffer[5] === this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE;
207
- }
208
-
209
- /**
210
- * Checks if a ClientHello message contains session resumption indicators
211
- * such as session tickets or PSK (Pre-Shared Key) extensions.
212
- *
213
- * @param buffer - The buffer containing a ClientHello message
214
- * @param enableLogging - Whether to enable logging
215
- * @returns Object containing details about session resumption and SNI presence
216
- */
217
- public static hasSessionResumption(
218
- buffer: Buffer,
219
- enableLogging: boolean = false
220
- ): { isResumption: boolean; hasSNI: boolean } {
221
- const log = (message: string) => {
222
- if (enableLogging) {
223
- console.log(`[Session Resumption] ${message}`);
224
- }
225
- };
226
-
227
- if (!this.isClientHello(buffer)) {
228
- return { isResumption: false, hasSNI: false };
229
- }
230
-
231
- try {
232
- // Check for session ID presence first
233
- let pos = 5 + 1 + 3 + 2; // Position after handshake type, length and client version
234
- pos += 32; // Skip client random
235
-
236
- if (pos + 1 > buffer.length) return { isResumption: false, hasSNI: false };
237
-
238
- const sessionIdLength = buffer[pos];
239
- let hasNonEmptySessionId = sessionIdLength > 0;
240
-
241
- if (hasNonEmptySessionId) {
242
- log(`Detected non-empty session ID (length: ${sessionIdLength})`);
243
- }
244
-
245
- // Continue to check for extensions
246
- pos += 1 + sessionIdLength;
247
-
248
- // Skip cipher suites
249
- if (pos + 2 > buffer.length) return { isResumption: false, hasSNI: false };
250
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
251
- pos += 2 + cipherSuitesLength;
252
-
253
- // Skip compression methods
254
- if (pos + 1 > buffer.length) return { isResumption: false, hasSNI: false };
255
- const compressionMethodsLength = buffer[pos];
256
- pos += 1 + compressionMethodsLength;
257
-
258
- // Check for extensions
259
- if (pos + 2 > buffer.length) return { isResumption: false, hasSNI: false };
260
-
261
- // Look for session resumption extensions
262
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
263
- pos += 2;
264
-
265
- // Extensions end position
266
- const extensionsEnd = pos + extensionsLength;
267
- if (extensionsEnd > buffer.length) return { isResumption: false, hasSNI: false };
268
-
269
- // Track resumption indicators
270
- let hasSessionTicket = false;
271
- let hasPSK = false;
272
- let hasEarlyData = false;
273
-
274
- // Iterate through extensions
275
- while (pos + 4 <= extensionsEnd) {
276
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
277
- pos += 2;
278
-
279
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
280
- pos += 2;
281
-
282
- if (extensionType === this.TLS_SESSION_TICKET_EXTENSION_TYPE) {
283
- log('Found session ticket extension');
284
- hasSessionTicket = true;
285
-
286
- // Check if session ticket has non-zero length (active ticket)
287
- if (extensionLength > 0) {
288
- log(`Session ticket has length ${extensionLength} - active ticket present`);
289
- }
290
- } else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
291
- log('Found PSK extension (TLS 1.3 resumption mechanism)');
292
- hasPSK = true;
293
- } else if (extensionType === this.TLS_EARLY_DATA_EXTENSION_TYPE) {
294
- log('Found Early Data extension (TLS 1.3 0-RTT)');
295
- hasEarlyData = true;
296
- }
297
-
298
- // Skip extension data
299
- pos += extensionLength;
300
- }
301
-
302
- // Check if SNI is included
303
- let hasSNI = false;
304
-
305
- // Reset position and scan again for SNI extension
306
- pos = 5 + 1 + 3 + 2; // Reset to after handshake type, length and client version
307
- pos += 32; // Skip client random
308
-
309
- if (pos + 1 <= buffer.length) {
310
- const sessionIdLength = buffer[pos];
311
- pos += 1 + sessionIdLength;
312
-
313
- // Skip cipher suites
314
- if (pos + 2 <= buffer.length) {
315
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
316
- pos += 2 + cipherSuitesLength;
317
-
318
- // Skip compression methods
319
- if (pos + 1 <= buffer.length) {
320
- const compressionMethodsLength = buffer[pos];
321
- pos += 1 + compressionMethodsLength;
322
-
323
- // Check for extensions
324
- if (pos + 2 <= buffer.length) {
325
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
326
- pos += 2;
327
-
328
- // Extensions end position
329
- const extensionsEnd = pos + extensionsLength;
330
- if (extensionsEnd <= buffer.length) {
331
- // Scan for SNI extension
332
- while (pos + 4 <= extensionsEnd) {
333
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
334
- pos += 2;
335
-
336
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
337
- pos += 2;
338
-
339
- if (extensionType === this.TLS_SNI_EXTENSION_TYPE) {
340
- // Check that the SNI extension actually has content
341
- if (extensionLength > 0) {
342
- hasSNI = true;
343
-
344
- // Try to extract the actual SNI value for logging
345
- try {
346
- // Skip to server_name_list_length (2 bytes)
347
- const tempPos = pos;
348
- if (tempPos + 2 <= extensionsEnd) {
349
- const nameListLength = (buffer[tempPos] << 8) + buffer[tempPos + 1];
350
-
351
- // Skip server_name_list_length (2 bytes)
352
- if (tempPos + 2 + 1 <= extensionsEnd) {
353
- // Check name_type (should be 0 for hostname)
354
- if (buffer[tempPos + 2] === 0) {
355
- // Skip name_type (1 byte)
356
- if (tempPos + 3 + 2 <= extensionsEnd) {
357
- // Get name_length (2 bytes)
358
- const nameLength = (buffer[tempPos + 3] << 8) + buffer[tempPos + 4];
359
-
360
- // Extract the hostname
361
- if (tempPos + 5 + nameLength <= extensionsEnd) {
362
- const hostname = buffer
363
- .slice(tempPos + 5, tempPos + 5 + nameLength)
364
- .toString('utf8');
365
- log(`Found SNI extension with server_name: ${hostname}`);
366
- }
367
- }
368
- }
369
- }
370
- }
371
- } catch (e) {
372
- log(`Error extracting SNI value: ${e}`);
373
- log('Found SNI extension with length: ' + extensionLength);
374
- }
375
- } else {
376
- log('Found empty SNI extension, treating as no SNI');
377
- }
378
- break;
379
- }
380
-
381
- // Skip extension data
382
- pos += extensionLength;
383
- }
384
- }
385
- }
386
- }
387
- }
388
- }
389
-
390
- // Consider it a resumption if any resumption mechanism is present
391
- const isResumption =
392
- hasSessionTicket || hasPSK || hasEarlyData || (hasNonEmptySessionId && !hasPSK); // Legacy resumption
393
-
394
- if (isResumption) {
395
- log(
396
- 'Session resumption detected: ' +
397
- (hasSessionTicket ? 'session ticket, ' : '') +
398
- (hasPSK ? 'PSK, ' : '') +
399
- (hasEarlyData ? 'early data, ' : '') +
400
- (hasNonEmptySessionId ? 'session ID' : '') +
401
- (hasSNI ? ', with SNI' : ', without SNI')
402
- );
403
- }
404
-
405
- // Return an object with both flags
406
- // For clarity: connections should be blocked if they have session resumption without SNI
407
- if (isResumption) {
408
- log(
409
- `Resumption summary - hasSNI: ${hasSNI ? 'yes' : 'no'}, resumption type: ${
410
- hasSessionTicket ? 'session ticket, ' : ''
411
- }${hasPSK ? 'PSK, ' : ''}${hasEarlyData ? 'early data, ' : ''}${
412
- hasNonEmptySessionId ? 'session ID' : ''
413
- }`
414
- );
415
- }
416
-
417
- return {
418
- isResumption,
419
- hasSNI,
420
- };
421
- } catch (error) {
422
- log(`Error checking for session resumption: ${error}`);
423
- return { isResumption: false, hasSNI: false };
424
- }
425
- }
426
-
427
- /**
428
- * Detects characteristics of a tab reactivation TLS handshake
429
- * These often have specific patterns in Chrome and other browsers
430
- *
431
- * @param buffer - The buffer containing a ClientHello message
432
- * @param enableLogging - Whether to enable logging
433
- * @returns true if this appears to be a tab reactivation handshake
434
- */
435
- public static isTabReactivationHandshake(
436
- buffer: Buffer,
437
- enableLogging: boolean = false
438
- ): boolean {
439
- const log = (message: string) => {
440
- if (enableLogging) {
441
- console.log(`[Tab Reactivation] ${message}`);
442
- }
443
- };
444
-
445
- if (!this.isClientHello(buffer)) {
446
- return false;
447
- }
448
-
449
- try {
450
- // Check for session ID presence (tab reactivation often has a session ID)
451
- let pos = 5 + 1 + 3 + 2; // Position after handshake type, length and client version
452
- pos += 32; // Skip client random
453
-
454
- if (pos + 1 > buffer.length) return false;
455
-
456
- const sessionIdLength = buffer[pos];
457
-
458
- // Non-empty session ID is a good indicator
459
- if (sessionIdLength > 0) {
460
- log(`Detected non-empty session ID (length: ${sessionIdLength})`);
461
-
462
- // Skip to extensions
463
- pos += 1 + sessionIdLength;
464
-
465
- // Skip cipher suites
466
- if (pos + 2 > buffer.length) return false;
467
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
468
- pos += 2 + cipherSuitesLength;
469
-
470
- // Skip compression methods
471
- if (pos + 1 > buffer.length) return false;
472
- const compressionMethodsLength = buffer[pos];
473
- pos += 1 + compressionMethodsLength;
474
-
475
- // Check for extensions
476
- if (pos + 2 > buffer.length) return false;
477
-
478
- // Look for specific extensions that indicate tab reactivation
479
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
480
- pos += 2;
481
-
482
- // Extensions end position
483
- const extensionsEnd = pos + extensionsLength;
484
- if (extensionsEnd > buffer.length) return false;
485
-
486
- // Tab reactivation often has session tickets but no SNI
487
- let hasSessionTicket = false;
488
- let hasSNI = false;
489
- let hasPSK = false;
490
-
491
- // Iterate through extensions
492
- while (pos + 4 <= extensionsEnd) {
493
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
494
- pos += 2;
495
-
496
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
497
- pos += 2;
498
-
499
- if (extensionType === this.TLS_SESSION_TICKET_EXTENSION_TYPE) {
500
- hasSessionTicket = true;
501
- } else if (extensionType === this.TLS_SNI_EXTENSION_TYPE) {
502
- hasSNI = true;
503
- } else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
504
- hasPSK = true;
505
- }
506
-
507
- // Skip extension data
508
- pos += extensionLength;
509
- }
510
-
511
- // Pattern for tab reactivation: session identifier + (ticket or PSK) but no SNI
512
- if ((hasSessionTicket || hasPSK) && !hasSNI) {
513
- log('Detected tab reactivation pattern: session resumption without SNI');
514
- return true;
515
- }
516
- }
517
- } catch (error) {
518
- log(`Error checking for tab reactivation: ${error}`);
519
- }
520
-
521
- return false;
522
- }
523
-
524
- /**
525
- * Extracts the SNI (Server Name Indication) from a TLS ClientHello message.
526
- * Implements robust parsing with support for session resumption edge cases.
527
- *
528
- * @param buffer - The buffer containing the TLS ClientHello message
529
- * @param enableLogging - Whether to enable detailed debug logging
530
- * @returns The extracted server name or undefined if not found
531
- */
532
- public static extractSNI(buffer: Buffer, enableLogging: boolean = false): string | undefined {
533
- // Logging helper
534
- const log = (message: string) => {
535
- if (enableLogging) {
536
- console.log(`[SNI Extraction] ${message}`);
537
- }
538
- };
539
-
540
- try {
541
- // Buffer must be at least 5 bytes (TLS record header)
542
- if (buffer.length < 5) {
543
- log('Buffer too small for TLS record header');
544
- return undefined;
545
- }
546
-
547
- // Check record type (must be TLS_HANDSHAKE_RECORD_TYPE = 22)
548
- if (buffer[0] !== this.TLS_HANDSHAKE_RECORD_TYPE) {
549
- log(`Not a TLS handshake record: ${buffer[0]}`);
550
- return undefined;
551
- }
552
-
553
- // Check TLS version
554
- const majorVersion = buffer[1];
555
- const minorVersion = buffer[2];
556
- log(`TLS version: ${majorVersion}.${minorVersion}`);
557
-
558
- // Parse record length (bytes 3-4, big-endian)
559
- const recordLength = (buffer[3] << 8) + buffer[4];
560
- log(`Record length: ${recordLength}`);
561
-
562
- // Validate record length against buffer size
563
- if (buffer.length < recordLength + 5) {
564
- log('Buffer smaller than expected record length');
565
- return undefined;
566
- }
567
-
568
- // Start of handshake message in the buffer
569
- let pos = 5;
570
-
571
- // Check handshake type (must be CLIENT_HELLO = 1)
572
- if (buffer[pos] !== this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE) {
573
- log(`Not a ClientHello message: ${buffer[pos]}`);
574
- return undefined;
575
- }
576
-
577
- // Skip handshake type (1 byte)
578
- pos += 1;
579
-
580
- // Parse handshake length (3 bytes, big-endian)
581
- const handshakeLength = (buffer[pos] << 16) + (buffer[pos + 1] << 8) + buffer[pos + 2];
582
- log(`Handshake length: ${handshakeLength}`);
583
-
584
- // Skip handshake length (3 bytes)
585
- pos += 3;
586
-
587
- // Check client version (2 bytes)
588
- const clientMajorVersion = buffer[pos];
589
- const clientMinorVersion = buffer[pos + 1];
590
- log(`Client version: ${clientMajorVersion}.${clientMinorVersion}`);
591
-
592
- // Skip client version (2 bytes)
593
- pos += 2;
594
-
595
- // Skip client random (32 bytes)
596
- pos += 32;
597
-
598
- // Parse session ID
599
- if (pos + 1 > buffer.length) {
600
- log('Buffer too small for session ID length');
601
- return undefined;
602
- }
603
-
604
- const sessionIdLength = buffer[pos];
605
- log(`Session ID length: ${sessionIdLength}`);
606
-
607
- // Skip session ID length (1 byte) and session ID
608
- pos += 1 + sessionIdLength;
609
-
610
- // Check if we have enough bytes left
611
- if (pos + 2 > buffer.length) {
612
- log('Buffer too small for cipher suites length');
613
- return undefined;
614
- }
615
-
616
- // Parse cipher suites length (2 bytes, big-endian)
617
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
618
- log(`Cipher suites length: ${cipherSuitesLength}`);
619
-
620
- // Skip cipher suites length (2 bytes) and cipher suites
621
- pos += 2 + cipherSuitesLength;
622
-
623
- // Check if we have enough bytes left
624
- if (pos + 1 > buffer.length) {
625
- log('Buffer too small for compression methods length');
626
- return undefined;
627
- }
628
-
629
- // Parse compression methods length (1 byte)
630
- const compressionMethodsLength = buffer[pos];
631
- log(`Compression methods length: ${compressionMethodsLength}`);
632
-
633
- // Skip compression methods length (1 byte) and compression methods
634
- pos += 1 + compressionMethodsLength;
635
-
636
- // Check if we have enough bytes for extensions length
637
- if (pos + 2 > buffer.length) {
638
- log('No extensions present or buffer too small');
639
- return undefined;
640
- }
641
-
642
- // Parse extensions length (2 bytes, big-endian)
643
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
644
- log(`Extensions length: ${extensionsLength}`);
645
-
646
- // Skip extensions length (2 bytes)
647
- pos += 2;
648
-
649
- // Extensions end position
650
- const extensionsEnd = pos + extensionsLength;
651
-
652
- // Check if extensions length is valid
653
- if (extensionsEnd > buffer.length) {
654
- log('Extensions length exceeds buffer size');
655
- return undefined;
656
- }
657
-
658
- // Track if we found session tickets (for improved resumption handling)
659
- let hasSessionTicket = false;
660
- let hasPskExtension = false;
661
-
662
- // Iterate through extensions
663
- while (pos + 4 <= extensionsEnd) {
664
- // Parse extension type (2 bytes, big-endian)
665
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
666
- log(`Extension type: 0x${extensionType.toString(16).padStart(4, '0')}`);
667
-
668
- // Skip extension type (2 bytes)
669
- pos += 2;
670
-
671
- // Parse extension length (2 bytes, big-endian)
672
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
673
- log(`Extension length: ${extensionLength}`);
674
-
675
- // Skip extension length (2 bytes)
676
- pos += 2;
677
-
678
- // Check if this is the SNI extension
679
- if (extensionType === this.TLS_SNI_EXTENSION_TYPE) {
680
- log('Found SNI extension');
681
-
682
- // Ensure we have enough bytes for the server name list
683
- if (pos + 2 > extensionsEnd) {
684
- log('Extension too small for server name list length');
685
- pos += extensionLength; // Skip this extension
686
- continue;
687
- }
688
-
689
- // Parse server name list length (2 bytes, big-endian)
690
- const serverNameListLength = (buffer[pos] << 8) + buffer[pos + 1];
691
- log(`Server name list length: ${serverNameListLength}`);
692
-
693
- // Skip server name list length (2 bytes)
694
- pos += 2;
695
-
696
- // Ensure server name list length is valid
697
- if (pos + serverNameListLength > extensionsEnd) {
698
- log('Server name list length exceeds extension size');
699
- break; // Exit the loop, extension parsing is broken
700
- }
701
-
702
- // End position of server name list
703
- const serverNameListEnd = pos + serverNameListLength;
704
-
705
- // Iterate through server names
706
- while (pos + 3 <= serverNameListEnd) {
707
- // Check name type (must be HOST_NAME_TYPE = 0 for hostname)
708
- const nameType = buffer[pos];
709
- log(`Name type: ${nameType}`);
710
-
711
- if (nameType !== this.TLS_SNI_HOST_NAME_TYPE) {
712
- log(`Unsupported name type: ${nameType}`);
713
- pos += 1; // Skip name type (1 byte)
714
-
715
- // Skip name length (2 bytes) and name data
716
- if (pos + 2 <= serverNameListEnd) {
717
- const nameLength = (buffer[pos] << 8) + buffer[pos + 1];
718
- pos += 2 + nameLength;
719
- } else {
720
- log('Invalid server name entry');
721
- break;
722
- }
723
- continue;
724
- }
725
-
726
- // Skip name type (1 byte)
727
- pos += 1;
728
-
729
- // Ensure we have enough bytes for name length
730
- if (pos + 2 > serverNameListEnd) {
731
- log('Server name entry too small for name length');
732
- break;
733
- }
734
-
735
- // Parse name length (2 bytes, big-endian)
736
- const nameLength = (buffer[pos] << 8) + buffer[pos + 1];
737
- log(`Name length: ${nameLength}`);
738
-
739
- // Skip name length (2 bytes)
740
- pos += 2;
741
-
742
- // Ensure we have enough bytes for the name
743
- if (pos + nameLength > serverNameListEnd) {
744
- log('Name length exceeds server name list size');
745
- break;
746
- }
747
-
748
- // Extract server name (hostname)
749
- const serverName = buffer.slice(pos, pos + nameLength).toString('utf8');
750
- log(`Extracted server name: ${serverName}`);
751
- return serverName;
752
- }
753
- } else if (extensionType === this.TLS_SESSION_TICKET_EXTENSION_TYPE) {
754
- // If we encounter a session ticket extension, mark it for later
755
- log('Found session ticket extension');
756
- hasSessionTicket = true;
757
- pos += extensionLength; // Skip this extension
758
- } else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
759
- // TLS 1.3 PSK extension - mark for resumption support
760
- log('Found PSK extension (TLS 1.3 resumption indicator)');
761
- hasPskExtension = true;
762
- // We'll skip the extension here and process it separately if needed
763
- pos += extensionLength;
764
- } else {
765
- // Skip this extension
766
- pos += extensionLength;
767
- }
768
- }
769
-
770
- // Log if we found session resumption indicators but no SNI
771
- if (hasSessionTicket || hasPskExtension) {
772
- log('Session resumption indicators present but no SNI found');
773
- }
774
-
775
- log('No SNI extension found in ClientHello');
776
- return undefined;
777
- } catch (error) {
778
- log(`Error parsing SNI: ${error instanceof Error ? error.message : String(error)}`);
779
- return undefined;
780
- }
781
- }
782
-
783
- /**
784
- * Attempts to extract SNI from the PSK extension in a TLS 1.3 ClientHello.
785
- *
786
- * In TLS 1.3, when a client attempts to resume a session, it may include
787
- * the server name in the PSK identity hint rather than in the SNI extension.
788
- *
789
- * @param buffer - The buffer containing the TLS ClientHello message
790
- * @param enableLogging - Whether to enable detailed debug logging
791
- * @returns The extracted server name or undefined if not found
792
- */
793
- public static extractSNIFromPSKExtension(
794
- buffer: Buffer,
795
- enableLogging: boolean = false
796
- ): string | undefined {
797
- const log = (message: string) => {
798
- if (enableLogging) {
799
- console.log(`[PSK-SNI Extraction] ${message}`);
800
- }
801
- };
802
-
803
- try {
804
- // Ensure this is a ClientHello
805
- if (!this.isClientHello(buffer)) {
806
- log('Not a ClientHello message');
807
- return undefined;
808
- }
809
-
810
- // Find the start position of extensions
811
- let pos = 5; // Start after record header
812
-
813
- // Skip handshake type (1 byte)
814
- pos += 1;
815
-
816
- // Skip handshake length (3 bytes)
817
- pos += 3;
818
-
819
- // Skip client version (2 bytes)
820
- pos += 2;
821
-
822
- // Skip client random (32 bytes)
823
- pos += 32;
824
-
825
- // Skip session ID
826
- if (pos + 1 > buffer.length) return undefined;
827
- const sessionIdLength = buffer[pos];
828
- pos += 1 + sessionIdLength;
829
-
830
- // Skip cipher suites
831
- if (pos + 2 > buffer.length) return undefined;
832
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
833
- pos += 2 + cipherSuitesLength;
834
-
835
- // Skip compression methods
836
- if (pos + 1 > buffer.length) return undefined;
837
- const compressionMethodsLength = buffer[pos];
838
- pos += 1 + compressionMethodsLength;
839
-
840
- // Check if we have extensions
841
- if (pos + 2 > buffer.length) {
842
- log('No extensions present');
843
- return undefined;
844
- }
845
-
846
- // Get extensions length
847
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
848
- pos += 2;
849
-
850
- // Extensions end position
851
- const extensionsEnd = pos + extensionsLength;
852
- if (extensionsEnd > buffer.length) return undefined;
853
-
854
- // Look for PSK extension
855
- while (pos + 4 <= extensionsEnd) {
856
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
857
- pos += 2;
858
-
859
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
860
- pos += 2;
861
-
862
- if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
863
- log('Found PSK extension');
864
-
865
- // PSK extension structure:
866
- // 2 bytes: identities list length
867
- if (pos + 2 > extensionsEnd) break;
868
- const identitiesLength = (buffer[pos] << 8) + buffer[pos + 1];
869
- pos += 2;
870
-
871
- // End of identities list
872
- const identitiesEnd = pos + identitiesLength;
873
- if (identitiesEnd > extensionsEnd) break;
874
-
875
- // Process each PSK identity
876
- while (pos + 2 <= identitiesEnd) {
877
- // Identity length (2 bytes)
878
- if (pos + 2 > identitiesEnd) break;
879
- const identityLength = (buffer[pos] << 8) + buffer[pos + 1];
880
- pos += 2;
881
-
882
- if (pos + identityLength > identitiesEnd) break;
883
-
884
- // Try to extract hostname from identity
885
- // Chrome often embeds the hostname in the PSK identity
886
- // This is a heuristic as there's no standard format
887
- if (identityLength > 0) {
888
- const identity = buffer.slice(pos, pos + identityLength);
889
-
890
- // Skip identity bytes
891
- pos += identityLength;
892
-
893
- // Skip obfuscated ticket age (4 bytes)
894
- if (pos + 4 <= identitiesEnd) {
895
- pos += 4;
896
- } else {
897
- break;
898
- }
899
-
900
- // Try to parse the identity as UTF-8
901
- try {
902
- const identityStr = identity.toString('utf8');
903
- log(`PSK identity: ${identityStr}`);
904
-
905
- // Check if the identity contains hostname hints
906
- // Chrome often embeds the hostname in a known format
907
- // Try to extract using common patterns
908
-
909
- // Pattern 1: Look for domain name pattern
910
- const domainPattern =
911
- /([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?/i;
912
- const domainMatch = identityStr.match(domainPattern);
913
- if (domainMatch && domainMatch[0]) {
914
- log(`Found domain in PSK identity: ${domainMatch[0]}`);
915
- return domainMatch[0];
916
- }
917
-
918
- // Pattern 2: Chrome sometimes uses a specific format with delimiters
919
- // This is a heuristic approach since the format isn't standardized
920
- const parts = identityStr.split('|');
921
- if (parts.length > 1) {
922
- for (const part of parts) {
923
- if (part.includes('.') && !part.includes('/')) {
924
- const possibleDomain = part.trim();
925
- if (/^[a-z0-9.-]+$/i.test(possibleDomain)) {
926
- log(`Found possible domain in PSK delimiter format: ${possibleDomain}`);
927
- return possibleDomain;
928
- }
929
- }
930
- }
931
- }
932
- } catch (e) {
933
- log('Failed to parse PSK identity as UTF-8');
934
- }
935
- }
936
- }
937
- } else {
938
- // Skip this extension
939
- pos += extensionLength;
940
- }
941
- }
942
-
943
- log('No hostname found in PSK extension');
944
- return undefined;
945
- } catch (error) {
946
- log(`Error parsing PSK: ${error instanceof Error ? error.message : String(error)}`);
947
- return undefined;
948
- }
949
- }
950
-
951
- /**
952
- * Checks if the buffer contains TLS 1.3 early data (0-RTT)
953
- * @param buffer - The buffer to check
954
- * @param enableLogging - Whether to enable logging
955
- * @returns true if early data is detected
956
- */
957
- public static hasEarlyData(buffer: Buffer, enableLogging: boolean = false): boolean {
958
- const log = (message: string) => {
959
- if (enableLogging) {
960
- console.log(`[Early Data] ${message}`);
961
- }
962
- };
963
-
964
- try {
965
- // Check if this is a valid ClientHello first
966
- if (!this.isClientHello(buffer)) {
967
- return false;
968
- }
969
-
970
- // Find the extensions section
971
- let pos = 5; // Start after record header
972
-
973
- // Skip handshake type (1 byte)
974
- pos += 1;
975
-
976
- // Skip handshake length (3 bytes)
977
- pos += 3;
978
-
979
- // Skip client version (2 bytes)
980
- pos += 2;
981
-
982
- // Skip client random (32 bytes)
983
- pos += 32;
984
-
985
- // Skip session ID
986
- if (pos + 1 > buffer.length) return false;
987
- const sessionIdLength = buffer[pos];
988
- pos += 1 + sessionIdLength;
989
-
990
- // Skip cipher suites
991
- if (pos + 2 > buffer.length) return false;
992
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
993
- pos += 2 + cipherSuitesLength;
994
-
995
- // Skip compression methods
996
- if (pos + 1 > buffer.length) return false;
997
- const compressionMethodsLength = buffer[pos];
998
- pos += 1 + compressionMethodsLength;
999
-
1000
- // Check if we have extensions
1001
- if (pos + 2 > buffer.length) return false;
1002
-
1003
- // Get extensions length
1004
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
1005
- pos += 2;
1006
-
1007
- // Extensions end position
1008
- const extensionsEnd = pos + extensionsLength;
1009
- if (extensionsEnd > buffer.length) return false;
1010
-
1011
- // Look for early data extension
1012
- while (pos + 4 <= extensionsEnd) {
1013
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
1014
- pos += 2;
1015
-
1016
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
1017
- pos += 2;
1018
-
1019
- if (extensionType === this.TLS_EARLY_DATA_EXTENSION_TYPE) {
1020
- log('Early Data (0-RTT) extension detected');
1021
- return true;
1022
- }
1023
-
1024
- // Skip to next extension
1025
- pos += extensionLength;
1026
- }
1027
-
1028
- return false;
1029
- } catch (error) {
1030
- log(`Error checking for early data: ${error}`);
1031
- return false;
1032
- }
1033
- }
1034
-
1035
- /**
1036
- * Attempts to extract SNI from an initial ClientHello packet and handles
1037
- * session resumption edge cases more robustly than the standard extraction.
1038
- *
1039
- * This method handles:
1040
- * 1. Standard SNI extraction
1041
- * 2. TLS 1.3 PSK-based resumption (Chrome, Firefox, etc.)
1042
- * 3. Session ticket-based resumption
1043
- * 4. Fragmented ClientHello messages
1044
- * 5. TLS 1.3 Early Data (0-RTT)
1045
- * 6. Chrome's connection racing behaviors
1046
- *
1047
- * @param buffer - The buffer containing the TLS ClientHello message
1048
- * @param connectionInfo - Optional connection information for fragment handling
1049
- * @param enableLogging - Whether to enable detailed debug logging
1050
- * @returns The extracted server name or undefined if not found
1051
- */
1052
- public static extractSNIWithResumptionSupport(
1053
- buffer: Buffer,
1054
- connectionInfo?: {
1055
- sourceIp?: string;
1056
- sourcePort?: number;
1057
- destIp?: string;
1058
- destPort?: number;
1059
- },
1060
- enableLogging: boolean = false
1061
- ): string | undefined {
1062
- const log = (message: string) => {
1063
- if (enableLogging) {
1064
- console.log(`[SNI Extraction] ${message}`);
1065
- }
1066
- };
1067
-
1068
- // Log buffer details for debugging
1069
- if (enableLogging) {
1070
- log(`Buffer size: ${buffer.length} bytes`);
1071
- log(`Buffer starts with: ${buffer.slice(0, Math.min(10, buffer.length)).toString('hex')}`);
1072
-
1073
- if (buffer.length >= 5) {
1074
- const recordType = buffer[0];
1075
- const majorVersion = buffer[1];
1076
- const minorVersion = buffer[2];
1077
- const recordLength = (buffer[3] << 8) + buffer[4];
1078
-
1079
- log(
1080
- `TLS Record: type=${recordType}, version=${majorVersion}.${minorVersion}, length=${recordLength}`
1081
- );
1082
- }
1083
- }
1084
-
1085
- // Check if we need to handle fragmented packets
1086
- let processBuffer = buffer;
1087
- if (connectionInfo) {
1088
- const connectionId = this.createConnectionId(connectionInfo);
1089
- const reassembledBuffer = this.handleFragmentedClientHello(
1090
- buffer,
1091
- connectionId,
1092
- enableLogging
1093
- );
1094
-
1095
- if (!reassembledBuffer) {
1096
- log(`Waiting for more fragments on connection ${connectionId}`);
1097
- return undefined; // Need more fragments to complete ClientHello
1098
- }
1099
-
1100
- processBuffer = reassembledBuffer;
1101
- log(`Using reassembled buffer of length ${processBuffer.length}`);
1102
- }
1103
-
1104
- // First try the standard SNI extraction
1105
- const standardSni = this.extractSNI(processBuffer, enableLogging);
1106
- if (standardSni) {
1107
- log(`Found standard SNI: ${standardSni}`);
1108
- return standardSni;
1109
- }
1110
-
1111
- // Check for session resumption when standard SNI extraction fails
1112
- if (this.isClientHello(processBuffer)) {
1113
- const resumptionInfo = this.hasSessionResumption(processBuffer, enableLogging);
1114
-
1115
- if (resumptionInfo.isResumption) {
1116
- log(`Detected session resumption in ClientHello without standard SNI`);
1117
-
1118
- // Try to extract SNI from PSK extension
1119
- const pskSni = this.extractSNIFromPSKExtension(processBuffer, enableLogging);
1120
- if (pskSni) {
1121
- log(`Extracted SNI from PSK extension: ${pskSni}`);
1122
- return pskSni;
1123
- }
1124
- }
1125
- }
1126
-
1127
- // Log detailed info about the ClientHello when SNI extraction fails
1128
- if (this.isClientHello(processBuffer) && enableLogging) {
1129
- log(`SNI extraction failed for ClientHello. Buffer details:`);
1130
-
1131
- if (processBuffer.length >= 43) {
1132
- // ClientHello with at least client random
1133
- const clientRandom = processBuffer.slice(11, 11 + 32).toString('hex');
1134
- log(`Client Random: ${clientRandom}`);
1135
-
1136
- // Log session ID length and presence
1137
- const sessionIdLength = processBuffer[43];
1138
- log(`Session ID length: ${sessionIdLength}`);
1139
-
1140
- if (sessionIdLength > 0 && processBuffer.length >= 44 + sessionIdLength) {
1141
- const sessionId = processBuffer.slice(44, 44 + sessionIdLength).toString('hex');
1142
- log(`Session ID: ${sessionId}`);
1143
- }
1144
- }
1145
- }
1146
-
1147
- return undefined;
1148
- }
1149
-
1150
- /**
1151
- * Main entry point for SNI extraction that handles all edge cases.
1152
- * This should be called for each TLS packet received from a client.
1153
- *
1154
- * The method uses connection tracking to handle fragmented ClientHello
1155
- * messages and various TLS 1.3 behaviors, including Chrome's connection
1156
- * racing patterns and tab reactivation behaviors.
1157
- *
1158
- * @param buffer - The buffer containing TLS data
1159
- * @param connectionInfo - Connection metadata (IPs and ports)
1160
- * @param enableLogging - Whether to enable detailed debug logging
1161
- * @param cachedSni - Optional cached SNI from previous connections (for racing detection)
1162
- * @returns The extracted server name or undefined if not found or more data needed
1163
- */
1164
- public static processTlsPacket(
1165
- buffer: Buffer,
1166
- connectionInfo: {
1167
- sourceIp: string;
1168
- sourcePort: number;
1169
- destIp: string;
1170
- destPort: number;
1171
- timestamp?: number;
1172
- },
1173
- enableLogging: boolean = false,
1174
- cachedSni?: string
1175
- ): string | undefined {
1176
- const log = (message: string) => {
1177
- if (enableLogging) {
1178
- console.log(`[TLS Packet] ${message}`);
1179
- }
1180
- };
1181
-
1182
- // Add timestamp if not provided
1183
- if (!connectionInfo.timestamp) {
1184
- connectionInfo.timestamp = Date.now();
1185
- }
1186
-
1187
- // Check if this is a TLS handshake or application data
1188
- if (!this.isTlsHandshake(buffer) && !this.isTlsApplicationData(buffer)) {
1189
- log('Not a TLS handshake or application data packet');
1190
- return undefined;
1191
- }
1192
-
1193
- // Create connection ID for tracking
1194
- const connectionId = this.createConnectionId(connectionInfo);
1195
- log(`Processing TLS packet for connection ${connectionId}, buffer length: ${buffer.length}`);
1196
-
1197
- // Handle application data with cached SNI (for connection racing)
1198
- if (this.isTlsApplicationData(buffer)) {
1199
- // If explicit cachedSni was provided, use it
1200
- if (cachedSni) {
1201
- log(`Using provided cached SNI for application data: ${cachedSni}`);
1202
- return cachedSni;
1203
- }
1204
-
1205
- log('Application data packet without cached SNI, cannot determine hostname');
1206
- return undefined;
1207
- }
1208
-
1209
- // Enhanced session resumption detection
1210
- if (this.isClientHello(buffer)) {
1211
- const resumptionInfo = this.hasSessionResumption(buffer, enableLogging);
1212
-
1213
- if (resumptionInfo.isResumption) {
1214
- log(`Session resumption detected in TLS packet`);
1215
-
1216
- // Always try standard SNI extraction first
1217
- const standardSni = this.extractSNI(buffer, enableLogging);
1218
- if (standardSni) {
1219
- log(`Found standard SNI in session resumption: ${standardSni}`);
1220
- return standardSni;
1221
- }
1222
-
1223
- // Enhanced session resumption SNI extraction
1224
- // Try extracting from PSK identity
1225
- const pskSni = this.extractSNIFromPSKExtension(buffer, enableLogging);
1226
- if (pskSni) {
1227
- log(`Extracted SNI from PSK extension: ${pskSni}`);
1228
- return pskSni;
1229
- }
1230
-
1231
- // Additional check for SNI in session tickets
1232
- if (enableLogging) {
1233
- log(`Checking for session ticket information to extract server name...`);
1234
- // Log more details for debugging
1235
- try {
1236
- // Look at the raw buffer for patterns
1237
- log(`Buffer hexdump (first 100 bytes): ${buffer.slice(0, 100).toString('hex')}`);
1238
-
1239
- // Try to find hostname-like patterns in the buffer
1240
- const bufferStr = buffer.toString('utf8', 0, buffer.length);
1241
- const hostnamePattern =
1242
- /([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?/gi;
1243
- const hostMatches = bufferStr.match(hostnamePattern);
1244
-
1245
- if (hostMatches && hostMatches.length > 0) {
1246
- log(`Possible hostnames found in buffer: ${hostMatches.join(', ')}`);
1247
-
1248
- // Check if any match looks like a valid domain
1249
- for (const match of hostMatches) {
1250
- if (match.includes('.') && match.length > 3) {
1251
- log(`Potential SNI found in session data: ${match}`);
1252
- // Don't automatically use this - just log for debugging
1253
- }
1254
- }
1255
- }
1256
- } catch (e) {
1257
- log(`Error scanning for patterns: ${e}`);
1258
- }
1259
- }
1260
-
1261
- log(`Session resumption without extractable SNI`);
1262
- // If allowSessionTicket=false, should be rejected by caller
1263
- }
1264
- }
1265
-
1266
- // For handshake messages, try the full extraction process
1267
- const sni = this.extractSNIWithResumptionSupport(buffer, connectionInfo, enableLogging);
1268
-
1269
- if (sni) {
1270
- log(`Successfully extracted SNI: ${sni}`);
1271
- return sni;
1272
- }
1273
-
1274
- // If we couldn't extract an SNI, check if this is a valid ClientHello
1275
- if (this.isClientHello(buffer)) {
1276
- log('Valid ClientHello detected, but no SNI extracted - might need more data');
1277
- }
1278
-
1279
- return undefined;
1280
- }
1281
- }