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.

294 lines 22.8 kB
/** * TLS Protocol Parser * Generic TLS parsing utilities separated from implementation logic */ import { Buffer } from 'buffer'; import { TlsRecordType, TlsHandshakeType, TlsExtensionType } from './constants.js'; /** * TLS protocol parser utilities */ export class TlsParser { /** * Checks if a buffer contains a TLS handshake record */ static isTlsHandshake(buffer) { return buffer.length > 0 && buffer[0] === TlsRecordType.HANDSHAKE; } /** * Checks if a buffer contains a TLS ClientHello message */ static isClientHello(buffer) { // Minimum ClientHello size (TLS record header + handshake header) if (buffer.length < 9) { return false; } // Check record type (must be TLS_HANDSHAKE_RECORD_TYPE) if (buffer[0] !== TlsRecordType.HANDSHAKE) { return false; } // Check handshake type at byte 5 (must be CLIENT_HELLO) return buffer[5] === TlsHandshakeType.CLIENT_HELLO; } /** * Gets the record length from a TLS record header */ static getTlsRecordLength(buffer) { if (buffer.length < 5) { return -1; } // Bytes 3-4 contain the record length (big-endian) return (buffer[3] << 8) + buffer[4]; } /** * Parses a TLS ClientHello message and extracts all components */ static parseClientHello(buffer) { 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]; // Parse record length (bytes 3-4, big-endian) const recordLength = (buffer[3] << 8) + buffer[4]; // 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]; // Skip handshake length (3 bytes) pos += 3; // 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); // 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]; 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); } // 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]; 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]; 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]; 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]; pos += 2; // Parse extension length (2 bytes, big-endian) const extensionLength = (buffer[pos] << 8) + buffer[pos + 1]; 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, data: extensionData }); // Track specific extension types if (extensionType === TlsExtensionType.SERVER_NAME) { // Server Name Indication (SNI) const sniNames = this.parseServerNameExtension(extensionData); serverNames.push(...sniNames); } 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); result.error = errorMessage; return result; } } /** * Parses the server name extension data and extracts hostnames */ static parseServerNameExtension(data) { const serverNames = []; try { // Need at least 2 bytes for server name list length if (data.length < 2) { return serverNames; } // 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) { return serverNames; } // 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 { return serverNames; } } // Parse hostname length (2 bytes) if (pos + 2 > listEnd) { return serverNames; } const nameLength = (data[pos] << 8) + data[pos + 1]; pos += 2; // Extract hostname if (pos + nameLength > listEnd) { return serverNames; } // Extract the hostname as UTF-8 try { const hostname = data.slice(pos, pos + nameLength).toString('utf8'); serverNames.push(hostname); } catch (err) { // Ignore invalid hostnames } // Move to next entry pos += nameLength; } return serverNames; } catch (error) { return serverNames; } } /** * Extract SNI (Server Name Indication) from ClientHello */ static extractSNI(buffer) { const parseResult = this.parseClientHello(buffer); if (!parseResult.isValid || !parseResult.serverNameList || parseResult.serverNameList.length === 0) { return null; } return parseResult.serverNameList[0]; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGFyc2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvcHJvdG9jb2xzL3Rscy9wYXJzZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBRUgsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUNoQyxPQUFPLEVBQUUsYUFBYSxFQUFFLGdCQUFnQixFQUFFLGdCQUFnQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFzQm5GOztHQUVHO0FBQ0gsTUFBTSxPQUFPLFNBQVM7SUFDcEI7O09BRUc7SUFDSCxNQUFNLENBQUMsY0FBYyxDQUFDLE1BQWM7UUFDbEMsT0FBTyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsSUFBSSxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssYUFBYSxDQUFDLFNBQVMsQ0FBQztJQUNwRSxDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENBQUMsYUFBYSxDQUFDLE1BQWM7UUFDakMsa0VBQWtFO1FBQ2xFLElBQUksTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUN0QixPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFFRCx3REFBd0Q7UUFDeEQsSUFBSSxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssYUFBYSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQzFDLE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztRQUVELHdEQUF3RDtRQUN4RCxPQUFPLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxnQkFBZ0IsQ0FBQyxZQUFZLENBQUM7SUFDckQsQ0FBQztJQUVEOztPQUVHO0lBQ0gsTUFBTSxDQUFDLGtCQUFrQixDQUFDLE1BQWM7UUFDdEMsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ3RCLE9BQU8sQ0FBQyxDQUFDLENBQUM7UUFDWixDQUFDO1FBRUQsbURBQW1EO1FBQ25ELE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFD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