Защита 0-RTT, ECH, гибридный PQ и продакшн TLS
Продакшн-защиты от повтора 0-RTT, ротация STEK в масштабах CDN, Encrypted ClientHello, гибридный постквантовый обмен ключами, выгрузка kTLS в ядро и метрики наблюдаемости для любого TLS-сервиса.
FinTech-сервис, обрабатывающий 50 тысяч TLS-рукопожатий в секунду, не может позволить себе наивно включённый 0-RTT, статический ключ сессионного тикета или чисто классический обмен ключами, который будущий квантовый компьютер способен ретроспективно взломать. Этот урок — о том, как продакшн-развёртывания защищаются от повтора, ротируют STEK, скрывают SNI через ECH, добавляют постквантовую защиту и наблюдают за TLS в масштабе.
Защита от повтора 0-RTT в продакшне (RFC 8446 §8 + RFC 8470)
Три ортогональных защиты применяются в совокупности:
1. Валидация возраста тикета. Клиент отправляет obfuscated_ticket_age; сервер вычитает ticket_age_add, учитывает ожидаемый RTT и отклоняет early_data, если результат выходит за пределы узкого окна (типично: 10 с). Злоумышленник, воспроизводящий данные часы спустя, получает немедленный отказ.
2. Однократное использование тикета. Кэш в памяти с ключом (ticket_nonce, age_bucket) отклоняет любую вторую попытку расшифровки. Cloudflare ведёт кэши на каждой точке присутствия плюс best-effort кросс-PoP синхронизацию, чтобы повторы между регионами также перехватывались в пределах окна.
3. Идемпотентность на уровне приложения. Только безопасные методы проходят в early_data. Фреймворки (Spring, Express, Caddy) проверяют заголовок запроса Early-Data: 1 и возвращают 425 Too Early для любого обработчика, изменяющего состояние.
Ротация ключа шифрования сессионных тикетов (STEK)
Спросите себя: если злоумышленник незаметно похитит ваш STEK (ключ шифрования сессионных тикетов) сегодня ночью — что он сможет расшифровать? Без прямой секретности при возобновлении утечка STEK позволяет злоумышленнику расшифровать все 0-RTT-данные из тикетов, выданных этим ключом. Правила для продакшна:
- Ротируйте не реже раза в час (Cloudflare ротирует ежечасно; операторы Nginx должны перезагружаться с новым
ssl_session_ticket_keyв начале очереди). - Держите кольцевой буфер из прежних ключей только для расшифровки (обычно 24 ключа), чтобы тикеты в пути продолжали возобновляться.
- Никогда не сохраняйте STEK на диск. Хранение только в памяти — обязательное требование.
- Распространяйте новые ключи на все бэкенды через аутентифицированное распределение до истечения старого ключа, чтобы кросс-узловые тикеты оставались возобновляемыми.
Вместе эти правила означают: злоумышленник, перехвативший тикеты сегодня, сможет воспроизводить их лишь в рамках окна действия текущего ключа — как правило, один час. Нарушьте любое из правил — и вы расширяете это окно с одного часа до срока жизни ключа на диске или в резервной копии.
Режимы отказа: ключи, сохранённые на диск и восстановленные после компрометации узла (инцидент GoDaddy 2023); статический STEK, настроенный на год, превращающий возобновление в монокультуру одного ключа.
Encrypted ClientHello (ECH, RFC 9849)
Обычный ClientHello в TLS 1.3 раскрывает SNI, ALPN, поддерживаемые группы и алгоритмы подписи — достаточно для идентификации целевого имени хоста и стека клиента. ECH (RFC 9849, опубликован в марте 2026) определяет:
- Внешний ClientHello, отправляемый на публичное имя хоста фронтенда.
- Внутренний ClientHello, зашифрованный через HPKE с использованием опубликованного ECH-ключа источника (полученного из DNS-записи
HTTPS/SVCB, RFC 9460).
Клиент получает ECH-ключ до TCP-соединения. Если он недоступен, клиент может использовать GREASE (отправить фиктивный ECH) или откатиться к обычному SNI. Chrome поставляет ECH по умолчанию начиная с Chrome 117+; Firefox включил его с версии 119; поддержка Safari всё ещё в разработке по состоянию на май 2026 года.
Операторы должны регулярно ротировать публичный ECH-ключ (TTL DNS — верхняя граница компрометации ключа) и принять, что внешний SNI по-прежнему называет общий фронтенд.
Гибридный постквантовый обмен ключами
draft-ietf-tls-ecdhe-mlkem (февраль 2026) стандартизирует X25519MLKEM768 (кодовая точка именованной группы 0x11EC). ML-KEM — это NIST FIPS 203 (стандартизирован в августе 2024). Гибридный общий секрет:
combined_secret = concat(ecdhe_secret, mlkem_secret)Это поступает в HKDF-Extract там, где раньше находился классический секрет ECDHE — расписание ключей ниже не изменяется. Гибрид безопасен, если держится хотя бы один примитив: квантовый компьютер, взломавший ECDHE, не сможет восстановить сессию, если ML-KEM устоит.
Стоимость на уровне протокола: публичные ключи ML-KEM-768 занимают 1184 байта, шифртексты — 1088 байт, что вынуждает ClientHello превысить 1500 байт и требует TCP-сегментации. Chrome 131+ (ноябрь 2024) поставляет гибридный PQ по умолчанию. По состоянию на Q1 2026 более трети трафика Cloudflare использует гибридные PQ-рукопожатия.
- Публичный ключ X25519
- 32 байта
- Публичный ключ ML-KEM-768
- 1 184 байта
- Шифртекст ML-KEM-768
- 1 088 байт
- Размер гибридного ClientHello
- >1 500 байт (TCP-сегментация)
- Chrome по умолчанию с
- Chrome 131 (ноябрь 2024)
- Трафик Cloudflare на гибридном PQ (Q1 2026)
- >33%
Ядерный TLS (kTLS) — выгрузка в ядро
setsockopt(fd, SOL_TLS, TLS_TX, &crypto_info) передаёт симметричный уровень записи ядру после завершения рукопожатия в пользовательском пространстве (Linux 4.13+, OpenSSL 3.2+). Современные ядра плюс поддерживаемый сетевой адаптер могут строить зашифрованные записи прямо на проводе с нулевой CPU-стоимостью на запись.
Netflix и Cloudflare приписывают экономию 8–29% CPU при обслуживании статических файлов использованию sendfile() поверх kTLS: файл перемещается из кэша страниц на сетевой адаптер, не попадая в пользовательское пространство. Поддерживаемые алгоритмы: AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305 — только TLS 1.3.
Вмешательство промежуточных узлов и GREASE (RFC 8701)
TLS 1.3 помещает 0x0303 (TLS 1.2) в поле legacy_version и отправляет фиктивную запись ChangeCipherSpec, поскольку слишком много промежуточных узлов прерывают соединения, байты которых не соответствуют шаблонам TLS 1.2. GREASE (Generate Random Extensions And Sustain Extensibility — механизм намеренного введения случайных расширений для проверки совместимости; RFC 8701) рассыпает зарезервированные кодовые точки (0x0A0A, 0x1A1A, …, 0xFAFA) по спискам расширений и наборам шифров. Корректный партнёр игнорирует неизвестные значения; хрупкий промежуточный узел ломается немедленно, обнажая ошибку перед поставщиком вместо того, чтобы дать ей окаменеть в протоколе.
Наблюдаемость в продакшне
Когда что-то идёт не так с TLS в продакшне — всплеск задержки, внезапное падение доли возобновлений, волна сообщений о дублирующихся POST от пользователей — какая метрика покажет, где искать причину? Шесть ниже образуют минимально жизнеспособный набор; каждая метрика напрямую соответствует режиму отказа, который вы изучили в этом юните.
Минимально жизнеспособный набор метрик Prometheus для TLS-сервиса:
tls_handshake_duration_seconds_bucket— гистограмма; p95 выше 200 мс на CDN-источнике — это регрессия.tls_resumption_total{kind="psk|ticket|none"}— резкое падение доли возобновлений обычно означает ротацию STEK без окна перекрытия.tls_version_total{version="1.2|1.3"}— помечайте любой трафик на 1.2.tls_cipher_suite_total— выявляйте нежелательные наборы шифров.tls_early_data_total{outcome="accepted|rejected"}— всплеск отклонений early_data нередко предшествует сообщениям клиентов о дублирующихся POST.tls_ocsp_staple_total{outcome="ok|expired|missing"}— отсутствие прикрепления на сертификате с must-staple вызовет отказы соединений.
Реальные производственные инциденты
- Истечение цепочки Let’s Encrypt 2021: срок действия DST Root CA X3 истёк; Android < 7.1.1 не прошёл верификацию, сломав длинный хвост устройств.
- Утечка STEK GoDaddy 2023: утечка STEK в неправильно настроенной резервной копии клиента позволила злоумышленнику расшифровать месяцы кэшированных данных 0-RTT — именно тот режим отказа, о котором предупреждает RFC 8446 §2.3.
- Heartbleed (CVE-2014-0160): отсутствующая проверка длины в расширении heartbeat OpenSSL позволяла злоумышленникам читать 64 КБ памяти процесса, включая приватные ключи. Постмортем изменил экосистему (BoringSSL, rustls) и является причиной, по которой kTLS ограничивает поверхность ядра только шифрованием уровня записи.
Гибридное рукопожатие ML-KEM-768 + x25519 (Chrome 131+)
Ошибка рукопожатия OpenSSL — определите причину.
% openssl s_client -connect api.example.internal:443 -tls1_3 -showcerts
CONNECTED(00000005)
depth=0 CN = api.example.internal
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = api.example.internal
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = api.example.internal
i:CN = Internal Corp Issuing CA
---
SSL handshake has read 1845 bytes and written 308 bytes
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server certificate
subject=CN = api.example.internal
issuer=CN = Internal Corp Issuing CA
---
SSL-Session:
Protocol : TLSv1.3
Verify return code: 21 (unable to verify the first certificate) Рукопожатие завершается, но verify return code = 21. В чём неправильная конфигурация и что сервер должен отправлять для исправления?
Почему утечка STEK позволяет расшифровывать данные 0-RTT, но не обычные рукопожатия TLS 1.3?
▸Почему это работает
mTLS в масштабах сервисной сетки. Istio + SPIFFE/SPIRE выдают свежие листовые сертификаты на каждую рабочую нагрузку (TTL нередко менее 24 часов), ротируя через API рабочей нагрузки SPIRE. При 10 000 подов с 24-часовой ротацией это примерно 7000 выдач сертификатов в минуту. CA уровня управления становится узким местом; иерархическая конструкция CA (корневой → промежуточный на кластер) держит подписание листов внутри кластера и делает его быстрым. Плоскость данных (Envoy) завершает и переинициирует TLS на каждом переходе — ваш бюджет сквозной задержки оплачивает рукопожатие TLS 1.3 на каждой границе сервиса, если только пулы соединений не амортизируют стоимость на тысячи запросов.
- 01Назовите три ортогональные защиты от повтора 0-RTT и что каждая из них перехватывает.
- 02Почему гибридный PQ конкатенирует секреты ECDHE и ML-KEM, а не использует только один из них?
- 03Какая продакшн-метрика наиболее прямо сигнализирует об ошибке ротации STEK?
Безопасность 0-RTT в продакшне требует трёх послойных защит: окно повтора по возрасту тикета, кэш одноразовых nonce на точку присутствия и 425 Too Early на уровне приложения для неидемпотентных маршрутов. Ключи шифрования сессионных тикетов должны ротироваться не реже раза в час, никогда не касаться диска и распределяться на все бэкенды до истечения старого ключа. Encrypted ClientHello (RFC 9849) скрывает целевой SNI от наблюдателей на пути; гибридный PQ (X25519MLKEM768) защищает от квантовых атак «записывай сейчас — расшифровывай потом» ценой увеличения ClientHello. Ядерный TLS выгружает симметричный уровень записи на сетевой адаптер, давая 8–29% экономии CPU при обслуживании статических файлов. Минимально жизнеспособный набор метрик отслеживает p95 продолжительности рукопожатия, долю возобновлений, исходы early_data и распределение наборов шифров — падение доли возобновлений является самым быстрым сигналом об ошибке ротации STEK.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
встречается в47
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Инвалидация, dirty-биты и containmiddle
- Слои композитора: продвижение, перекрытие и память GPUmiddle
- Observability в проде: LoAF, INP и полная поверхность атакиsenior
- Hidden classes, деревья переходов и расположение в памятиmiddle
- V8 в production: Isolates, сжатие указателей и реальные аварииsenior
- Что такое воркеры и зачем они нужныjunior
- Механика web workers: dedicated, shared и OffscreenCanvasmiddle
- Structured clone и transferablesmiddle
- SharedArrayBuffer, Atomics и cross-origin isolationsenior
- Пулы воркеров, Comlink и наблюдаемость в продакшенеsenior
- Восемь слоёв трассировки: от service worker до второй навигацииmiddle
- Пять канонических поломок: где производство стабильно ломаетсяsenior
- Метод трёх треков: чтение трасс и построение системы мониторингаsenior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- JSONB, массивы и когда side table побеждаетmiddle
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Где происходит data fetching — и почему это решает LCPjunior
- React Server Components и Suspense streamingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Что такое OpenTelemetry: API, SDK, Collector, OTLPjunior
- Сигналы OTel, Semantic Conventions и проводной формат OTLPmiddle
- Collector OTel: receivers, processors, exporters и паттерны развёртыванияmiddle
- Vendor-нейтральность, eBPF-инструментирование, Operator и OTel в браузере и serverlesssenior
- Эксплуатация OTel Collector: надёжность, version skew, режимы отказа и управлениеsenior
- Что такое trace propagation и почему сломанная propagation хуже отсутствия трейсовjunior
- traceparent и tracestate: полный формат W3C-заголовкаmiddle
- Baggage и async-границы: перенос контекста через очереди и callback''''иmiddle
- Async context на разных языках, service mesh, миграция B3 и безопасностьsenior
- Production-сбои propagation, span links и платформенный дизайнsenior
- Debugging-воронка: SLO → RED → trace → profilejunior
- Архитектура OTel: один SDK, четыре сигнала, один wire-форматmiddle
- Петля инцидента: от пейджера до постмортема до предотвращенияmiddle
- Масштаб, безопасность и ROI наблюдаемых системsenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.