@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.
463 lines • 40.3 kB
JavaScript
import { Buffer } from 'buffer';
import { TlsRecordType, TlsHandshakeType, TlsExtensionType, 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LWhlbGxvLXBhcnNlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3RzL3Byb3RvY29scy90bHMvc25pL2NsaWVudC1oZWxsby1wYXJzZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUNoQyxPQUFPLEVBQ0wsYUFBYSxFQUNiLGdCQUFnQixFQUNoQixnQkFBZ0IsRUFDaEIsUUFBUSxFQUNULE1BQU0sdUJBQXVCLENBQUM7QUFvRC9COztHQUVHO0FBQ0gsTUFBTSxPQUFPLGlCQUFpQjtJQUM1QixzREFBc0Q7YUFDdkMsc0JBQWlCLEdBQXNDLElBQUksR0FBRyxFQUFFLENBQUM7YUFDakUsb0JBQWUsR0FBVyxJQUFJLENBQUMsR0FBQywwQ0FBMEM7SUFFekY7O09BRUc7SUFDSyxNQUFNLENBQUMsdUJBQXVCO1FBQ3BDLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUN2QixLQUFLLE1BQU0sQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDLGlCQUFpQixDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUM7WUFDcEUsSUFBSSxHQUFHLEdBQUcsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUM7Z0JBQ2hELElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7WUFDOUMsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSSxNQUFNLENBQUMsMkJBQTJCLENBQ3ZDLE1BQWMsRUFDZCxZQUFvQixFQUNwQixNQUF1QjtRQUV2QixNQUFNLEdBQUcsR0FBRyxNQUFNLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRSxDQUFDLENBQUMsQ0FBQztRQUVqQywwQ0FBMEM7UUFDMUMsSUFBSSxDQUFDLHVCQUF1QixFQUFFLENB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