/** * TLS fingerprint spoofing — Chrome JA3 cipher suite ordering. * * Node.js TLS doesn't expose JA3 hash control directly, but ordering * cipher suites to match Chrome's TLS ClientHello reduces fingerprint * detectability vs the default OpenSSL order. * * For full JA3 bypass, pair with the browser-stealth engine (Camoufox). */ import { Agent as HttpsAgent } from 'node:https'; import { connect as tlsConnect } from 'node:tls'; // Chrome 120+ TLS 1.3 + 1.2 cipher suite order export const CHROME_CIPHERS = [ 'TLS_AES_128_GCM_SHA256', 'TLS_AES_256_GCM_SHA384', 'TLS_CHACHA20_POLY1305_SHA256', 'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-CHACHA20-POLY1305', 'ECDHE-RSA-CHACHA20-POLY1305', 'ECDHE-RSA-AES128-SHA', 'ECDHE-RSA-AES256-SHA', 'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'AES128-SHA', 'AES256-SHA', ].join(':'); // Chrome's ECDH curve preference order export const CHROME_CURVES = 'X25519:P-256:P-384'; export interface TlsFingerprintOptions { rejectUnauthorized?: boolean; keepAlive?: boolean; keepAliveMsecs?: number; maxSockets?: number; } /** * Returns an HTTPS agent configured with Chrome-like TLS settings. * Drop-in replacement for the default agent in fetch/got/axios. */ export function createChromeTlsAgent(opts: TlsFingerprintOptions = {}): HttpsAgent { return new HttpsAgent({ ciphers: CHROME_CIPHERS, ecdhCurve: CHROME_CURVES, minVersion: 'TLSv1.2', maxVersion: 'TLSv1.3', rejectUnauthorized: opts.rejectUnauthorized ?? true, keepAlive: opts.keepAlive ?? true, keepAliveMsecs: opts.keepAliveMsecs ?? 10_000, maxSockets: opts.maxSockets ?? 50, }); } /** * Probe a host and return its JA3-relevant TLS parameters. * Useful for verifying that the fingerprint changed vs default. */ export async function probeTlsFingerprint(host: string, port = 443): Promise<{ protocol: string; cipher: string; authorized: boolean; subjectCN: string | null; }> { return new Promise((resolve, reject) => { const socket = tlsConnect({ host, port, servername: host, ciphers: CHROME_CIPHERS, ecdhCurve: CHROME_CURVES, minVersion: 'TLSv1.2', rejectUnauthorized: false, }, () => { const cert = socket.getPeerCertificate(); resolve({ protocol: socket.getProtocol() ?? 'unknown', cipher: socket.getCipher()?.name ?? 'unknown', authorized: socket.authorized, subjectCN: cert?.subject?.CN?.toString() ?? null, }); socket.destroy(); }); socket.on('error', reject); setTimeout(() => { socket.destroy(); reject(new Error('TLS probe timeout')); }, 5_000); }); } /** * Node.js fetch dispatcher options with Chrome TLS agent. * Pass as the `dispatcher` option to undici or as `agent` to node-fetch. */ export const chromeTlsAgent = createChromeTlsAgent(); export const CHROME_TLS_HEADERS = { 'Accept-Encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'en-US,en;q=0.9', 'sec-ch-ua': '"Google Chrome";v="120", "Chromium";v="120", "Not-A.Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Upgrade-Insecure-Requests': '1', };