1. Байт-парсер SNI (RFC 6066)
Главным инженерным ядром CF-PROXY является функция extractSNI(buf) в файле bridge.mjs. Вместо создания полноценного TLS-терминатора (требующего ключей шифрования и подмены сертификатов), прокси анализирует входящий сырой буфер сокета на 443 порту в момент рукопожатия TLS.
Согласно спецификации RFC 6066 (TLS Server Name Indication), клиент при установке защищенного соединения отправляет имя запрашиваемого виртуального хоста в открытом виде (в заголовке ClientHello), чтобы целевой сервер мог выбрать нужный SSL-сертификат.
function extractSNI(buf) {
try {
// Проверка сигнатуры TLS: Record Type 0x16 (Handshake) и Handshake Type 0x01 (ClientHello)
if (buf.length < 43 || buf[0] !== 0x16 || buf[5] !== 0x01) return null;
let pos = 43; // Смещение заголовочной части и случайных байтов клиента (Client Random)
if (pos >= buf.length) return null;
// Пропуск Session ID
pos += 1 + buf[pos];
if (pos + 2 > buf.length) return null;
// Пропуск списка Cipher Suites
pos += 2 + buf.readUInt16BE(pos);
if (pos + 1 > buf.length) return null;
// Пропуск методов компрессии (Compression Methods)
pos += 1 + buf[pos];
if (pos + 2 > buf.length) return null;
// Чтение длины блока расширений TLS
const extTotalLen = buf.readUInt16BE(pos);
pos += 2;
const end = Math.min(buf.length, pos + extTotalLen);
// Итерация по блокам расширений в поисках Type 0x0000 (server_name)
while (pos + 4 <= end) {
const extType = buf.readUInt16BE(pos);
const extLen = buf.readUInt16BE(pos + 2);
pos += 4;
if (extType === 0x0000 && pos + 5 <= end) {
const nameLen = buf.readUInt16BE(pos + 3);
if (pos + 5 + nameLen <= end) {
return buf.toString("utf8", pos + 5, pos + 5 + nameLen);
}
}
pos += extLen;
}
} catch (_) {}
return null;
}
2. Анатомия пакета TLS ClientHello
Ниже представлена структура байтового смещения, по которой Node.js извлекает целевой хост без расшифровки:
+--------------------------------------------------------------------------+
| Offset (Hex/Dec) | Size | Field Description |
+--------------------------------------------------------------------------+
| 0x00 (0) | 1 Byte | 0x16 - TLS Record ContentType (Handshake) |
| 0x01 (1) | 2 Bytes | TLS Protocol Version (0x0301 = TLS 1.0/1.2) |
| 0x03 (3) | 2 Bytes | Length of Handshake Message Body |
| 0x05 (5) | 1 Byte | 0x01 - HandshakeType (ClientHello) |
| 0x06 (6) | 3 Bytes | Handshake Length |
| 0x09 (9) | 2 Bytes | Client Version (0x0303 = TLS 1.2 / 1.3) |
| 0x0B (11) | 32 Bytes| Random Data (Unix Timestamp + Random bytes) |
| 0x2B (43) | 1+N Byte| Session ID Length + Session ID bytes |
| Pos_A | 2+N Byte| Cipher Suites Length + Cipher Suites array |
| Pos_B | 1+N Byte| Compression Methods Length + Methods bytes |
| Pos_C | 2 Bytes | Extensions Length |
| Extension: | | |
| +0 | 2 Bytes | Extension Type (0x0000 = server_name) |
| +2 | 2 Bytes | Extension Length |
| +4 | 2 Bytes | Server Name List Length |
| +6 | 1 Byte | NameType (0x00 = Host_Name) |
| +7 | 2 Bytes | Host Name Length |
| +9 | N Bytes | Host Name String (ASCII: "chatgpt.com") |
+--------------------------------------------------------------------------+
3. Безопасность Zero-CA
В традиционных прокси-серверах (Fiddler, Charles, mitmproxy) клиент соединяется с прокси, прокси подменяет SSL-сертификат сайта своим собственным и расшифровывает весь входящий и исходящий трафик.
1. Установка стороннего Root CA в систему даёт возможность любому процессу с правами администратора расшифровывать банковские операции и пароли.
2. Большинство AI-инструментов (Google Antigravity, Cursor, расширения VS Code) используют встроенные хранилища сертификатов (Certificate Pinning) и наотрез отказываются работать через MITM-сертификаты.
Подход CF-PROXY: Мост вообще не генерирует ключей шифрования. После извлечения SNI-строки, сырые криптографические байты TLS ClientHello упаковываются в WebSocket и передаются удаленному серверу. Сквозной ключ симметричного шифрования (AES-GCM / ChaCha20-Poly1305) согласуется исключительно между вашим клиентом и сервером OpenAI / Google.
4. WebSocket Multiplexing
После того, как SNI распознан (например, chatgpt.com), узел инициирует исходящее соединение по протоколу WebSocket к шлюзу Cloudflare:
const ws = new WebSocket(`wss://${WORKER_HOST}/?host=${encodeURIComponent(sni)}&port=443`);
ws.binaryType = "arraybuffer";
Все последующие пакеты от клиента перенаправляются в метод ws.send(chunk), а входящие ответы от сервера передаются обратно клиенту через client.write(new Uint8Array(e.data)).
Благодаря флагу client.setNoDelay(true) отключается алгоритм Нейгла, обеспечивая минимальную задержку при передаче интерактивных потоков токенов (Stream Responses в ChatGPT и Antigravity).
5. Маршрутизация в Cloudflare Edge
Cloudflare Worker развернут на глобальной сети Anycast. При входящем WebSocket запросе он считывает query-параметры host и port и вызывает системный API connect():
import { connect } from 'cloudflare:sockets';
export default {
async fetch(request) {
const url = new URL(request.url);
const targetHost = url.searchParams.get("host");
const targetPort = parseInt(url.searchParams.get("port") || "443");
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
server.accept();
// Открытие сырого TCP-сокета к целевому серверу через сеть Cloudflare
const tcpSocket = connect({ hostname: targetHost, port: targetPort });
// Связывание потоков WebSocket <-> TCP
pipeStreams(server, tcpSocket);
return new Response(null, { status: 101, webSocket: client });
}
};