@push.rocks/smartproxy 20.0.1 → 21.1.0
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.
- package/changelog.md +26 -0
- package/dist_ts/core/utils/proxy-protocol.d.ts +5 -17
- package/dist_ts/core/utils/proxy-protocol.js +13 -97
- package/dist_ts/core/utils/websocket-utils.d.ts +6 -7
- package/dist_ts/core/utils/websocket-utils.js +10 -66
- package/dist_ts/detection/detectors/http-detector-v2.d.ts +33 -0
- package/dist_ts/detection/detectors/http-detector-v2.js +87 -0
- package/dist_ts/detection/detectors/http-detector.d.ts +33 -0
- package/dist_ts/detection/detectors/http-detector.js +89 -0
- package/dist_ts/detection/detectors/quick-detector.d.ts +28 -0
- package/dist_ts/detection/detectors/quick-detector.js +131 -0
- package/dist_ts/detection/detectors/routing-extractor.d.ts +28 -0
- package/dist_ts/detection/detectors/routing-extractor.js +122 -0
- package/dist_ts/detection/detectors/tls-detector-v2.d.ts +33 -0
- package/dist_ts/detection/detectors/tls-detector-v2.js +80 -0
- package/dist_ts/detection/detectors/tls-detector.d.ts +33 -0
- package/dist_ts/detection/detectors/tls-detector.js +106 -0
- package/dist_ts/detection/index.d.ts +17 -0
- package/dist_ts/detection/index.js +22 -0
- package/dist_ts/detection/models/detection-types.d.ts +87 -0
- package/dist_ts/detection/models/detection-types.js +5 -0
- package/dist_ts/detection/models/interfaces.d.ts +97 -0
- package/dist_ts/detection/models/interfaces.js +5 -0
- package/dist_ts/detection/protocol-detector-v2.d.ts +46 -0
- package/dist_ts/detection/protocol-detector-v2.js +116 -0
- package/dist_ts/detection/protocol-detector.d.ts +74 -0
- package/dist_ts/detection/protocol-detector.js +173 -0
- package/dist_ts/detection/utils/buffer-utils.d.ts +61 -0
- package/dist_ts/detection/utils/buffer-utils.js +127 -0
- package/dist_ts/detection/utils/fragment-manager.d.ts +31 -0
- package/dist_ts/detection/utils/fragment-manager.js +53 -0
- package/dist_ts/detection/utils/parser-utils.d.ts +42 -0
- package/dist_ts/detection/utils/parser-utils.js +63 -0
- package/dist_ts/index.d.ts +2 -1
- package/dist_ts/index.js +3 -2
- package/dist_ts/protocols/common/fragment-handler.d.ts +73 -0
- package/dist_ts/protocols/common/fragment-handler.js +117 -0
- package/dist_ts/protocols/common/index.d.ts +7 -0
- package/dist_ts/protocols/common/index.js +8 -0
- package/dist_ts/protocols/common/types.d.ts +68 -0
- package/dist_ts/protocols/common/types.js +7 -0
- package/dist_ts/protocols/http/constants.d.ts +119 -0
- package/dist_ts/protocols/http/constants.js +200 -0
- package/dist_ts/protocols/http/index.d.ts +7 -0
- package/dist_ts/protocols/http/index.js +8 -0
- package/dist_ts/protocols/http/parser.d.ts +58 -0
- package/dist_ts/protocols/http/parser.js +184 -0
- package/dist_ts/protocols/http/types.d.ts +62 -0
- package/dist_ts/protocols/http/types.js +5 -0
- package/dist_ts/protocols/index.d.ts +11 -0
- package/dist_ts/protocols/index.js +12 -0
- package/dist_ts/protocols/proxy/index.d.ts +6 -0
- package/dist_ts/protocols/proxy/index.js +7 -0
- package/dist_ts/protocols/proxy/parser.d.ts +44 -0
- package/dist_ts/protocols/proxy/parser.js +153 -0
- package/dist_ts/protocols/proxy/types.d.ts +47 -0
- package/dist_ts/protocols/proxy/types.js +6 -0
- package/dist_ts/protocols/tls/alerts/index.d.ts +4 -0
- package/dist_ts/protocols/tls/alerts/index.js +5 -0
- package/dist_ts/protocols/tls/alerts/tls-alert.d.ts +150 -0
- package/dist_ts/protocols/tls/alerts/tls-alert.js +226 -0
- package/dist_ts/protocols/tls/constants.d.ts +122 -0
- package/dist_ts/protocols/tls/constants.js +135 -0
- package/dist_ts/protocols/tls/index.d.ts +12 -0
- package/dist_ts/protocols/tls/index.js +27 -0
- package/dist_ts/protocols/tls/parser.d.ts +53 -0
- package/dist_ts/protocols/tls/parser.js +294 -0
- package/dist_ts/protocols/tls/sni/client-hello-parser.d.ts +100 -0
- package/dist_ts/protocols/tls/sni/client-hello-parser.js +463 -0
- package/dist_ts/protocols/tls/sni/index.d.ts +5 -0
- package/dist_ts/protocols/tls/sni/index.js +6 -0
- package/dist_ts/protocols/tls/sni/sni-extraction.d.ts +58 -0
- package/dist_ts/protocols/tls/sni/sni-extraction.js +275 -0
- package/dist_ts/protocols/tls/types.d.ts +65 -0
- package/dist_ts/protocols/tls/types.js +5 -0
- package/dist_ts/protocols/tls/utils/index.d.ts +4 -0
- package/dist_ts/protocols/tls/utils/index.js +5 -0
- package/dist_ts/protocols/tls/utils/tls-utils.d.ts +158 -0
- package/dist_ts/protocols/tls/utils/tls-utils.js +187 -0
- package/dist_ts/protocols/websocket/constants.d.ts +55 -0
- package/dist_ts/protocols/websocket/constants.js +58 -0
- package/dist_ts/protocols/websocket/index.d.ts +7 -0
- package/dist_ts/protocols/websocket/index.js +8 -0
- package/dist_ts/protocols/websocket/types.d.ts +47 -0
- package/dist_ts/protocols/websocket/types.js +5 -0
- package/dist_ts/protocols/websocket/utils.d.ts +25 -0
- package/dist_ts/protocols/websocket/utils.js +103 -0
- package/dist_ts/proxies/http-proxy/models/http-types.d.ts +25 -27
- package/dist_ts/proxies/http-proxy/models/http-types.js +24 -44
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +81 -61
- package/dist_ts/proxies/smart-proxy/tls-manager.js +2 -1
- package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -2
- package/dist_ts/proxies/smart-proxy/utils/index.js +3 -4
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +112 -8
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +231 -76
- package/dist_ts/tls/index.d.ts +5 -7
- package/dist_ts/tls/index.js +8 -11
- package/dist_ts/tls/sni/client-hello-parser.js +3 -2
- package/dist_ts/tls/sni/sni-handler.js +4 -4
- package/dist_ts/tls/utils/tls-utils.d.ts +1 -110
- package/dist_ts/tls/utils/tls-utils.js +4 -116
- package/package.json +17 -8
- package/readme.md +471 -2345
- package/readme.plan.md +0 -0
- package/ts/core/utils/proxy-protocol.ts +14 -131
- package/ts/core/utils/websocket-utils.ts +12 -60
- package/ts/detection/detectors/http-detector.ts +114 -0
- package/ts/detection/detectors/quick-detector.ts +148 -0
- package/ts/detection/detectors/routing-extractor.ts +147 -0
- package/ts/detection/detectors/tls-detector.ts +120 -0
- package/ts/detection/index.ts +25 -0
- package/ts/detection/models/detection-types.ts +102 -0
- package/ts/detection/models/interfaces.ts +115 -0
- package/ts/detection/protocol-detector.ts +230 -0
- package/ts/detection/utils/buffer-utils.ts +141 -0
- package/ts/detection/utils/fragment-manager.ts +64 -0
- package/ts/detection/utils/parser-utils.ts +77 -0
- package/ts/index.ts +3 -2
- package/ts/protocols/common/fragment-handler.ts +163 -0
- package/ts/protocols/common/index.ts +8 -0
- package/ts/protocols/common/types.ts +76 -0
- package/ts/protocols/http/constants.ts +219 -0
- package/ts/protocols/http/index.ts +8 -0
- package/ts/protocols/http/parser.ts +219 -0
- package/ts/protocols/http/types.ts +70 -0
- package/ts/protocols/index.ts +12 -0
- package/ts/protocols/proxy/index.ts +7 -0
- package/ts/protocols/proxy/parser.ts +183 -0
- package/ts/protocols/proxy/types.ts +53 -0
- package/ts/{tls → protocols/tls}/alerts/tls-alert.ts +1 -1
- package/ts/protocols/tls/index.ts +37 -0
- package/ts/protocols/tls/sni/index.ts +6 -0
- package/ts/{tls → protocols/tls}/utils/tls-utils.ts +1 -1
- package/ts/protocols/websocket/constants.ts +60 -0
- package/ts/protocols/websocket/index.ts +8 -0
- package/ts/protocols/websocket/types.ts +53 -0
- package/ts/protocols/websocket/utils.ts +98 -0
- package/ts/proxies/http-proxy/models/http-types.ts +29 -46
- package/ts/proxies/smart-proxy/models/interfaces.ts +7 -1
- package/ts/proxies/smart-proxy/models/route-types.ts +0 -1
- package/ts/proxies/smart-proxy/route-connection-handler.ts +91 -68
- package/ts/proxies/smart-proxy/tls-manager.ts +1 -0
- package/ts/proxies/smart-proxy/utils/index.ts +2 -13
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +323 -86
- package/ts/tls/index.ts +8 -12
- package/ts/tls/sni/sni-handler.ts +3 -3
- package/ts/forwarding/config/forwarding-types.ts +0 -76
- package/ts/forwarding/config/index.ts +0 -26
- package/ts/forwarding/factory/forwarding-factory.ts +0 -189
- package/ts/forwarding/factory/index.ts +0 -5
- package/ts/forwarding/handlers/base-handler.ts +0 -155
- package/ts/forwarding/handlers/http-handler.ts +0 -163
- package/ts/forwarding/handlers/https-passthrough-handler.ts +0 -185
- package/ts/forwarding/handlers/https-terminate-to-http-handler.ts +0 -312
- package/ts/forwarding/handlers/https-terminate-to-https-handler.ts +0 -297
- package/ts/forwarding/handlers/index.ts +0 -9
- package/ts/forwarding/index.ts +0 -35
- package/ts/proxies/smart-proxy/utils/route-patterns.ts +0 -403
- /package/ts/{tls → protocols/tls}/alerts/index.ts +0 -0
- /package/ts/{tls → protocols/tls}/sni/client-hello-parser.ts +0 -0
- /package/ts/{tls → protocols/tls}/sni/sni-extraction.ts +0 -0
- /package/ts/{tls → protocols/tls}/utils/index.ts +0 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { TlsRecordType, TlsHandshakeType, TlsExtensionType, TlsUtils } from '../utils/tls-utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* Class for parsing TLS ClientHello messages
|
|
5
|
+
*/
|
|
6
|
+
export class ClientHelloParser {
|
|
7
|
+
// Buffer for handling fragmented ClientHello messages
|
|
8
|
+
static { this.fragmentedBuffers = new Map(); }
|
|
9
|
+
static { this.fragmentTimeout = 1000; } // ms to wait for fragments before cleanup
|
|
10
|
+
/**
|
|
11
|
+
* Clean up expired fragments
|
|
12
|
+
*/
|
|
13
|
+
static cleanupExpiredFragments() {
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
for (const [connectionId, info] of this.fragmentedBuffers.entries()) {
|
|
16
|
+
if (now - info.timestamp > this.fragmentTimeout) {
|
|
17
|
+
this.fragmentedBuffers.delete(connectionId);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Handles potential fragmented ClientHello messages by buffering and reassembling
|
|
23
|
+
* TLS record fragments that might span multiple TCP packets.
|
|
24
|
+
*
|
|
25
|
+
* @param buffer The current buffer fragment
|
|
26
|
+
* @param connectionId Unique identifier for the connection
|
|
27
|
+
* @param logger Optional logging function
|
|
28
|
+
* @returns A complete buffer if reassembly is successful, or undefined if more fragments are needed
|
|
29
|
+
*/
|
|
30
|
+
static handleFragmentedClientHello(buffer, connectionId, logger) {
|
|
31
|
+
const log = logger || (() => { });
|
|
32
|
+
// Periodically clean up expired fragments
|
|
33
|
+
this.cleanupExpiredFragments();
|
|
34
|
+
// Check if we've seen this connection before
|
|
35
|
+
if (!this.fragmentedBuffers.has(connectionId)) {
|
|
36
|
+
// New connection, start with this buffer
|
|
37
|
+
this.fragmentedBuffers.set(connectionId, {
|
|
38
|
+
buffer,
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
connectionId
|
|
41
|
+
});
|
|
42
|
+
// Evaluate if this buffer already contains a complete ClientHello
|
|
43
|
+
try {
|
|
44
|
+
if (buffer.length >= 5) {
|
|
45
|
+
// Get the record length from TLS header
|
|
46
|
+
const recordLength = (buffer[3] << 8) + buffer[4] + 5; // +5 for the TLS record header itself
|
|
47
|
+
log(`Initial buffer size: ${buffer.length}, expected record length: ${recordLength}`);
|
|
48
|
+
// Check if this buffer already contains a complete TLS record
|
|
49
|
+
if (buffer.length >= recordLength) {
|
|
50
|
+
log(`Initial buffer contains complete ClientHello, length: ${buffer.length}`);
|
|
51
|
+
return buffer;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
log(`Initial buffer too small (${buffer.length} bytes), needs at least 5 bytes for TLS header`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
log(`Error checking initial buffer completeness: ${e}`);
|
|
60
|
+
}
|
|
61
|
+
log(`Started buffering connection ${connectionId}, initial size: ${buffer.length}`);
|
|
62
|
+
return undefined; // Need more fragments
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Existing connection, append this buffer
|
|
66
|
+
const existingInfo = this.fragmentedBuffers.get(connectionId);
|
|
67
|
+
const newBuffer = Buffer.concat([existingInfo.buffer, buffer]);
|
|
68
|
+
// Update the buffer and timestamp
|
|
69
|
+
this.fragmentedBuffers.set(connectionId, {
|
|
70
|
+
...existingInfo,
|
|
71
|
+
buffer: newBuffer,
|
|
72
|
+
timestamp: Date.now()
|
|
73
|
+
});
|
|
74
|
+
log(`Appended to buffer for ${connectionId}, new size: ${newBuffer.length}`);
|
|
75
|
+
// Check if we now have a complete ClientHello
|
|
76
|
+
try {
|
|
77
|
+
if (newBuffer.length >= 5) {
|
|
78
|
+
// Get the record length from TLS header
|
|
79
|
+
const recordLength = (newBuffer[3] << 8) + newBuffer[4] + 5; // +5 for the TLS record header itself
|
|
80
|
+
log(`Reassembled buffer size: ${newBuffer.length}, expected record length: ${recordLength}`);
|
|
81
|
+
// Check if we have a complete TLS record now
|
|
82
|
+
if (newBuffer.length >= recordLength) {
|
|
83
|
+
log(`Assembled complete ClientHello, length: ${newBuffer.length}, needed: ${recordLength}`);
|
|
84
|
+
// Extract the complete TLS record (might be followed by more data)
|
|
85
|
+
const completeRecord = newBuffer.slice(0, recordLength);
|
|
86
|
+
// Check if this record is indeed a ClientHello (type 1) at position 5
|
|
87
|
+
if (completeRecord.length > 5 &&
|
|
88
|
+
completeRecord[5] === TlsHandshakeType.CLIENT_HELLO) {
|
|
89
|
+
log(`Verified record is a ClientHello handshake message`);
|
|
90
|
+
// Complete message received, remove from tracking
|
|
91
|
+
this.fragmentedBuffers.delete(connectionId);
|
|
92
|
+
return completeRecord;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
log(`Record is complete but not a ClientHello handshake, continuing to buffer`);
|
|
96
|
+
// This might be another TLS record type preceding the ClientHello
|
|
97
|
+
// Try checking for a ClientHello starting at the end of this record
|
|
98
|
+
if (newBuffer.length > recordLength + 5) {
|
|
99
|
+
const nextRecordType = newBuffer[recordLength];
|
|
100
|
+
log(`Next record type: ${nextRecordType} (looking for ${TlsRecordType.HANDSHAKE})`);
|
|
101
|
+
if (nextRecordType === TlsRecordType.HANDSHAKE) {
|
|
102
|
+
const handshakeType = newBuffer[recordLength + 5];
|
|
103
|
+
log(`Next handshake type: ${handshakeType} (looking for ${TlsHandshakeType.CLIENT_HELLO})`);
|
|
104
|
+
if (handshakeType === TlsHandshakeType.CLIENT_HELLO) {
|
|
105
|
+
// Found a ClientHello in the next record, return the entire buffer
|
|
106
|
+
log(`Found ClientHello in subsequent record, returning full buffer`);
|
|
107
|
+
this.fragmentedBuffers.delete(connectionId);
|
|
108
|
+
return newBuffer;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
log(`Error checking reassembled buffer completeness: ${e}`);
|
|
118
|
+
}
|
|
119
|
+
return undefined; // Still need more fragments
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Parses a TLS ClientHello message and extracts all components
|
|
124
|
+
*
|
|
125
|
+
* @param buffer The buffer containing the ClientHello message
|
|
126
|
+
* @param logger Optional logging function
|
|
127
|
+
* @returns Parsed ClientHello or undefined if parsing failed
|
|
128
|
+
*/
|
|
129
|
+
static parseClientHello(buffer, logger) {
|
|
130
|
+
const log = logger || (() => { });
|
|
131
|
+
const result = {
|
|
132
|
+
isValid: false,
|
|
133
|
+
hasSessionId: false,
|
|
134
|
+
extensions: [],
|
|
135
|
+
hasSessionTicket: false,
|
|
136
|
+
hasPsk: false,
|
|
137
|
+
hasEarlyData: false
|
|
138
|
+
};
|
|
139
|
+
try {
|
|
140
|
+
// Check basic validity
|
|
141
|
+
if (buffer.length < 5) {
|
|
142
|
+
result.error = 'Buffer too small for TLS record header';
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
// Check record type (must be HANDSHAKE)
|
|
146
|
+
if (buffer[0] !== TlsRecordType.HANDSHAKE) {
|
|
147
|
+
result.error = `Not a TLS handshake record: ${buffer[0]}`;
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
// Get TLS version from record header
|
|
151
|
+
const majorVersion = buffer[1];
|
|
152
|
+
const minorVersion = buffer[2];
|
|
153
|
+
result.version = [majorVersion, minorVersion];
|
|
154
|
+
log(`TLS record version: ${majorVersion}.${minorVersion}`);
|
|
155
|
+
// Parse record length (bytes 3-4, big-endian)
|
|
156
|
+
const recordLength = (buffer[3] << 8) + buffer[4];
|
|
157
|
+
log(`Record length: ${recordLength}`);
|
|
158
|
+
// Validate record length against buffer size
|
|
159
|
+
if (buffer.length < recordLength + 5) {
|
|
160
|
+
result.error = 'Buffer smaller than expected record length';
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
// Start of handshake message in the buffer
|
|
164
|
+
let pos = 5;
|
|
165
|
+
// Check handshake type (must be CLIENT_HELLO)
|
|
166
|
+
if (buffer[pos] !== TlsHandshakeType.CLIENT_HELLO) {
|
|
167
|
+
result.error = `Not a ClientHello message: ${buffer[pos]}`;
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
// Skip handshake type (1 byte)
|
|
171
|
+
pos += 1;
|
|
172
|
+
// Parse handshake length (3 bytes, big-endian)
|
|
173
|
+
const handshakeLength = (buffer[pos] << 16) + (buffer[pos + 1] << 8) + buffer[pos + 2];
|
|
174
|
+
log(`Handshake length: ${handshakeLength}`);
|
|
175
|
+
// Skip handshake length (3 bytes)
|
|
176
|
+
pos += 3;
|
|
177
|
+
// Check client version (2 bytes)
|
|
178
|
+
const clientMajorVersion = buffer[pos];
|
|
179
|
+
const clientMinorVersion = buffer[pos + 1];
|
|
180
|
+
log(`Client version: ${clientMajorVersion}.${clientMinorVersion}`);
|
|
181
|
+
// Skip client version (2 bytes)
|
|
182
|
+
pos += 2;
|
|
183
|
+
// Extract client random (32 bytes)
|
|
184
|
+
if (pos + 32 > buffer.length) {
|
|
185
|
+
result.error = 'Buffer too small for client random';
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
result.random = buffer.slice(pos, pos + 32);
|
|
189
|
+
log(`Client random: ${result.random.toString('hex')}`);
|
|
190
|
+
// Skip client random (32 bytes)
|
|
191
|
+
pos += 32;
|
|
192
|
+
// Parse session ID
|
|
193
|
+
if (pos + 1 > buffer.length) {
|
|
194
|
+
result.error = 'Buffer too small for session ID length';
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
const sessionIdLength = buffer[pos];
|
|
198
|
+
log(`Session ID length: ${sessionIdLength}`);
|
|
199
|
+
pos += 1;
|
|
200
|
+
result.hasSessionId = sessionIdLength > 0;
|
|
201
|
+
if (sessionIdLength > 0) {
|
|
202
|
+
if (pos + sessionIdLength > buffer.length) {
|
|
203
|
+
result.error = 'Buffer too small for session ID';
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
result.sessionId = buffer.slice(pos, pos + sessionIdLength);
|
|
207
|
+
log(`Session ID: ${result.sessionId.toString('hex')}`);
|
|
208
|
+
}
|
|
209
|
+
// Skip session ID
|
|
210
|
+
pos += sessionIdLength;
|
|
211
|
+
// Check if we have enough bytes left for cipher suites
|
|
212
|
+
if (pos + 2 > buffer.length) {
|
|
213
|
+
result.error = 'Buffer too small for cipher suites length';
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
// Parse cipher suites length (2 bytes, big-endian)
|
|
217
|
+
const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
218
|
+
log(`Cipher suites length: ${cipherSuitesLength}`);
|
|
219
|
+
pos += 2;
|
|
220
|
+
// Extract cipher suites
|
|
221
|
+
if (pos + cipherSuitesLength > buffer.length) {
|
|
222
|
+
result.error = 'Buffer too small for cipher suites';
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
result.cipherSuites = buffer.slice(pos, pos + cipherSuitesLength);
|
|
226
|
+
// Skip cipher suites
|
|
227
|
+
pos += cipherSuitesLength;
|
|
228
|
+
// Check if we have enough bytes left for compression methods
|
|
229
|
+
if (pos + 1 > buffer.length) {
|
|
230
|
+
result.error = 'Buffer too small for compression methods length';
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
// Parse compression methods length (1 byte)
|
|
234
|
+
const compressionMethodsLength = buffer[pos];
|
|
235
|
+
log(`Compression methods length: ${compressionMethodsLength}`);
|
|
236
|
+
pos += 1;
|
|
237
|
+
// Extract compression methods
|
|
238
|
+
if (pos + compressionMethodsLength > buffer.length) {
|
|
239
|
+
result.error = 'Buffer too small for compression methods';
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
result.compressionMethods = buffer.slice(pos, pos + compressionMethodsLength);
|
|
243
|
+
// Skip compression methods
|
|
244
|
+
pos += compressionMethodsLength;
|
|
245
|
+
// Check if we have enough bytes for extensions length
|
|
246
|
+
if (pos + 2 > buffer.length) {
|
|
247
|
+
// No extensions present - this is valid for older TLS versions
|
|
248
|
+
result.isValid = true;
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
// Parse extensions length (2 bytes, big-endian)
|
|
252
|
+
const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
253
|
+
log(`Extensions length: ${extensionsLength}`);
|
|
254
|
+
pos += 2;
|
|
255
|
+
// Extensions end position
|
|
256
|
+
const extensionsEnd = pos + extensionsLength;
|
|
257
|
+
// Check if extensions length is valid
|
|
258
|
+
if (extensionsEnd > buffer.length) {
|
|
259
|
+
result.error = 'Extensions length exceeds buffer size';
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
// Iterate through extensions
|
|
263
|
+
const serverNames = [];
|
|
264
|
+
while (pos + 4 <= extensionsEnd) {
|
|
265
|
+
// Parse extension type (2 bytes, big-endian)
|
|
266
|
+
const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
|
|
267
|
+
log(`Extension type: 0x${extensionType.toString(16).padStart(4, '0')}`);
|
|
268
|
+
pos += 2;
|
|
269
|
+
// Parse extension length (2 bytes, big-endian)
|
|
270
|
+
const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
271
|
+
log(`Extension length: ${extensionLength}`);
|
|
272
|
+
pos += 2;
|
|
273
|
+
// Extract extension data
|
|
274
|
+
if (pos + extensionLength > extensionsEnd) {
|
|
275
|
+
result.error = `Extension ${extensionType} data exceeds bounds`;
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
const extensionData = buffer.slice(pos, pos + extensionLength);
|
|
279
|
+
// Record all extensions
|
|
280
|
+
result.extensions.push({
|
|
281
|
+
type: extensionType,
|
|
282
|
+
length: extensionLength,
|
|
283
|
+
data: extensionData
|
|
284
|
+
});
|
|
285
|
+
// Track specific extension types
|
|
286
|
+
if (extensionType === TlsExtensionType.SERVER_NAME) {
|
|
287
|
+
// Server Name Indication (SNI)
|
|
288
|
+
this.parseServerNameExtension(extensionData, serverNames, logger);
|
|
289
|
+
}
|
|
290
|
+
else if (extensionType === TlsExtensionType.SESSION_TICKET) {
|
|
291
|
+
// Session ticket
|
|
292
|
+
result.hasSessionTicket = true;
|
|
293
|
+
}
|
|
294
|
+
else if (extensionType === TlsExtensionType.PRE_SHARED_KEY) {
|
|
295
|
+
// TLS 1.3 PSK
|
|
296
|
+
result.hasPsk = true;
|
|
297
|
+
}
|
|
298
|
+
else if (extensionType === TlsExtensionType.EARLY_DATA) {
|
|
299
|
+
// TLS 1.3 Early Data (0-RTT)
|
|
300
|
+
result.hasEarlyData = true;
|
|
301
|
+
}
|
|
302
|
+
// Move to next extension
|
|
303
|
+
pos += extensionLength;
|
|
304
|
+
}
|
|
305
|
+
// Store any server names found
|
|
306
|
+
if (serverNames.length > 0) {
|
|
307
|
+
result.serverNameList = serverNames;
|
|
308
|
+
}
|
|
309
|
+
// Mark as valid if we get here
|
|
310
|
+
result.isValid = true;
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
315
|
+
log(`Error parsing ClientHello: ${errorMessage}`);
|
|
316
|
+
result.error = errorMessage;
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Parses the server name extension data and extracts hostnames
|
|
322
|
+
*
|
|
323
|
+
* @param data Extension data buffer
|
|
324
|
+
* @param serverNames Array to populate with found server names
|
|
325
|
+
* @param logger Optional logging function
|
|
326
|
+
* @returns true if parsing succeeded
|
|
327
|
+
*/
|
|
328
|
+
static parseServerNameExtension(data, serverNames, logger) {
|
|
329
|
+
const log = logger || (() => { });
|
|
330
|
+
try {
|
|
331
|
+
// Need at least 2 bytes for server name list length
|
|
332
|
+
if (data.length < 2) {
|
|
333
|
+
log('SNI extension too small for server name list length');
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
// Parse server name list length (2 bytes)
|
|
337
|
+
const listLength = (data[0] << 8) + data[1];
|
|
338
|
+
// Skip to first name entry
|
|
339
|
+
let pos = 2;
|
|
340
|
+
// End of list
|
|
341
|
+
const listEnd = pos + listLength;
|
|
342
|
+
// Validate length
|
|
343
|
+
if (listEnd > data.length) {
|
|
344
|
+
log('SNI server name list exceeds extension data');
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
// Process all name entries
|
|
348
|
+
while (pos + 3 <= listEnd) {
|
|
349
|
+
// Name type (1 byte)
|
|
350
|
+
const nameType = data[pos];
|
|
351
|
+
pos += 1;
|
|
352
|
+
// For hostname, type must be 0
|
|
353
|
+
if (nameType !== 0) {
|
|
354
|
+
// Skip this entry
|
|
355
|
+
if (pos + 2 <= listEnd) {
|
|
356
|
+
const nameLength = (data[pos] << 8) + data[pos + 1];
|
|
357
|
+
pos += 2 + nameLength;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
log('Malformed SNI entry');
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Parse hostname length (2 bytes)
|
|
366
|
+
if (pos + 2 > listEnd) {
|
|
367
|
+
log('SNI extension truncated');
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const nameLength = (data[pos] << 8) + data[pos + 1];
|
|
371
|
+
pos += 2;
|
|
372
|
+
// Extract hostname
|
|
373
|
+
if (pos + nameLength > listEnd) {
|
|
374
|
+
log('SNI hostname truncated');
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
// Extract the hostname as UTF-8
|
|
378
|
+
try {
|
|
379
|
+
const hostname = data.slice(pos, pos + nameLength).toString('utf8');
|
|
380
|
+
log(`Found SNI hostname: ${hostname}`);
|
|
381
|
+
serverNames.push(hostname);
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
log(`Error extracting hostname: ${err}`);
|
|
385
|
+
}
|
|
386
|
+
// Move to next entry
|
|
387
|
+
pos += nameLength;
|
|
388
|
+
}
|
|
389
|
+
return serverNames.length > 0;
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
log(`Error parsing SNI extension: ${error}`);
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Determines if a ClientHello contains session resumption indicators
|
|
398
|
+
*
|
|
399
|
+
* @param buffer The ClientHello buffer
|
|
400
|
+
* @param logger Optional logging function
|
|
401
|
+
* @returns Session resumption result
|
|
402
|
+
*/
|
|
403
|
+
static hasSessionResumption(buffer, logger) {
|
|
404
|
+
const log = logger || (() => { });
|
|
405
|
+
if (!TlsUtils.isClientHello(buffer)) {
|
|
406
|
+
return { isResumption: false, hasSNI: false };
|
|
407
|
+
}
|
|
408
|
+
const parseResult = this.parseClientHello(buffer, logger);
|
|
409
|
+
if (!parseResult.isValid) {
|
|
410
|
+
log(`ClientHello parse failed: ${parseResult.error}`);
|
|
411
|
+
return { isResumption: false, hasSNI: false };
|
|
412
|
+
}
|
|
413
|
+
// Check resumption indicators
|
|
414
|
+
const hasSessionId = parseResult.hasSessionId;
|
|
415
|
+
const hasSessionTicket = parseResult.hasSessionTicket;
|
|
416
|
+
const hasPsk = parseResult.hasPsk;
|
|
417
|
+
const hasEarlyData = parseResult.hasEarlyData;
|
|
418
|
+
// Check for SNI
|
|
419
|
+
const hasSNI = !!parseResult.serverNameList && parseResult.serverNameList.length > 0;
|
|
420
|
+
// Consider it a resumption if any resumption mechanism is present
|
|
421
|
+
const isResumption = hasSessionTicket || hasPsk || hasEarlyData ||
|
|
422
|
+
(hasSessionId && !hasPsk); // Legacy resumption
|
|
423
|
+
// Log details
|
|
424
|
+
if (isResumption) {
|
|
425
|
+
log('Session resumption detected: ' +
|
|
426
|
+
(hasSessionTicket ? 'session ticket, ' : '') +
|
|
427
|
+
(hasPsk ? 'PSK, ' : '') +
|
|
428
|
+
(hasEarlyData ? 'early data, ' : '') +
|
|
429
|
+
(hasSessionId ? 'session ID' : '') +
|
|
430
|
+
(hasSNI ? ', with SNI' : ', without SNI'));
|
|
431
|
+
}
|
|
432
|
+
return { isResumption, hasSNI };
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Checks if a ClientHello appears to be from a tab reactivation
|
|
436
|
+
*
|
|
437
|
+
* @param buffer The ClientHello buffer
|
|
438
|
+
* @param logger Optional logging function
|
|
439
|
+
* @returns true if it appears to be a tab reactivation
|
|
440
|
+
*/
|
|
441
|
+
static isTabReactivationHandshake(buffer, logger) {
|
|
442
|
+
const log = logger || (() => { });
|
|
443
|
+
if (!TlsUtils.isClientHello(buffer)) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
// Parse the ClientHello
|
|
447
|
+
const parseResult = this.parseClientHello(buffer, logger);
|
|
448
|
+
if (!parseResult.isValid) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
// Tab reactivation pattern: session identifier + (ticket or PSK) but no SNI
|
|
452
|
+
const hasSessionId = parseResult.hasSessionId;
|
|
453
|
+
const hasSessionTicket = parseResult.hasSessionTicket;
|
|
454
|
+
const hasPsk = parseResult.hasPsk;
|
|
455
|
+
const hasSNI = !!parseResult.serverNameList && parseResult.serverNameList.length > 0;
|
|
456
|
+
if ((hasSessionId && (hasSessionTicket || hasPsk)) && !hasSNI) {
|
|
457
|
+
log('Detected tab reactivation pattern: session resumption without SNI');
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LWhlbGxvLXBhcnNlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3RzL3Byb3RvY29scy90bHMvc25pL2NsaWVudC1oZWxsby1wYXJzZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUNoQyxPQUFPLEVBQ0wsYUFBYSxFQUNiLGdCQUFnQixFQUNoQixnQkFBZ0IsRUFDaEIsUUFBUSxFQUNULE1BQU0sdUJBQXVCLENBQUM7QUFvRC9COztHQUVHO0FBQ0gsTUFBTSxPQUFPLGlCQUFpQjtJQUM1QixzREFBc0Q7YUFDdkMsc0JBQWlCLEdBQXNDLElBQUksR0FBRyxFQUFFLENBQUM7YUFDakUsb0JBQWUsR0FBVyxJQUFJLENBQUMsR0FBQywwQ0FBMEM7SUFFekY7O09BRUc7SUFDSyxNQUFNLENBQUMsdUJBQXVCO1FBQ3BDLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUN2QixLQUFLLE1BQU0sQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDLGlCQUFpQixDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUM7WUFDcEUsSUFBSSxHQUFHLEdBQUcsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUM7Z0JBQ2hELElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7WUFDOUMsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSSxNQUFNLENBQUMsMkJBQTJCLENBQ3ZDLE1BQWMsRUFDZCxZQUFvQixFQUNwQixNQUF1QjtRQUV2QixNQUFNLEdBQUcsR0FBRyxNQUFNLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRSxDQUFDLENBQUMsQ0FBQztRQUVqQywwQ0FBMEM7UUFDMUMsSUFBSSxDQUFDLHVCQUF1QixFQUFFLENB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
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TLS SNI (Server Name Indication) protocol utilities
|
|
3
|
+
*/
|
|
4
|
+
export * from './client-hello-parser.js';
|
|
5
|
+
export * from './sni-extraction.js';
|
|
6
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90cy9wcm90b2NvbHMvdGxzL3NuaS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyxxQkFBcUIsQ0FBQyJ9
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { type LoggerFunction } from './client-hello-parser.js';
|
|
3
|
+
/**
|
|
4
|
+
* Connection tracking information
|
|
5
|
+
*/
|
|
6
|
+
export interface ConnectionInfo {
|
|
7
|
+
sourceIp: string;
|
|
8
|
+
sourcePort: number;
|
|
9
|
+
destIp: string;
|
|
10
|
+
destPort: number;
|
|
11
|
+
timestamp?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Utilities for extracting SNI information from TLS handshakes
|
|
15
|
+
*/
|
|
16
|
+
export declare class SniExtraction {
|
|
17
|
+
/**
|
|
18
|
+
* Extracts the SNI (Server Name Indication) from a TLS ClientHello message.
|
|
19
|
+
*
|
|
20
|
+
* @param buffer The buffer containing the TLS ClientHello message
|
|
21
|
+
* @param logger Optional logging function
|
|
22
|
+
* @returns The extracted server name or undefined if not found
|
|
23
|
+
*/
|
|
24
|
+
static extractSNI(buffer: Buffer, logger?: LoggerFunction): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Attempts to extract SNI from the PSK extension in a TLS 1.3 ClientHello.
|
|
27
|
+
*
|
|
28
|
+
* In TLS 1.3, when a client attempts to resume a session, it may include
|
|
29
|
+
* the server name in the PSK identity hint rather than in the SNI extension.
|
|
30
|
+
*
|
|
31
|
+
* @param buffer The buffer containing the TLS ClientHello message
|
|
32
|
+
* @param logger Optional logging function
|
|
33
|
+
* @returns The extracted server name or undefined if not found
|
|
34
|
+
*/
|
|
35
|
+
static extractSNIFromPSKExtension(buffer: Buffer, logger?: LoggerFunction): string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Main entry point for SNI extraction with support for fragmented messages
|
|
38
|
+
* and session resumption edge cases.
|
|
39
|
+
*
|
|
40
|
+
* @param buffer The buffer containing TLS data
|
|
41
|
+
* @param connectionInfo Connection tracking information
|
|
42
|
+
* @param logger Optional logging function
|
|
43
|
+
* @param cachedSni Optional previously cached SNI value
|
|
44
|
+
* @returns The extracted server name or undefined
|
|
45
|
+
*/
|
|
46
|
+
static extractSNIWithResumptionSupport(buffer: Buffer, connectionInfo?: ConnectionInfo, logger?: LoggerFunction, cachedSni?: string): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Unified method for processing a TLS packet and extracting SNI.
|
|
49
|
+
* Main entry point for SNI extraction that handles all edge cases.
|
|
50
|
+
*
|
|
51
|
+
* @param buffer The buffer containing TLS data
|
|
52
|
+
* @param connectionInfo Connection tracking information
|
|
53
|
+
* @param logger Optional logging function
|
|
54
|
+
* @param cachedSni Optional previously cached SNI value
|
|
55
|
+
* @returns The extracted server name or undefined
|
|
56
|
+
*/
|
|
57
|
+
static processTlsPacket(buffer: Buffer, connectionInfo: ConnectionInfo, logger?: LoggerFunction, cachedSni?: string): string | undefined;
|
|
58
|
+
}
|