import { Buffer } from 'buffer'; import { TlsRecordType, TlsHandshakeType, TlsExtensionType, TlsUtils } from '../utils/tls-utils.js'; import { ClientHelloParser, type LoggerFunction } from './client-hello-parser.js'; import { SniExtraction, type ConnectionInfo } from './sni-extraction.js'; /** * SNI (Server Name Indication) handler for TLS connections. * Provides robust extraction of SNI values from TLS ClientHello messages * with support for fragmented packets, TLS 1.3 resumption, Chrome-specific * connection behaviors, and tab hibernation/reactivation scenarios. * * This class retains the original API but leverages the new modular implementation * for better maintainability and testability. */ export class SniHandler { // Re-export constants for backward compatibility private static readonly TLS_HANDSHAKE_RECORD_TYPE = TlsRecordType.HANDSHAKE; private static readonly TLS_APPLICATION_DATA_TYPE = TlsRecordType.APPLICATION_DATA; private static readonly TLS_CLIENT_HELLO_HANDSHAKE_TYPE = TlsHandshakeType.CLIENT_HELLO; private static readonly TLS_SNI_EXTENSION_TYPE = TlsExtensionType.SERVER_NAME; private static readonly TLS_SESSION_TICKET_EXTENSION_TYPE = TlsExtensionType.SESSION_TICKET; private static readonly TLS_SNI_HOST_NAME_TYPE = 0; // NameType.HOST_NAME in RFC 6066 private static readonly TLS_PSK_EXTENSION_TYPE = TlsExtensionType.PRE_SHARED_KEY; private static readonly TLS_PSK_KE_MODES_EXTENSION_TYPE = TlsExtensionType.PSK_KEY_EXCHANGE_MODES; private static readonly TLS_EARLY_DATA_EXTENSION_TYPE = TlsExtensionType.EARLY_DATA; /** * Checks if a buffer contains a TLS handshake message (record type 22) * @param buffer - The buffer to check * @returns true if the buffer starts with a TLS handshake record type */ public static isTlsHandshake(buffer: Buffer): boolean { return TlsUtils.isTlsHandshake(buffer); } /** * Checks if a buffer contains TLS application data (record type 23) * @param buffer - The buffer to check * @returns true if the buffer starts with a TLS application data record type */ public static isTlsApplicationData(buffer: Buffer): boolean { return TlsUtils.isTlsApplicationData(buffer); } /** * Creates a connection ID based on source/destination information * Used to track fragmented ClientHello messages across multiple packets * * @param connectionInfo - Object containing connection identifiers (IP/port) * @returns A string ID for the connection */ public static createConnectionId(connectionInfo: { sourceIp?: string; sourcePort?: number; destIp?: string; destPort?: number; }): string { return TlsUtils.createConnectionId(connectionInfo); } /** * 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 enableLogging - Whether to enable logging * @returns A complete buffer if reassembly is successful, or undefined if more fragments are needed */ public static handleFragmentedClientHello( buffer: Buffer, connectionId: string, enableLogging: boolean = false ): Buffer | undefined { const logger = enableLogging ? (message: string) => console.log(`[SNI Fragment] ${message}`) : undefined; return ClientHelloParser.handleFragmentedClientHello(buffer, connectionId, logger); } /** * Checks if a buffer contains a TLS ClientHello message * @param buffer - The buffer to check * @returns true if the buffer appears to be a ClientHello message */ public static isClientHello(buffer: Buffer): boolean { return TlsUtils.isClientHello(buffer); } /** * Checks if a ClientHello message contains session resumption indicators * such as session tickets or PSK (Pre-Shared Key) extensions. * * @param buffer - The buffer containing a ClientHello message * @param enableLogging - Whether to enable logging * @returns Object containing details about session resumption and SNI presence */ public static hasSessionResumption( buffer: Buffer, enableLogging: boolean = false ): { isResumption: boolean; hasSNI: boolean } { const logger = enableLogging ? (message: string) => console.log(`[Session Resumption] ${message}`) : undefined; return ClientHelloParser.hasSessionResumption(buffer, logger); } /** * Detects characteristics of a tab reactivation TLS handshake * These often have specific patterns in Chrome and other browsers * * @param buffer - The buffer containing a ClientHello message * @param enableLogging - Whether to enable logging * @returns true if this appears to be a tab reactivation handshake */ public static isTabReactivationHandshake( buffer: Buffer, enableLogging: boolean = false ): boolean { const logger = enableLogging ? (message: string) => console.log(`[Tab Reactivation] ${message}`) : undefined; return ClientHelloParser.isTabReactivationHandshake(buffer, logger); } /** * Extracts the SNI (Server Name Indication) from a TLS ClientHello message. * Implements robust parsing with support for session resumption edge cases. * * @param buffer - The buffer containing the TLS ClientHello message * @param enableLogging - Whether to enable detailed debug logging * @returns The extracted server name or undefined if not found */ public static extractSNI(buffer: Buffer, enableLogging: boolean = false): string | undefined { const logger = enableLogging ? (message: string) => console.log(`[SNI Extraction] ${message}`) : undefined; return SniExtraction.extractSNI(buffer, logger); } /** * Attempts to extract SNI from the PSK extension in a TLS 1.3 ClientHello. * * In TLS 1.3, when a client attempts to resume a session, it may include * the server name in the PSK identity hint rather than in the SNI extension. * * @param buffer - The buffer containing the TLS ClientHello message * @param enableLogging - Whether to enable detailed debug logging * @returns The extracted server name or undefined if not found */ public static extractSNIFromPSKExtension( buffer: Buffer, enableLogging: boolean = false ): string | undefined { const logger = enableLogging ? (message: string) => console.log(`[PSK-SNI Extraction] ${message}`) : undefined; return SniExtraction.extractSNIFromPSKExtension(buffer, logger); } /** * Checks if the buffer contains TLS 1.3 early data (0-RTT) * @param buffer - The buffer to check * @param enableLogging - Whether to enable logging * @returns true if early data is detected */ public static hasEarlyData(buffer: Buffer, enableLogging: boolean = false): boolean { // This functionality has been moved to ClientHelloParser // We can implement it in terms of the parse result if needed const logger = enableLogging ? (message: string) => console.log(`[Early Data] ${message}`) : undefined; const parseResult = ClientHelloParser.parseClientHello(buffer, logger); return parseResult.isValid && parseResult.hasEarlyData; } /** * Attempts to extract SNI from an initial ClientHello packet and handles * session resumption edge cases more robustly than the standard extraction. * * This method handles: * 1. Standard SNI extraction * 2. TLS 1.3 PSK-based resumption (Chrome, Firefox, etc.) * 3. Session ticket-based resumption * 4. Fragmented ClientHello messages * 5. TLS 1.3 Early Data (0-RTT) * 6. Chrome's connection racing behaviors * * @param buffer - The buffer containing the TLS ClientHello message * @param connectionInfo - Optional connection information for fragment handling * @param enableLogging - Whether to enable detailed debug logging * @returns The extracted server name or undefined if not found or more data needed */ public static extractSNIWithResumptionSupport( buffer: Buffer, connectionInfo?: { sourceIp?: string; sourcePort?: number; destIp?: string; destPort?: number; }, enableLogging: boolean = false ): string | undefined { const logger = enableLogging ? (message: string) => console.log(`[SNI Extraction] ${message}`) : undefined; return SniExtraction.extractSNIWithResumptionSupport( buffer, connectionInfo as ConnectionInfo, logger ); } /** * Main entry point for SNI extraction that handles all edge cases. * This should be called for each TLS packet received from a client. * * The method uses connection tracking to handle fragmented ClientHello * messages and various TLS 1.3 behaviors, including Chrome's connection * racing patterns and tab reactivation behaviors. * * @param buffer - The buffer containing TLS data * @param connectionInfo - Connection metadata (IPs and ports) * @param enableLogging - Whether to enable detailed debug logging * @param cachedSni - Optional cached SNI from previous connections (for racing detection) * @returns The extracted server name or undefined if not found or more data needed */ public static processTlsPacket( buffer: Buffer, connectionInfo: { sourceIp: string; sourcePort: number; destIp: string; destPort: number; timestamp?: number; }, enableLogging: boolean = false, cachedSni?: string ): string | undefined { const logger = enableLogging ? (message: string) => console.log(`[TLS Packet] ${message}`) : undefined; return SniExtraction.processTlsPacket(buffer, connectionInfo, logger, cachedSni); } }