TLS 1.3 handshake: ClientHello with key_share (ECDHE), ServerHello+Certificate+Finished in 1 RTT, 0-RTT resumption with PSK and replay risk, comparison with TLS 1.2 (2 RTT), forward secrecy via ephemeral ECDHE, and certificate validation failures (chain/expiry/SAN/mTLS). Free concept lesson #53 in foundations.
TLS — это слой, который превращает untrusted internet в secure pipe. Без него любой Wi-Fi-сосед читает твои cookies и логины, любой ISP подменяет реквизиты платежей, любой ноутбук в кофейне доверяет «банковской форме» от MITM. HTTPS, gRPC, WebSocket, postgres sslmode=require, Kafka SASL_SSL, mTLS в service mesh — везде один и тот же handshake.
Знать TLS нужно не чтобы выучить наизусть ASN.1 структуру X.509, а чтобы:
CERT_AUTHORITY_INVALID, DATE_INVALID, COMMON_NAME_INVALID — три разные первопричины, три разных fix-а.expired in prod outage.TLS делает две вещи: (1) договаривается о ключах шифрования без передачи самих ключей по wire (Diffie-Hellman), и (2) проверяет identity сервера через PKI (цепочка подписей до root CA в OS trust store). Всё остальное — детали, ciphers и оптимизации round-trip'ов.
Handshake — самая дорогая часть TLS. После него уже идёт обычный symmetric encryption (AES-GCM или ChaCha20-Poly1305) — быстрый, дешёвый, hardware-accelerated на современных CPU. Поэтому почти всё развитие TLS за 25 лет — это попытки укоротить handshake: с 2 RTT (TLS 1.2) до 1 RTT (TLS 1.3) до 0 RTT (resumption). Каждый RTT на 4G mobile = ~100ms; для пользователя из Австралии до US-east — ~250ms. Tooling вокруг latency не врёт.
PKI — отдельная религия. Корни доверия (root CA certs) встроены в OS и браузер; всё остальное проверяется относительно них. Когда видишь NET::ERR_CERT_* — значит, какая-то проверка в этой цепочке сломалась.
Четыре группы:
browser, client-tls, client-cache) — приложение, TLS-стек и PSK session cache для resumption.wire) — untrusted транспорт. Всё, что выходит наружу, должно считаться подслушиваемым.server-tls, cert, app) — TLS-стек, сертификат с приватным ключом, приложение (nginx/api), которое получает уже расшифрованный plaintext.ca, root) — issuing CA (Let's Encrypt) и root CA в OS trust store.Edges — физические соединения: socket'ы внутри хостов, TCP-сегменты через wire, иссуенс сертификата через PKI. Анимация (FlowBuilder) показывает поток сообщений ПО ЭТИМ edges — handshake идёт в обе стороны по одним и тем же проводам.
TLS 1.3 (1-RTT) — современный baseline. ClientHello несёт сразу всё необходимое: список cipher suites, supported_groups (X25519, P-256) и — главное — key_share с ephemeral ECDHE public key клиента. Сервер выбирает cipher, генерирует свою ECDHE keypair, выводит shared_secret = ECDH(client_pub, server_priv) и через HKDF делает handshake_traffic_keys. Уже под этими ключами летят Certificate + CertificateVerify + Finished. Клиент валидирует chain до root, проверяет SAN, отвечает своим Finished — handshake done за один round-trip. После handshake сервер сразу шлёт NewSessionTicket для будущего 0-RTT.
0-RTT resumption (PSK). При повторном визите клиент кладёт PSK ticket + early_data в самый первый flight: ClientHello + GET /api/v1/feed едут одним пакетом. Сервер извлекает early_traffic_keys из PSK и передаёт запрос в app до завершения handshake. Latency-выигрыш огромен (на 4G mobile = -100ms), но early_data повторяема: атакующий, перехвативший ClientHello, может пересохранить его через час, и сервер без anti-replay выполнит запрос дважды. Поэтому 0-RTT — только для idempotent GET; для POST/PUT нужно дождаться полного handshake или иметь anti-replay cache по (PSK_id, ClientHello.random).
TLS 1.2 vs 1.3 RTT comparison. В TLS 1.2 ClientHello НЕ несёт key_share — клиент сначала узнаёт, что выбрал сервер, и только потом шлёт KeyExchange. Итого: 2 RTT TLS поверх 1 RTT TCP = 3 RTT до first byte. В TLS 1.3 ClientHello уже спекулятивно несёт key_share для популярных групп — серверу нужен только один ответ. 1 RTT TLS + 1 RTT TCP = 2 RTT до first byte. С 0-RTT и TCP Fast Open можно дойти до 1 RTT. На mobile в 4G (100ms RTT) это разница 300 → 200 → 100 ms до first byte — заметная глазу.
Forward Secrecy через ECDHE. Каждая сессия использует НОВУЮ ephemeral ECDHE keypair у клиента И у сервера. Cert privkey используется только для подписи transcript_hash (доказывает владение доменом), но НЕ участвует в derive ключей. Поэтому через год, когда атакующий выкрадет cert privkey (insider, утёкший бэкап), у него по-прежнему нет ключей к историческому pcap — ephemeral ключи давно стёрты. В контрасте: TLS 1.2 с RSA key exchange шифровал premaster_secret публичным ключом сертификата — компромет cert privkey раскрывает ВСЁ ретроактивно. NSA-style «harvest now, decrypt later» — реальный сценарий, поэтому TLS 1.3 полностью запретил RSA key exchange.
Certificate validation failures. Три классические ошибки + bonus mTLS. Self-signed cert → CERT_AUTHORITY_INVALID (chain не доходит до trusted root). Expired cert (notAfter в прошлом) → CERT_DATE_INVALID. Wrong SAN (cert на www.example.com, а запрашиваем api.example.com) → CERT_COMMON_NAME_INVALID. И mTLS: сервер шлёт CertificateRequest, клиент отвечает своим cert (issued by internal CA), сервер валидирует его по тем же правилам chain/SAN/expiry. Это основа zero-trust между сервисами без API-ключей.
ADR-1: Forward Secrecy обязательно — TLS 1.3 only, ECDHE only.
Контекст: компромет cert privkey — это «когда», не «если». TLS 1.2 + RSA key exchange даёт ретроактивную дешифровку: pcap всех прошлых сессий становится читабельным. Государственные акторы копят шифрованный трафик именно в расчёте на этот момент.
Решение: на всех endpoints — TLS 1.3 (или TLS 1.2 с явным запретом RSA key exchange). Cipher policy: ECDHE-* only. Cert privkey подписывает transcript, не шифрует secret.
Последствия: компромет cert раскрывает только future сессии — успеваешь ротировать. Прошлый pcap бесполезен. Цена — нельзя поддерживать legacy-клиентов на TLS 1.0/1.1 (но PCI и так требует 1.2+).
ADR-2: ECDHE поверх классического DHE.
Forward secrecy можно сделать и через finite-field Diffie-Hellman. ECDHE с X25519 (или secp256r1) выигрывает по трём осям: стойкость (256-bit curve ≈ 3072-bit DHE prime), производительность (5–10× быстрее на CPU — критично для edge с миллионами handshake/sec), размер (key_share 32 байта vs 384 для DHE-3072 — меньше bandwidth, меньше шанс TCP fragmentation первого flight). Logjam-атака 2015 года показала, что 1024-bit DH primes брейкабельны гос-актором; ECDHE этому классу атак неуязвим.
ADR-3: 0-RTT (early data) — только для idempotent запросов.
0-RTT даёт -100ms на mobile, но early_data повторяема: тот же ClientHello + EarlyData можно ресэндить через час, и сервер выполнит запрос дважды. Решение: 0-RTT включён только для GET/HEAD/OPTIONS; все mutating endpoints (POST/PUT/DELETE/PATCH) отвергают early_data — handshake должен завершиться. Опционально: anti-replay cache по (PSK_id, ClientHello.random) с TTL = ticket_lifetime. Latency-выигрыш сохраняется для read-heavy профиля (~80% трафика), state changes защищены от replay.
Strict-Transport-Security первый visit пользователя по http:// уязвим к SSL-stripping (sslstrip). HSTS + preload list гарантируют, что браузер вообще не попробует HTTP.expired cert outage. Поставь monitoring на 30 дней до expiry, alert в pager.find . -name "*.key" | xargs git log -- находит результаты в каждом крупном репо. Используй secrets manager (Vault, AWS Secrets Manager, sealed-secrets).TLS — это всегда «да» для production traffic. Реальные случаи отказа:
«Внутри VPC можно без TLS» — это не оптимизация, это техдолг. Zero-trust mesh с mTLS должен быть default; opt-out требует доказательства.