UNPKG

@push.rocks/smartproxy

Version:

A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.

464 lines 40.3 kB
import { Buffer } from 'buffer'; import { TlsRecordType, TlsHandshakeType, TlsExtensionType } from '../../protocols/tls/index.js'; import { TlsUtils } from '../utils/tls-utils.js'; /** * Class for parsing TLS ClientHello messages */ export class ClientHelloParser { // Buffer for handling fragmented ClientHello messages static { this.fragmentedBuffers = new Map(); } static { this.fragmentTimeout = 1000; } // ms to wait for fragments before cleanup /** * Clean up expired fragments */ static cleanupExpiredFragments() { const now = Date.now(); for (const [connectionId, info] of this.fragmentedBuffers.entries()) { if (now - info.timestamp > this.fragmentTimeout) { this.fragmentedBuffers.delete(connectionId); } } } /** * Handles potential fragmented ClientHello messages by buffering and reassembling * TLS record fragments that might span multiple TCP packets. * * @param buffer The current buffer fragment * @param connectionId Unique identifier for the connection * @param logger Optional logging function * @returns A complete buffer if reassembly is successful, or undefined if more fragments are needed */ static handleFragmentedClientHello(buffer, connectionId, logger) { const log = logger || (() => { }); // Periodically clean up expired fragments this.cleanupExpiredFragments(); // Check if we've seen this connection before if (!this.fragmentedBuffers.has(connectionId)) { // New connection, start with this buffer this.fragmentedBuffers.set(connectionId, { buffer, timestamp: Date.now(), connectionId }); // Evaluate if this buffer already contains a complete ClientHello try { if (buffer.length >= 5) { // Get the record length from TLS header const recordLength = (buffer[3] << 8) + buffer[4] + 5; // +5 for the TLS record header itself log(`Initial buffer size: ${buffer.length}, expected record length: ${recordLength}`); // Check if this buffer already contains a complete TLS record if (buffer.length >= recordLength) { log(`Initial buffer contains complete ClientHello, length: ${buffer.length}`); return buffer; } } else { log(`Initial buffer too small (${buffer.length} bytes), needs at least 5 bytes for TLS header`); } } catch (e) { log(`Error checking initial buffer completeness: ${e}`); } log(`Started buffering connection ${connectionId}, initial size: ${buffer.length}`); return undefined; // Need more fragments } else { // Existing connection, append this buffer const existingInfo = this.fragmentedBuffers.get(connectionId); const newBuffer = Buffer.concat([existingInfo.buffer, buffer]); // Update the buffer and timestamp this.fragmentedBuffers.set(connectionId, { ...existingInfo, buffer: newBuffer, timestamp: Date.now() }); log(`Appended to buffer for ${connectionId}, new size: ${newBuffer.length}`); // Check if we now have a complete ClientHello try { if (newBuffer.length >= 5) { // Get the record length from TLS header const recordLength = (newBuffer[3] << 8) + newBuffer[4] + 5; // +5 for the TLS record header itself log(`Reassembled buffer size: ${newBuffer.length}, expected record length: ${recordLength}`); // Check if we have a complete TLS record now if (newBuffer.length >= recordLength) { log(`Assembled complete ClientHello, length: ${newBuffer.length}, needed: ${recordLength}`); // Extract the complete TLS record (might be followed by more data) const completeRecord = newBuffer.slice(0, recordLength); // Check if this record is indeed a ClientHello (type 1) at position 5 if (completeRecord.length > 5 && completeRecord[5] === TlsHandshakeType.CLIENT_HELLO) { log(`Verified record is a ClientHello handshake message`); // Complete message received, remove from tracking this.fragmentedBuffers.delete(connectionId); return completeRecord; } else { log(`Record is complete but not a ClientHello handshake, continuing to buffer`); // This might be another TLS record type preceding the ClientHello // Try checking for a ClientHello starting at the end of this record if (newBuffer.length > recordLength + 5) { const nextRecordType = newBuffer[recordLength]; log(`Next record type: ${nextRecordType} (looking for ${TlsRecordType.HANDSHAKE})`); if (nextRecordType === TlsRecordType.HANDSHAKE) { const handshakeType = newBuffer[recordLength + 5]; log(`Next handshake type: ${handshakeType} (looking for ${TlsHandshakeType.CLIENT_HELLO})`); if (handshakeType === TlsHandshakeType.CLIENT_HELLO) { // Found a ClientHello in the next record, return the entire buffer log(`Found ClientHello in subsequent record, returning full buffer`); this.fragmentedBuffers.delete(connectionId); return newBuffer; } } } } } } } catch (e) { log(`Error checking reassembled buffer completeness: ${e}`); } return undefined; // Still need more fragments } } /** * Parses a TLS ClientHello message and extracts all components * * @param buffer The buffer containing the ClientHello message * @param logger Optional logging function * @returns Parsed ClientHello or undefined if parsing failed */ static parseClientHello(buffer, logger) { const log = logger || (() => { }); const result = { isValid: false, hasSessionId: false, extensions: [], hasSessionTicket: false, hasPsk: false, hasEarlyData: false }; try { // Check basic validity if (buffer.length < 5) { result.error = 'Buffer too small for TLS record header'; return result; } // Check record type (must be HANDSHAKE) if (buffer[0] !== TlsRecordType.HANDSHAKE) { result.error = `Not a TLS handshake record: ${buffer[0]}`; return result; } // Get TLS version from record header const majorVersion = buffer[1]; const minorVersion = buffer[2]; result.version = [majorVersion, minorVersion]; log(`TLS record version: ${majorVersion}.${minorVersion}`); // Parse record length (bytes 3-4, big-endian) const recordLength = (buffer[3] << 8) + buffer[4]; log(`Record length: ${recordLength}`); // Validate record length against buffer size if (buffer.length < recordLength + 5) { result.error = 'Buffer smaller than expected record length'; return result; } // Start of handshake message in the buffer let pos = 5; // Check handshake type (must be CLIENT_HELLO) if (buffer[pos] !== TlsHandshakeType.CLIENT_HELLO) { result.error = `Not a ClientHello message: ${buffer[pos]}`; return result; } // Skip handshake type (1 byte) pos += 1; // Parse handshake length (3 bytes, big-endian) const handshakeLength = (buffer[pos] << 16) + (buffer[pos + 1] << 8) + buffer[pos + 2]; log(`Handshake length: ${handshakeLength}`); // Skip handshake length (3 bytes) pos += 3; // Check client version (2 bytes) const clientMajorVersion = buffer[pos]; const clientMinorVersion = buffer[pos + 1]; log(`Client version: ${clientMajorVersion}.${clientMinorVersion}`); // Skip client version (2 bytes) pos += 2; // Extract client random (32 bytes) if (pos + 32 > buffer.length) { result.error = 'Buffer too small for client random'; return result; } result.random = buffer.slice(pos, pos + 32); log(`Client random: ${result.random.toString('hex')}`); // Skip client random (32 bytes) pos += 32; // Parse session ID if (pos + 1 > buffer.length) { result.error = 'Buffer too small for session ID length'; return result; } const sessionIdLength = buffer[pos]; log(`Session ID length: ${sessionIdLength}`); pos += 1; result.hasSessionId = sessionIdLength > 0; if (sessionIdLength > 0) { if (pos + sessionIdLength > buffer.length) { result.error = 'Buffer too small for session ID'; return result; } result.sessionId = buffer.slice(pos, pos + sessionIdLength); log(`Session ID: ${result.sessionId.toString('hex')}`); } // Skip session ID pos += sessionIdLength; // Check if we have enough bytes left for cipher suites if (pos + 2 > buffer.length) { result.error = 'Buffer too small for cipher suites length'; return result; } // Parse cipher suites length (2 bytes, big-endian) const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1]; log(`Cipher suites length: ${cipherSuitesLength}`); pos += 2; // Extract cipher suites if (pos + cipherSuitesLength > buffer.length) { result.error = 'Buffer too small for cipher suites'; return result; } result.cipherSuites = buffer.slice(pos, pos + cipherSuitesLength); // Skip cipher suites pos += cipherSuitesLength; // Check if we have enough bytes left for compression methods if (pos + 1 > buffer.length) { result.error = 'Buffer too small for compression methods length'; return result; } // Parse compression methods length (1 byte) const compressionMethodsLength = buffer[pos]; log(`Compression methods length: ${compressionMethodsLength}`); pos += 1; // Extract compression methods if (pos + compressionMethodsLength > buffer.length) { result.error = 'Buffer too small for compression methods'; return result; } result.compressionMethods = buffer.slice(pos, pos + compressionMethodsLength); // Skip compression methods pos += compressionMethodsLength; // Check if we have enough bytes for extensions length if (pos + 2 > buffer.length) { // No extensions present - this is valid for older TLS versions result.isValid = true; return result; } // Parse extensions length (2 bytes, big-endian) const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1]; log(`Extensions length: ${extensionsLength}`); pos += 2; // Extensions end position const extensionsEnd = pos + extensionsLength; // Check if extensions length is valid if (extensionsEnd > buffer.length) { result.error = 'Extensions length exceeds buffer size'; return result; } // Iterate through extensions const serverNames = []; while (pos + 4 <= extensionsEnd) { // Parse extension type (2 bytes, big-endian) const extensionType = (buffer[pos] << 8) + buffer[pos + 1]; log(`Extension type: 0x${extensionType.toString(16).padStart(4, '0')}`); pos += 2; // Parse extension length (2 bytes, big-endian) const extensionLength = (buffer[pos] << 8) + buffer[pos + 1]; log(`Extension length: ${extensionLength}`); pos += 2; // Extract extension data if (pos + extensionLength > extensionsEnd) { result.error = `Extension ${extensionType} data exceeds bounds`; return result; } const extensionData = buffer.slice(pos, pos + extensionLength); // Record all extensions result.extensions.push({ type: extensionType, length: extensionLength, data: extensionData }); // Track specific extension types if (extensionType === TlsExtensionType.SERVER_NAME) { // Server Name Indication (SNI) this.parseServerNameExtension(extensionData, serverNames, logger); } else if (extensionType === TlsExtensionType.SESSION_TICKET) { // Session ticket result.hasSessionTicket = true; } else if (extensionType === TlsExtensionType.PRE_SHARED_KEY) { // TLS 1.3 PSK result.hasPsk = true; } else if (extensionType === TlsExtensionType.EARLY_DATA) { // TLS 1.3 Early Data (0-RTT) result.hasEarlyData = true; } // Move to next extension pos += extensionLength; } // Store any server names found if (serverNames.length > 0) { result.serverNameList = serverNames; } // Mark as valid if we get here result.isValid = true; return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log(`Error parsing ClientHello: ${errorMessage}`); result.error = errorMessage; return result; } } /** * Parses the server name extension data and extracts hostnames * * @param data Extension data buffer * @param serverNames Array to populate with found server names * @param logger Optional logging function * @returns true if parsing succeeded */ static parseServerNameExtension(data, serverNames, logger) { const log = logger || (() => { }); try { // Need at least 2 bytes for server name list length if (data.length < 2) { log('SNI extension too small for server name list length'); return false; } // Parse server name list length (2 bytes) const listLength = (data[0] << 8) + data[1]; // Skip to first name entry let pos = 2; // End of list const listEnd = pos + listLength; // Validate length if (listEnd > data.length) { log('SNI server name list exceeds extension data'); return false; } // Process all name entries while (pos + 3 <= listEnd) { // Name type (1 byte) const nameType = data[pos]; pos += 1; // For hostname, type must be 0 if (nameType !== 0) { // Skip this entry if (pos + 2 <= listEnd) { const nameLength = (data[pos] << 8) + data[pos + 1]; pos += 2 + nameLength; continue; } else { log('Malformed SNI entry'); return false; } } // Parse hostname length (2 bytes) if (pos + 2 > listEnd) { log('SNI extension truncated'); return false; } const nameLength = (data[pos] << 8) + data[pos + 1]; pos += 2; // Extract hostname if (pos + nameLength > listEnd) { log('SNI hostname truncated'); return false; } // Extract the hostname as UTF-8 try { const hostname = data.slice(pos, pos + nameLength).toString('utf8'); log(`Found SNI hostname: ${hostname}`); serverNames.push(hostname); } catch (err) { log(`Error extracting hostname: ${err}`); } // Move to next entry pos += nameLength; } return serverNames.length > 0; } catch (error) { log(`Error parsing SNI extension: ${error}`); return false; } } /** * Determines if a ClientHello contains session resumption indicators * * @param buffer The ClientHello buffer * @param logger Optional logging function * @returns Session resumption result */ static hasSessionResumption(buffer, logger) { const log = logger || (() => { }); if (!TlsUtils.isClientHello(buffer)) { return { isResumption: false, hasSNI: false }; } const parseResult = this.parseClientHello(buffer, logger); if (!parseResult.isValid) { log(`ClientHello parse failed: ${parseResult.error}`); return { isResumption: false, hasSNI: false }; } // Check resumption indicators const hasSessionId = parseResult.hasSessionId; const hasSessionTicket = parseResult.hasSessionTicket; const hasPsk = parseResult.hasPsk; const hasEarlyData = parseResult.hasEarlyData; // Check for SNI const hasSNI = !!parseResult.serverNameList && parseResult.serverNameList.length > 0; // Consider it a resumption if any resumption mechanism is present const isResumption = hasSessionTicket || hasPsk || hasEarlyData || (hasSessionId && !hasPsk); // Legacy resumption // Log details if (isResumption) { log('Session resumption detected: ' + (hasSessionTicket ? 'session ticket, ' : '') + (hasPsk ? 'PSK, ' : '') + (hasEarlyData ? 'early data, ' : '') + (hasSessionId ? 'session ID' : '') + (hasSNI ? ', with SNI' : ', without SNI')); } return { isResumption, hasSNI }; } /** * Checks if a ClientHello appears to be from a tab reactivation * * @param buffer The ClientHello buffer * @param logger Optional logging function * @returns true if it appears to be a tab reactivation */ static isTabReactivationHandshake(buffer, logger) { const log = logger || (() => { }); if (!TlsUtils.isClientHello(buffer)) { return false; } // Parse the ClientHello const parseResult = this.parseClientHello(buffer, logger); if (!parseResult.isValid) { return false; } // Tab reactivation pattern: session identifier + (ticket or PSK) but no SNI const hasSessionId = parseResult.hasSessionId; const hasSessionTicket = parseResult.hasSessionTicket; const hasPsk = parseResult.hasPsk; const hasSNI = !!parseResult.serverNameList && parseResult.serverNameList.length > 0; if ((hasSessionId && (hasSessionTicket || hasPsk)) && !hasSNI) { log('Detected tab reactivation pattern: session resumption without SNI'); return true; } return false; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LWhlbGxvLXBhcnNlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL3Rscy9zbmkvY2xpZW50LWhlbGxvLXBhcnNlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQ2hDLE9BQU8sRUFDTCxhQUFhLEVBQ2IsZ0JBQWdCLEVBQ2hCLGdCQUFnQixFQUNqQixNQUFNLDhCQUE4QixDQUFDO0FBQ3RDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQW9EakQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8saUJBQWlCO0lBQzVCLHNEQUFzRDthQUN2QyxzQkFBaUIsR0FBc0MsSUFBSSxHQUFHLEVBQUUsQ0FBQzthQUNqRSxvQkFBZSxHQUFXLElBQUksQ0FBQyxHQUFDLDBDQUEwQztJQUV6Rjs7T0FFRztJQUNLLE1BQU0sQ0FBQyx1QkFBdUI7UUFDcEMsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLEtBQUssTUFBTSxDQUFDLFlBQVksRUFBRSxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsaUJBQWlCLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQztZQUNwRSxJQUFJLEdBQUcsR0FBRyxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQyxlQUFlLEVBQUUsQ0FBQztnQkFDaEQsSUFBSSxDQUFDLGlCQUFpQixDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQztZQUM5QyxDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7Ozs7T0FRRztJQUNJLE1BQU0sQ0FBQywyQkFBMkIsQ0FDdkMsTUFBYyxFQUNkLFlBQW9CLEVBQ3BCLE1BQXVCO1FBRXZCLE1BQU0sR0FBRyxHQUFHLE1BQU0sSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQyxDQUFDO1FBRWpDLDBDQUEwQztRQUMxQyxJQUFJ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