Когда ProxyMapService расшифровывает HTTPS-трафик, он работает как man-in-the-middle: TLS-соединение от браузера заканчивается на прокси, а к целевому серверу прокси открывает собственное TLS-соединение. Сертификат, который прокси предъявляет клиенту, генерируется на лету из собственного корневого CA — но даже с валидной цепочкой сертификатов есть то, что сервер видит и что так просто не замаскировать: сам TLS-хендшейк.
ClientHello очень характерен для софта, который его отправил. .NET-овский SslStream предлагает свой набор шифров и расширений, в своём порядке, без GREASE. Для тех, кто смотрит — антибот-систем, CDN, сервисов капчи — такое соединение браузером не пахнет. Форматы фингерпринтов вроде JA3 и JA4 сжимают ClientHello в короткий хэш, идентифицирующий клиентский софт, — и этот хэш уезжает с каждым TLS-соединением, которое открывает прокси.
Что изменилось
В новой версии ProxyMapService появилась опция UseBoringSsl в SslClientOptions. Когда она включена, исходящий TLS-хендшейк выполняет не SslStream, а BoringSslStream — TLS-стрим на базе BoringSSL, TLS-библиотеки, которую использует Chromium.
BoringSslStream повторяет ClientHello Google Chrome побайтово: те же наборы шифров, те же расширения в том же порядке, GREASE, постквантовые группы ключей, ALPN и ALPS. С точки зрения сервера соединение выглядит так, будто его сделал настоящий браузер Chrome.
Разницу легко увидеть в JA4-отпечатке ClientHello (оба значения получены интеграционным тестом из состава ProxyMapService, за прокси — HTTP/1.1-клиент):
| Клиент | JA4-отпечаток |
|---|---|
.NET SslStream |
t13d311100_e8f1e7e78f70_f6eeaf393d2e |
| BoringSslStream (Chrome) | t13d1517h2_8daaf6152771_cb7bf5808d99 |
Первая часть отпечатка расшифровывается так: TCP, TLS 1.3, SNI присутствует, затем количество cipher suites, количество расширений и ALPN-протокол. .NET-овский хендшейк предлагает 31 шифр и 11 расширений и ни на какой браузер не похож. Хендшейк BoringSslStream предлагает 15 шифров и 17 расширений — а хэш шифров 8daaf6152771 — это в точности тот хэш, который выдаёт сам Google Chrome.
Как включить
Должны выполняться два условия:
- включена расшифровка SSL (
DecryptSSL) — тогда прокси сам открывает TLS-соединения наружу; как это настроить, описано в посте про расшифровку SSL/TLS-трафика; - включён
UseBoringSslвSslClientOptions:
"SslClientOptions": {
"EnabledSslProtocols": "Tls12,Tls13",
"CheckCertificateRevocation": true,
"ApplicationProtocols": "http/1.1,h2",
"UseBoringSsl": true
}
Перезапустите сервис, чтобы изменение вступило в силу. Опция влияет только на исходящие (upstream) соединения расшифрованного трафика; на то, как прокси принимает клиентские соединения, она не влияет.
Что осталось как было
- ALPN. Согласованный прикладной протокол по-прежнему определяется настройкой
SslClientOptions.ApplicationProtocols— HTTP/2 к серверу работает ровно так, как описано в посте про трансляцию HTTP/1.1 → HTTP/2. - Проверка сертификатов.
CheckCertificateRevocation, списокTrustedCAsи опция маппингаIgnoreCertificateErrorsс BoringSslStream работают в полную силу. - Никаких лишних зависимостей. Нативный бинарник BoringSSL входит в состав NuGet-пакета для Windows x64, Linux x64 и macOS ARM64; ничего отдельно устанавливать не нужно.
Одна настройка намеренно перестаёт играть роль: EnabledSslProtocols на BoringSslStream не действует, потому что версии TLS и наборы шифров выбираются так же, как их выбирает Chrome — в этом весь смысл.
Более тихий лог по умолчанию
В той же версии Event Log стал тише. Многие Information-сообщения, которые раньше писались на каждое соединение, теперь по умолчанию выключены, поэтому в логе остаётся в основном то, что важно: запросы, ответы, предупреждения и ошибки. Подробные сообщения никуда не делись — они включаются в секции DetailedLogging:
"DetailedLogging": {
"LogStep": false,
"Http2Logging": false,
"LogConnections": false,
"LogTlsHandshake": false,
"LogProtocolSwitch": false,
"LogResponseSource": false
}
| Настройка | Какие сообщения включает |
|---|---|
LogConnections |
Жизненный цикл соединений: клиент подключился, соединение с сервером установлено, отключение. Предупреждения (ошибка соединения и т.п.) пишутся всегда. |
LogTlsHandshake |
Успешные TLS-хендшейки. Неудачные — это warnings, они пишутся всегда. |
LogProtocolSwitch |
Согласование протоколов: определение h2, понижение до HTTP/1.1 для host override и кэша, трансляция HTTP/1.1 → HTTP/2. |
LogResponseSource |
Откуда пришёл ответ: с сервера, из кэша или из файла, отданного прокси. |
Http2Logging |
Диагностика HTTP/2: прерванные потоки (RST_STREAM) и закрытие соединения (GOAWAY) с кодами ошибок. |
LogStep |
Шаги обработки сессии: по строке на каждый шаг конечного автомата. |
Когда диагностируете проблему с соединением, обычно первыми стоит включить LogConnections и LogTlsHandshake; LogStep даёт полную картину того, как сессия двигалась по хендлерам.
Проверить отпечаток
В репозитории есть интеграционный тест, который перехватывает ClientHello, отправляемый прокси наружу, и вычисляет его JA4-отпечаток. Запустите тестовый экземпляр прокси (start_proxymapservice_test.sh / .ps1) и выполните:
dotnet test ProxyMapService.Tests --filter "FullyQualifiedName~Ja4Fingerprint" -l "console;verbosity=detailed"
Тест печатает JA4-отпечаток вместе с полным списком предложенных шифров и расширений и проверяет, что с UseBoringSsl: true хэш шифров совпадает с хэшем Chrome 8daaf6152771, а при выключенной опции — не совпадает.