@push.rocks/smartproxy 3.37.1 → 3.37.3
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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.portproxy.js +121 -63
- package/dist_ts/classes.snihandler.d.ts +17 -2
- package/dist_ts/classes.snihandler.js +176 -8
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.portproxy.ts +142 -80
- package/ts/classes.snihandler.ts +195 -7
package/ts/classes.snihandler.ts
CHANGED
|
@@ -11,6 +11,8 @@ export class SniHandler {
|
|
|
11
11
|
private static readonly TLS_SNI_EXTENSION_TYPE = 0x0000;
|
|
12
12
|
private static readonly TLS_SESSION_TICKET_EXTENSION_TYPE = 0x0023;
|
|
13
13
|
private static readonly TLS_SNI_HOST_NAME_TYPE = 0;
|
|
14
|
+
private static readonly TLS_PSK_EXTENSION_TYPE = 0x0029; // Pre-Shared Key extension type for TLS 1.3
|
|
15
|
+
private static readonly TLS_PSK_KE_MODES_EXTENSION_TYPE = 0x002D; // PSK Key Exchange Modes
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Checks if a buffer contains a TLS handshake message (record type 22)
|
|
@@ -178,6 +180,7 @@ export class SniHandler {
|
|
|
178
180
|
|
|
179
181
|
// Track if we found session tickets (for improved resumption handling)
|
|
180
182
|
let hasSessionTicket = false;
|
|
183
|
+
let hasPskExtension = false;
|
|
181
184
|
|
|
182
185
|
// Iterate through extensions
|
|
183
186
|
while (pos + 4 <= extensionsEnd) {
|
|
@@ -275,15 +278,21 @@ export class SniHandler {
|
|
|
275
278
|
log('Found session ticket extension');
|
|
276
279
|
hasSessionTicket = true;
|
|
277
280
|
pos += extensionLength; // Skip this extension
|
|
281
|
+
} else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
|
|
282
|
+
// TLS 1.3 PSK extension - mark for resumption support
|
|
283
|
+
log('Found PSK extension (TLS 1.3 resumption indicator)');
|
|
284
|
+
hasPskExtension = true;
|
|
285
|
+
// We'll skip the extension here and process it separately if needed
|
|
286
|
+
pos += extensionLength;
|
|
278
287
|
} else {
|
|
279
288
|
// Skip this extension
|
|
280
289
|
pos += extensionLength;
|
|
281
290
|
}
|
|
282
291
|
}
|
|
283
292
|
|
|
284
|
-
// Log if we found
|
|
285
|
-
if (hasSessionTicket) {
|
|
286
|
-
log('Session
|
|
293
|
+
// Log if we found session resumption indicators but no SNI
|
|
294
|
+
if (hasSessionTicket || hasPskExtension) {
|
|
295
|
+
log('Session resumption indicators present but no SNI found');
|
|
287
296
|
}
|
|
288
297
|
|
|
289
298
|
log('No SNI extension found in ClientHello');
|
|
@@ -294,12 +303,177 @@ export class SniHandler {
|
|
|
294
303
|
}
|
|
295
304
|
}
|
|
296
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Attempts to extract SNI from the PSK extension in a TLS 1.3 ClientHello.
|
|
308
|
+
*
|
|
309
|
+
* In TLS 1.3, when a client attempts to resume a session, it may include
|
|
310
|
+
* the server name in the PSK identity hint rather than in the SNI extension.
|
|
311
|
+
*
|
|
312
|
+
* @param buffer - The buffer containing the TLS ClientHello message
|
|
313
|
+
* @param enableLogging - Whether to enable detailed debug logging
|
|
314
|
+
* @returns The extracted server name or undefined if not found
|
|
315
|
+
*/
|
|
316
|
+
public static extractSNIFromPSKExtension(
|
|
317
|
+
buffer: Buffer,
|
|
318
|
+
enableLogging: boolean = false
|
|
319
|
+
): string | undefined {
|
|
320
|
+
const log = (message: string) => {
|
|
321
|
+
if (enableLogging) {
|
|
322
|
+
console.log(`[PSK-SNI Extraction] ${message}`);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
// Ensure this is a ClientHello
|
|
328
|
+
if (!this.isClientHello(buffer)) {
|
|
329
|
+
log('Not a ClientHello message');
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Find the start position of extensions
|
|
334
|
+
let pos = 5; // Start after record header
|
|
335
|
+
|
|
336
|
+
// Skip handshake type (1 byte)
|
|
337
|
+
pos += 1;
|
|
338
|
+
|
|
339
|
+
// Skip handshake length (3 bytes)
|
|
340
|
+
pos += 3;
|
|
341
|
+
|
|
342
|
+
// Skip client version (2 bytes)
|
|
343
|
+
pos += 2;
|
|
344
|
+
|
|
345
|
+
// Skip client random (32 bytes)
|
|
346
|
+
pos += 32;
|
|
347
|
+
|
|
348
|
+
// Skip session ID
|
|
349
|
+
if (pos + 1 > buffer.length) return undefined;
|
|
350
|
+
const sessionIdLength = buffer[pos];
|
|
351
|
+
pos += 1 + sessionIdLength;
|
|
352
|
+
|
|
353
|
+
// Skip cipher suites
|
|
354
|
+
if (pos + 2 > buffer.length) return undefined;
|
|
355
|
+
const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
356
|
+
pos += 2 + cipherSuitesLength;
|
|
357
|
+
|
|
358
|
+
// Skip compression methods
|
|
359
|
+
if (pos + 1 > buffer.length) return undefined;
|
|
360
|
+
const compressionMethodsLength = buffer[pos];
|
|
361
|
+
pos += 1 + compressionMethodsLength;
|
|
362
|
+
|
|
363
|
+
// Check if we have extensions
|
|
364
|
+
if (pos + 2 > buffer.length) {
|
|
365
|
+
log('No extensions present');
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Get extensions length
|
|
370
|
+
const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
371
|
+
pos += 2;
|
|
372
|
+
|
|
373
|
+
// Extensions end position
|
|
374
|
+
const extensionsEnd = pos + extensionsLength;
|
|
375
|
+
if (extensionsEnd > buffer.length) return undefined;
|
|
376
|
+
|
|
377
|
+
// Look for PSK extension
|
|
378
|
+
while (pos + 4 <= extensionsEnd) {
|
|
379
|
+
const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
|
|
380
|
+
pos += 2;
|
|
381
|
+
|
|
382
|
+
const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
383
|
+
pos += 2;
|
|
384
|
+
|
|
385
|
+
if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
|
|
386
|
+
log('Found PSK extension');
|
|
387
|
+
|
|
388
|
+
// PSK extension structure:
|
|
389
|
+
// 2 bytes: identities list length
|
|
390
|
+
if (pos + 2 > extensionsEnd) break;
|
|
391
|
+
const identitiesLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
392
|
+
pos += 2;
|
|
393
|
+
|
|
394
|
+
// End of identities list
|
|
395
|
+
const identitiesEnd = pos + identitiesLength;
|
|
396
|
+
if (identitiesEnd > extensionsEnd) break;
|
|
397
|
+
|
|
398
|
+
// Process each PSK identity
|
|
399
|
+
while (pos + 2 <= identitiesEnd) {
|
|
400
|
+
// Identity length (2 bytes)
|
|
401
|
+
if (pos + 2 > identitiesEnd) break;
|
|
402
|
+
const identityLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
403
|
+
pos += 2;
|
|
404
|
+
|
|
405
|
+
if (pos + identityLength > identitiesEnd) break;
|
|
406
|
+
|
|
407
|
+
// Try to extract hostname from identity
|
|
408
|
+
// Chrome often embeds the hostname in the PSK identity
|
|
409
|
+
// This is a heuristic as there's no standard format
|
|
410
|
+
if (identityLength > 0) {
|
|
411
|
+
const identity = buffer.slice(pos, pos + identityLength);
|
|
412
|
+
|
|
413
|
+
// Skip identity bytes
|
|
414
|
+
pos += identityLength;
|
|
415
|
+
|
|
416
|
+
// Skip obfuscated ticket age (4 bytes)
|
|
417
|
+
pos += 4;
|
|
418
|
+
|
|
419
|
+
// Try to parse the identity as UTF-8
|
|
420
|
+
try {
|
|
421
|
+
const identityStr = identity.toString('utf8');
|
|
422
|
+
log(`PSK identity: ${identityStr}`);
|
|
423
|
+
|
|
424
|
+
// Check if the identity contains hostname hints
|
|
425
|
+
// Chrome often embeds the hostname in a known format
|
|
426
|
+
// Try to extract using common patterns
|
|
427
|
+
|
|
428
|
+
// Pattern 1: Look for domain name pattern
|
|
429
|
+
const domainPattern = /([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?/i;
|
|
430
|
+
const domainMatch = identityStr.match(domainPattern);
|
|
431
|
+
if (domainMatch && domainMatch[0]) {
|
|
432
|
+
log(`Found domain in PSK identity: ${domainMatch[0]}`);
|
|
433
|
+
return domainMatch[0];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Pattern 2: Chrome sometimes uses a specific format with delimiters
|
|
437
|
+
// This is a heuristic approach since the format isn't standardized
|
|
438
|
+
const parts = identityStr.split('|');
|
|
439
|
+
if (parts.length > 1) {
|
|
440
|
+
for (const part of parts) {
|
|
441
|
+
if (part.includes('.') && !part.includes('/')) {
|
|
442
|
+
const possibleDomain = part.trim();
|
|
443
|
+
if (/^[a-z0-9.-]+$/i.test(possibleDomain)) {
|
|
444
|
+
log(`Found possible domain in PSK delimiter format: ${possibleDomain}`);
|
|
445
|
+
return possibleDomain;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
} catch (e) {
|
|
451
|
+
log('Failed to parse PSK identity as UTF-8');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
// Skip this extension
|
|
457
|
+
pos += extensionLength;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
log('No hostname found in PSK extension');
|
|
462
|
+
return undefined;
|
|
463
|
+
} catch (error) {
|
|
464
|
+
log(`Error parsing PSK: ${error instanceof Error ? error.message : String(error)}`);
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
297
469
|
/**
|
|
298
470
|
* Attempts to extract SNI from an initial ClientHello packet and handles
|
|
299
471
|
* session resumption edge cases more robustly than the standard extraction.
|
|
300
472
|
*
|
|
301
|
-
* This method
|
|
302
|
-
*
|
|
473
|
+
* This method handles:
|
|
474
|
+
* 1. Standard SNI extraction
|
|
475
|
+
* 2. TLS 1.3 PSK-based resumption (Chrome, Firefox, etc.)
|
|
476
|
+
* 3. Session ticket-based resumption
|
|
303
477
|
*
|
|
304
478
|
* @param buffer - The buffer containing the TLS ClientHello message
|
|
305
479
|
* @param enableLogging - Whether to enable detailed debug logging
|
|
@@ -312,6 +486,9 @@ export class SniHandler {
|
|
|
312
486
|
// First try the standard SNI extraction
|
|
313
487
|
const standardSni = this.extractSNI(buffer, enableLogging);
|
|
314
488
|
if (standardSni) {
|
|
489
|
+
if (enableLogging) {
|
|
490
|
+
console.log(`[SNI Extraction] Found standard SNI: ${standardSni}`);
|
|
491
|
+
}
|
|
315
492
|
return standardSni;
|
|
316
493
|
}
|
|
317
494
|
|
|
@@ -322,8 +499,19 @@ export class SniHandler {
|
|
|
322
499
|
console.log('[SNI Extraction] Detected ClientHello without standard SNI, possible session resumption');
|
|
323
500
|
}
|
|
324
501
|
|
|
325
|
-
//
|
|
326
|
-
|
|
502
|
+
// Try to extract from PSK extension (TLS 1.3 resumption)
|
|
503
|
+
const pskSni = this.extractSNIFromPSKExtension(buffer, enableLogging);
|
|
504
|
+
if (pskSni) {
|
|
505
|
+
if (enableLogging) {
|
|
506
|
+
console.log(`[SNI Extraction] Extracted SNI from PSK extension: ${pskSni}`);
|
|
507
|
+
}
|
|
508
|
+
return pskSni;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Could add more browser-specific heuristics here if needed
|
|
512
|
+
if (enableLogging) {
|
|
513
|
+
console.log('[SNI Extraction] Failed to extract SNI from resumption mechanisms');
|
|
514
|
+
}
|
|
327
515
|
}
|
|
328
516
|
|
|
329
517
|
return undefined;
|