Этап 5 — TLS 1.3: handshake до последнего байта
«Замочек в браузере» — это полстраницы криптографии в одном маленьком handshake. Сейчас разберём TLS 1.3 на молекулы: ClientHello, ServerHello, ECDHE, цепочка сертификатов, OCSP, 0-RTT, session resumption, ECH.
«Суббота, 03:12. Алерты: у половины сервисов клиенты получают certificate expired. Деплоев не было». Прежде чем читать: как сертификат мог «сломаться сам», если код никто не трогал? Сколько мест в цепочке запроса (CDN → WAF → балансировщик → pod) вообще предъявляют сертификаты — и за каждым ли из них кто-то следит? Держи вопрос в голове: в конце урока ты соберёшь чек-лист, чтобы такой субботы у тебя не было.
- Проследить TLS 1.3 handshake сообщение за сообщением — и объяснить, почему он укладывается в 1 RTT
- Объяснить роль SNI (и что от него скрывает ECH) и ALPN (как выбирается HTTP/2 vs HTTP/3)
- Рассказать, как ECDHE даёт forward secrecy — и почему записанный сегодня трафик не расшифруют завтра
- Прочитать цепочку сертификатов: leaf → intermediate → root, SAN, сроки, кто кого подписал
- Сравнить CRL, OCSP и OCSP stapling — и включить правильное
- Взвесить 0-RTT: где минус-один-RTT, а где replay-атака
- Снять handshake руками:
openssl s_client, ssllabs, проверка сроков в одну строку
В этапе 4 TCP-канал открылся за 1 RTT — теперь каждое сообщение TLS едет в этих сегментах, и каждый лишний round-trip виден пользователю. Из этапа 2 вспомни записи CAA (какому CA можно выпускать сертификат) и HTTPS/SVCB с полем ech — обе выстрелят в этом уроке. Базовая интуиция «зачем шифровать и что такое сертификат» — «Сети с нуля», урок 7.
1. Зачем TLS 1.3, а не 1.2
TLS 1.3 — стандарт с 2018. По сравнению с 1.2 — это революция:
| Свойство | TLS 1.2 | TLS 1.3 |
|---|---|---|
| Handshake RTT | 2 RTT | 1 RTT (0-RTT при resumption) |
| Шифры | Десятки опций, многие плохие | 5 modern AEAD шифров — всё |
| Forward secrecy | Опционально | Обязательно (только ECDHE) |
| RSA key exchange | Поддерживается | Удалён |
| SHA-1, MD5 | Кое-где есть | Запрещены |
| Шифрование сертификата | В клире | Зашифрован после Server Hello |
В 2026: TLS 1.3 обязательно, 1.2 — только legacy-клиенты, 1.0/1.1 — давно отключены везде.
2. Handshake — поле за полем
TLS 1.3 handshake состоит из 4 групп сообщений. Разберём каждое в деталях.
ClientHello — самое большое сообщение
Это «вступление» клиента. Хотя выглядит как один пакет, внутри — много extension'ов:
ClientHello {
legacy_version: 0x0303, # TLS 1.2 (для совместимости с middleboxes)
random: 32 байта случайных,
legacy_session_id: 32 байта (или 0),
cipher_suites: [
TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384,
TLS_CHACHA20_POLY1305_SHA256,
... # список того, что клиент поддерживает
],
legacy_compression_methods: [0x00], # no compression
extensions: [
supported_versions: [TLS 1.3, TLS 1.2],
supported_groups: [x25519, secp256r1, secp384r1, ...],
signature_algorithms: [ecdsa_secp256r1_sha256, rsa_pss_rsae_sha256, ...],
key_share: [
{group: x25519, key: 32 байта публичного ECDHE},
...
],
server_name: [host="app.example.com"], # ★ SNI — критично
application_layer_protocol_negotiation: ["h2", "http/1.1"], # ★ ALPN
psk_key_exchange_modes: [psk_dhe_ke], # для resumption
pre_shared_key: ..., # если это resumption
early_data: ..., # если 0-RTT
encrypted_client_hello: ..., # ECH (опц.)
]
}
Каждое поле важно. Разберём ключевые.
3. SNI — Server Name Indication
На одном IP может быть хостингов десятки сайтов. Когда клиент устанавливает TLS, сервер должен знать, для какого хоста отдать сертификат. SNI — это extension в ClientHello, где клиент указывает server_name.
Проблема: SNI идёт в открытом виде (это до того, как ключ согласован). Провайдер видит, к какому сайту ты обращаешься, даже не видя содержимого.
Решение: ECH (Encrypted Client Hello) — RFC 9180. Клиент берёт публичный ключ из HTTPS-DNS-записи, шифрует SNI плюс другие фингерпринт-extensions, и шлёт как «outer ClientHello» с фейковым именем. Сервер расшифровывает.
ECH развёрнут в Cloudflare, Firefox/Chrome поддерживают. Это серьёзный шаг для приватности.
4. ALPN — Application Layer Protocol Negotiation
В TLS 1.2 после handshake клиент мог только начать слать HTTP/1.1. Чтобы согласовать HTTP/2 или HTTP/3, нужен extension ALPN.
Клиент шлёт список — «я могу h2, http/1.1». Сервер в ServerHello выбирает — «давай h2». Дальше пакеты — это HTTP/2 frames.
Без ALPN — нет HTTP/2. Это тоже основа быстрого веба сейчас.
5. ECDHE — обмен ключами по математике
Это сердце TLS. Обе стороны должны договориться о симметричном ключе, не передавая его в клире. Diffie-Hellman решает эту задачу с 1976 года.
Идея (упрощённо)
- Клиент берёт случайное число a (приватное), вычисляет A = ga mod p. Отправляет A.
- Сервер берёт случайное b, вычисляет B = gb mod p. Отправляет B.
- Клиент вычисляет S = Ba mod p. Сервер вычисляет S = Ab mod p. Это одно и то же число — общий секрет.
- Атакующий видит g, p, A, B — но вычислить a или b из A или B вычислительно сложно (discrete log problem).
ECDHE — то же самое, но на эллиптических кривых (Curve25519, P-256). Безопаснее при том же размере ключа, быстрее.
«E» в конце — Ephemeral, то есть ключи генерируются заново при каждом handshake. Это обеспечивает Forward Secrecy: даже если завтра украдут приватный ключ сервера, прошлые сессии расшифровать нельзя — там были другие эфемерные ключи.
Диффи-Хеллман — смешивание красок
Классическое объяснение. У всех на виду стоит банка общей жёлтой краски (параметры g, p). Ты втайне добавляешь в свою порцию каплю своего секретного цвета (число a) и ставишь смесь на стол — все её видят, но «размешать обратно» и выделить твой секретный цвет нельзя. Сервер делает то же со своим секретом b. Теперь каждый берёт чужую смесь и добавляет свой секрет: у обоих получается один и тот же итоговый цвет (общий ключ S) — а подслушивающий видел только промежуточные смеси и итог собрать не может. «Ephemeral» — краски замешиваются заново на каждое соединение: украли сегодняшний секрет — вчерашние цвета не восстановить.
6. Сертификат и цепочка
Сервер шлёт цепочку сертификатов (Certificate сообщение, зашифровано в TLS 1.3). Клиент проверяет:
- Имя.
Subject Alternative Names(SAN) сертификата должно содержатьapp.example.com(или wildcard*.example.com). Старое поле CN — игнорируется. - Срок.
notBefore ≤ now ≤ notAfter. Современные сертификаты — на 90 дней (Let's Encrypt). - Цепочка. Сертификат подписан приватным ключом intermediate CA. Intermediate подписан root CA, который вшит в trust store браузера/ОС.
- Подпись. Криптографическая проверка каждого звена.
- Не отозван. CRL или OCSP — об этом ниже.
- CT log. Certificate Transparency — сертификат должен быть в публичных логах (с 2018 это требование Chrome).
Что значит «доверенный root»
В твоём браузере / ОС вшит trust store — список ~150-200 root-сертификатов. Если цепочка от твоего сертификата приходит к одному из них — браузер доверяет. Если нет — большая красная страница.
Каждый root CA проходит аудит, делает Key Signing Ceremony, и должен выпускать сертификаты только подтверждённым владельцам доменов.
SAN, wildcards и multi-domain
# Wildcard покрывает один уровень
*.example.com → www.example.com, api.example.com ✓
→ app.staging.example.com ✗
# Multi-SAN — много имён в одном сертификате
SAN: example.com, www.example.com, api.example.com, *.staging.example.com
7. OCSP — отзыв сертификатов
Что, если сертификат скомпрометирован и его нужно отозвать до истечения срока? Есть два механизма:
📜 CRL — Certificate Revocation List
CA публикует список отозванных серийников. Браузер периодически скачивает.
Проблема: CRL разрастается до мегабайт. Никто реально не качает.
⚡ OCSP — Online Certificate Status Protocol
Браузер делает HTTP-запрос к OCSP-серверу: «серийник X — отозван?». Получает ответ.
Проблема: Дополнительный RTT при каждом TLS handshake. И утечка приватности — OCSP-сервер видит, какие сайты ты открываешь.
OCSP stapling
Решение — OCSP stapling: сервер сам периодически запрашивает свой OCSP-статус и «прикладывает» его к сертификату в handshake. Клиент получает свежий статус без отдельного запроса.
Это включается в Nginx / Caddy одной строкой:
ssl_stapling on;
ssl_stapling_verify on;
OCSP Must-Staple
В сертификате может быть расширение OCSP Must-Staple — это говорит браузеру: «без OCSP-staple не доверяй мне». Защита от ситуации «OCSP сервер атакован, и через 7 дней при тишине считается ok».
8. Session resumption — экономия RTT
Сделал handshake, открыл сайт, закрыл. Через минуту вернулся. Можно ли не делать handshake заново?
TLS 1.3 поддерживает два режима:
Session Tickets
Сервер в конце handshake шлёт session ticket — зашифрованный своим ключом «снимок» состояния. Клиент сохраняет. При следующем подключении вкладывает ticket в ClientHello. Сервер расшифровывает и понимает: «о, это та же сессия». Не нужно делать новый ECDHE.
PSK (Pre-Shared Key)
Аналогично, но «ключ» хранит и сервер, и клиент. Используется в IoT и контролируемых сценариях.
Resumption экономит RTT — handshake становится «0.5-RTT» вместо 1-RTT.
9. 0-RTT — отправить данные сразу с ClientHello
Самая агрессивная оптимизация. При resumption клиент может вложить данные в первое же сообщение — раньше, чем handshake завершён.
ClientHello {
pre_shared_key: <ticket>,
early_data: enabled,
...
}
+ Application Data (HTTP-запрос!) ← данные едут СРАЗУ
Сервер расшифровывает и обрабатывает запрос. Ответ возвращается за 1 RTT — фактически 0 RTT для данных от клиента.
0-RTT данные не имеют защиты от replay. Атакующий может перехватить и переслать тот же 0-RTT-запрос дважды. Это плохо для не-идемпотентных операций.
Поэтому правило: 0-RTT только для идемпотентных запросов (GET, OPTIONS). Никогда не разрешай POST/PUT/DELETE через 0-RTT.
10. Шифры в TLS 1.3 — все пять
В TLS 1.2 было 300+ cipher suites, многие небезопасные. В TLS 1.3 — пять, все безопасные AEAD-шифры:
| Cipher | Шифр | Hash | Когда |
|---|---|---|---|
| TLS_AES_128_GCM_SHA256 | AES-128-GCM | SHA-256 | Дефолт. AES в хардваре — везде быстрый. |
| TLS_AES_256_GCM_SHA384 | AES-256-GCM | SHA-384 | Если параноишь — больше ключ. |
| TLS_CHACHA20_POLY1305_SHA256 | ChaCha20-Poly1305 | SHA-256 | Лучше для мобильных без AES-NI. |
| TLS_AES_128_CCM_SHA256 | AES-128-CCM | SHA-256 | Редко. |
| TLS_AES_128_CCM_8_SHA256 | AES-128-CCM с 64-bit auth tag | SHA-256 | IoT, узкий канал. |
AEAD = Authenticated Encryption with Associated Data. Шифрование + integrity check в одном алгоритме. Никакого MAC-then-encrypt или encrypt-then-MAC — это уязвимости 1.2.
11. Post-quantum — что будет завтра
Квантовые компьютеры (когда они появятся) смогут сломать ECDHE и RSA. Сегодня атакующие могут хранить зашифрованные сессии и расшифровать через 10 лет, когда квантовые компьютеры станут реальностью. Это называется «harvest now, decrypt later».
Решение — post-quantum cryptography. В 2024 NIST стандартизировал ML-KEM (Kyber). Cloudflare, Google, Apple уже разворачивают X25519MLKEM768 — гибрид классической X25519 и Kyber. Это компромисс: если Kyber сломают — X25519 защитит. Если X25519 сломают (квантовым) — Kyber защитит.
Запомни: гибриды развёрнуты в Chrome, Cloudflare с 2024. Скоро ты увидишь это и на серверах.
12. mTLS — взаимная аутентификация
Обычный TLS аутентифицирует только сервер. Клиент анонимен (или авторизуется через Bearer-токен поверх TLS).
mTLS — обе стороны предъявляют сертификаты. Сервер в CertificateRequest просит клиента: «дай свой сертификат». Клиент шлёт свой Certificate + CertVerify (подписывает hash handshake-сообщений своим приватным ключом).
Где используется:
- Service Mesh (Istio, Linkerd) — каждый микросервис имеет свой cert, идентичность = cert
- Zero Trust — отказ от «сети как периметра», идентичность — основа доступа
- API между партнёрами — банки, фин-теки, медицина
- Device authentication — IoT
13. TLS terminator и middleware
В реальности TLS терминируется не на твоём приложении — а раньше:
- CDN (Cloudflare) — на edge
- WAF — между CDN и origin
- Load balancer (ALB / Nginx) — на границе твоего VPC
- Service mesh sidecar (Envoy) — на каждом pod
То есть один запрос может пройти через 3-4 TLS-туннеля. На каждом — отдельный handshake, сертификаты, OCSP.
Это нормально. Но за этим нужно следить:
- Все сертификаты должны вовремя обновляться
- Версии TLS должны быть согласованы
- mTLS внутри кластера — желательно
14. Мини-лаба: попробуй прямо сейчас
openssl есть почти в любой системе (в Windows — в составе Git Bash). Разбери «замочек» любого сайта:
# Полный handshake вживую
openssl s_client -connect example.com:443 -servername example.com -tls1_3
# Какие TLS-версии поддерживает сервер
nmap --script ssl-enum-ciphers -p 443 example.com
# Цепочка сертификатов и сроки
echo | openssl s_client -connect example.com:443 -showcerts 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
# Только дата истечения, ничего лишнего
echo | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -enddate
# OCSP — проверить статус
openssl ocsp -issuer chain.pem -cert cert.pem \
-url http://ocsp.example.com -resp_text
# Проверить весь сайт онлайн
# https://www.ssllabs.com/ssltest/ → A+ обязательно
# Сделать локальный самоподписанный для тестов
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=localhost"
В выводе s_client найди строки из этого урока: TLSv1.3 и cipher
(обычно TLS_AES_256_GCM_SHA384 или TLS_AES_128_GCM_SHA256), блок
Certificate chain — там видно leaf → intermediate (root сервер не шлёт: он уже в
твоём trust store), поле Verify return code: 0 (ok). Команда с
-noout -dates вернёт notBefore/notAfter — у Let's Encrypt разница будет 90 дней.
Проверь и провальный случай: openssl s_client -connect expired.badssl.com:443 —
увидишь certificate has expired, то самое сообщение из субботнего инцидента.
15. Что с этим делает DevOps
- cert-manager в K8s. Оператор, автоматически выпускает / обновляет сертификаты Let's Encrypt. Без него руками — больно.
- certbot на VM. Если без K8s — certbot решает то же самое.
- AWS ACM. Бесплатные сертификаты для ALB / CloudFront / API Gateway. Авторенью.
- Мониторинг истечения. Алерты «истекает через 14 дней». Не доводить до 0.
- TLS-конфиг. Mozilla SSL Config Generator → копируешь готовый конфиг для Nginx/Apache. ssl-config.mozilla.org
- HSTS. Заголовок
Strict-Transport-Securityс preload — обязательно. - CAA-записи в DNS. Защита от выпуска левых сертификатов.
- OCSP stapling. Включить, проверить через ssllabs.
- mTLS в Service Mesh. Istio / Linkerd — настроить strict mTLS внутри кластера.
- SSLLabs A+. Хотя бы раз в месяц прогоняй основной домен.
16. Задачи самопроверки — реши, потом раскрой ответ
Задача 1. Ночью истёк сертификат — но только на одном из слоёв: CDN показывает валидный, а мобильное приложение, ходящее мимо CDN прямо на балансировщик, получает ошибку. Как такое возможно и что внедрить, чтобы это не повторялось?
TLS терминируется несколько раз: у CDN свой сертификат (авто-обновляемый), у балансировщика — свой, и вот он-то и истёк. Браузерные пользователи ходят через CDN и ничего не видят, приложение — напрямую. Внедрять: инвентаризацию ВСЕХ точек терминации, автообновление на каждой (ACM/cert-manager/certbot) и внешний мониторинг срока именно тех endpoint'ов, куда реально ходят клиенты, с алертом за 14 дней.
Задача 2. Безопасность требует: «даже если приватный ключ сервера утечёт, записанный ранее трафик не должен расшифровываться». Какое свойство TLS это обеспечивает и почему в TLS 1.3 оно есть всегда?
Forward secrecy. Сессионные ключи выводятся из эфемерного ECDHE-обмена, который живёт один handshake, а не из долгоживущего ключа сертификата. В TLS 1.2 существовал RSA key exchange без этого свойства (украл ключ — расшифровал архив трафика), в TLS 1.3 он удалён: ECDHE обязателен, так что forward secrecy — не опция, а конструктивное свойство протокола.
Задача 3. Включили 0-RTT ради скорости, и пентест показал: запрос «перевести 100 $» можно записать и воспроизвести повторно. Почему это касается именно 0-RTT и как жить дальше?
Early data едет ДО завершения handshake, и у сервера нет способа отличить повтор: атакующий может переслать записанный 0-RTT-пакет ещё раз (replay). Поэтому 0-RTT разрешают только для идемпотентных запросов (GET без побочных эффектов); мутации должны ехать после полного рукопожатия — серверы и CDN умеют отвечать 425 Too Early, а приложение может требовать анти-replay токены.
Задача 4. На сервере один IP и десять HTTPS-сайтов. Благодаря какому полю ClientHello сервер выбирает правильный сертификат — и какую проблему приватности это поле создаёт?
SNI: клиент открытым текстом пишет имя хоста в ClientHello, и сервер (или балансировщик) по нему выбирает сертификат и бэкенд. Обратная сторона: провайдер и любой наблюдатель видят, НА КАКОЙ сайт ты идёшь, даже не расшифровывая трафик — по SNI работают блокировки. Решение — ECH (Encrypted Client Hello): настоящий SNI шифруется ключом из DNS-записи HTTPS/SVCB.
Задача 5. Wildcard-сертификат *.example.com прекрасно работает для api.example.com, но canary.api.example.com получает ошибку имени. Почему и какие варианты?
Wildcard покрывает ровно один уровень: *.example.com — это api, www, cdn, но не canary.api. Варианты: выпустить сертификат с нужным SAN (например, *.api.example.com или явное имя), пересмотреть схему имён, или использовать автоматический выпуск per-host (cert-manager + Let's Encrypt), где каждый хост получает своё имя в SAN.
17. Словарик урока
Канал зашифрован, ALPN сказал серверу «давай h2». Но что это меняет? Внутри защищённой трубы начинается совсем другой HTTP: бинарные кадры вместо текста, десятки параллельных запросов в одном соединении, сжатие заголовков — а HTTP/3 и вовсе выбрасывает TCP. Как выглядит современный HTTP изнутри — следующий этап.
Чек-лист «понимаю это»
- ☐ Назову, чем TLS 1.3 отличается от 1.2
- ☐ Объясню три фазы handshake: ClientHello, ServerHello, Finished
- ☐ Понимаю SNI и зачем ECH
- ☐ Объясню ALPN и связь с HTTP/2
- ☐ Понимаю ECDHE и forward secrecy
- ☐ Знаю, что в сертификате (SAN, срок, цепочка)
- ☐ Различаю CRL, OCSP, OCSP stapling, Must-Staple
- ☐ Объясню session resumption и 0-RTT (с замечанием про replay)
- ☐ Помню 5 cipher suites TLS 1.3
- ☐ Слышал про post-quantum гибриды (X25519MLKEM768)
- ☐ Знаю, где работает mTLS
- ☐ Прогнал свой домен через SSLLabs