TLS и PKI
TLS 1.3 строит зашифрованный канал за один round trip и доказывает, с кем вы говорите, цепочкой сертификатов, закреплённой в доверенном CA. Так браузер решает, что зелёный замок честен, — и так это ломается.
Пейджер звонит в 03:00: каждый запрос к api.payments.internal падает с x509: certificate has expired. Сервис не менялся, код не менялся — число в файле перевалило с notAfter: 2026-06-13 на секунду позже, и парк клиентов, доверявший этому серверу два года, разом отказался с ним говорить. Ни атакующего, ни бага, ни деплоя. Просто дата. Та же машинерия, что тихо не дала этому сбою стать незаметным man-in-the-middle, и есть машинерия, которая только что вас уронила: сертификат, цепочка и клиент, решающий, доверять ли. Поймёшь эту машинерию — перестанешь ею удивляться.
К концу урока ты будешь знать, как TLS 1.3 поднимает зашифрованный, аутентифицированный канал за один round trip, и как именно браузер решает, что сертификат валиден, — цепочка, CA, trust store и проверки между ними.
Что TLS на самом деле обязан сделать
У TLS две задачи, которые постоянно путают: конфиденциальность (никто на проводе не прочитает байты) и аутентификация (вы действительно говорите с payments.example.com, а не с самозванцем посередине). Одно шифрование без второго бесполезно — атакующий, который перехватывает трафик, с тем же успехом поднимет свой зашифрованный туннель к вам, читая всё насквозь. Весь смысл сертификатной машинерии — привязать ключи шифрования к проверенной личности, чтобы канал был и приватным, и доказуемо к нужной стороне.
TLS 1.3 (RFC 8446) — это намеренный сброс протокола. Он удалил опасные части TLS 1.2 — статический обмен ключами RSA, шифры в режиме CBC, RC4, сжатие, renegotiation — и оставил только конструкции с forward secrecy и AEAD. Практических следствий два: рукопожатие быстрее (один round trip вместо двух для нового соединения и опциональный zero round trip для возобновления), и множество способов всё неправильно настроить резко сократилось. Не осталось рычага «откатиться на экспортную крипту», который можно повернуть не туда.
Рукопожатие 1.3 за один round trip
Вот последовательность для свежего соединения. Клиент открывает ClientHello, который уже содержит key share — эфемерное публичное значение Диффи-Хеллмана — для кривых, которые сервер, по его догадке, примет. Эта догадка и есть приём, схлопывающий два round trip в один: клиент не ждёт, пока ему скажут, какие параметры использовать, — он спекулирует.
Сервер отвечает ServerHello со своим key share. В этот миг обе стороны могут вычислить общий секрет через Диффи-Хеллмана и вывести симметричные ключи рукопожатия — поэтому всё, что сервер шлёт дальше, уже зашифровано, включая сертификат. Затем идёт часть, которая собственно кого-то аутентифицирует:
- Certificate: листовой сертификат сервера плюс промежуточные, нужные для построения цепочки.
- CertificateVerify: подпись, сделанная приватным ключом сертификата, над транскриптом всего рукопожатия до этого момента. Это шаг, доказывающий, что сервер владеет приватным ключом, соответствующим публичному ключу в сертификате, — а не просто скопировал чужой файл сертификата.
- Finished: MAC над транскриптом, запирающий рукопожатие от подмены.
Клиент проверяет цепочку и подпись CertificateVerify, шлёт свой Finished, и идут данные приложения. Один round trip из конца в конец.
▸Почему это работает
Почему CertificateVerify — несущий шаг? Потому что сертификат публичен — кто угодно скачает сертификат payments.example.com. Чего самозванец не сможет — это произвести валидную подпись над этим конкретным транскриптом рукопожатия без соответствующего приватного ключа. Эта подпись, пересчитанная над данными, включающими свежие key share обеих сторон, и связывает «я предъявляю эту личность» с «я могу прямо сейчас доказать, что владею ею». Уберите её — и TLS не аутентифицирует ничего.
Сертификаты, цепочки и CA
Сертификат — это подписанное утверждение: «этот публичный ключ принадлежит этому имени, и я, издатель, ручаюсь за это до этой даты истечения». Имя живёт в поле Subject Alternative Name (SAN) (старый Common Name современные клиенты игнорируют). Критично: листовому сертификату сервера не доверяют напрямую. Он подписан промежуточным CA, который подписан корневым CA. Эта последовательность — лист → промежуточный → корень — и есть цепочка сертификатов.
Причина этого слоя косвенности операционная, а не криптографическая. Приватные ключи корневых CA хранят офлайн в аппаратных модулях безопасности (HSM) и почти никогда не трогают, потому что утечка корневого ключа обесценивает все сертификаты, что он когда-либо закреплял, и его нельзя тихо заменить — он зашит в миллиарды устройств. Промежуточные делают повседневную подпись и могут быть отозваны и ротированы без апокалипсиса. Сервер обязан слать свой лист и промежуточные; сервер, отдающий только лист, выдаёт классическую ошибку «цепочка не доверена» на клиентах, у которых этот промежуточный не закэширован.
| Звено цепочки | Кем подписан | Где живёт ключ | Если скомпрометирован |
|---|---|---|---|
| Лист (ваш сервер) | Промежуточный CA | На сервере / балансировщике | Отозвать + перевыпустить; срок ~90 дней ограничивает радиус |
| Промежуточный CA | Корневой CA | В HSM удостоверяющего центра, онлайн для подписи | Отозвать промежуточный; ротировать; перевыпустить его листы |
| Корневой CA | Сам себя (самоподписанный) | Офлайн HSM, трогают редко | Катастрофа: убирается из всех trust store, заменяется медленно |
Как браузер решает, что сертификат валиден
Вот часть, которую инженеры чаще всего отмахивают. Когда приходит лист, клиент прогоняет цепочку проверок, и все они должны пройти — провал любой одной и вызывает страшное полноэкранное предупреждение:
- Построить цепочку к доверенному якорю. Клиент идёт лист → промежуточный → корень и подтверждает, что корень есть в его trust store — предустановленном списке корневых CA операционной системы или браузера, которым решено доверять априори. Доверие не магия; оно упирается в этот выверенный список из нескольких сотен корней. Если цепочка не оканчивается на одном из них — сертификат не доверен, точка.
- Проверить каждую подпись вверх по цепочке. Подпись каждого сертификата должна валидироваться против публичного ключа следующего сертификата. Разрыв где угодно означает, что цепочка подделана или неполна.
- Проверить даты валидности. Каждый сертификат в цепочке должен быть в своём окне
notBefore/notAfter. Это и есть пейджер в 03:00 про истёкший сертификат. - Проверить имя. Имя хоста, которое запросил клиент, должно совпасть с записью SAN на листе. Подключение к
payments.example.comи получение сертификата дляexample.comбез совпадающего SAN — провал; именно это останавливает атакующего, предъявляющего валидный сертификат для домена, которым он владеет. - Проверить отзыв. Не отозвал ли CA этот сертификат досрочно (компрометация ключа, ошибочная выдача)? Проверяется через CRL или, чаще, OCSP / OCSP stapling, когда сервер прикладывает свежее подписанное доказательство «всё ещё годен», чтобы клиенту не звонить CA.
Ваш внутренний сервис `payments.internal` предъявляет сертификат. Клиенты отвергают его с 'certificate signed by unknown authority', хотя сам сертификат корректен и не истёк. Какой фикс верный?
Что в рукопожатии TLS 1.3 сообщение сервера `CertificateVerify` на самом деле доказывает, чего не доказывает само по себе сообщение `Certificate`?
Клиент подключается к `payments.example.com`, и сервер предъявляет криптографически валидный, не истёкший, корректно собранный в цепочку сертификат — но для имени `cdn.example.com`. Что произойдёт?
Расположи шаги свежего рукопожатия TLS 1.3 от первого к последнему:
- 1 ClientHello с эфемерным key share
- 2 ServerHello с key share сервера — обе стороны выводят ключи
- 3 Сервер шлёт Certificate + CertificateVerify (уже зашифрованно)
- 4 Клиент валидирует цепочку, подпись, имя и даты
- 5 Клиент шлёт Finished; идут данные приложения
- 01Пройди по полному рукопожатию TLS 1.3 и объясни, как оно аутентифицирует сервер за один round trip.
- 02Перечисли каждую проверку, которую клиент выполняет, решая, что серверный сертификат валиден, и объясни, от чего каждая защищает.
У TLS две задачи: конфиденциальность (шифрование на проводе) и аутентификация (вы действительно говорите с названным сервером), и сертификатная машинерия существует ради второй. TLS 1.3 удалил небезопасные части 1.2 и делает рукопожатие за один round trip: клиент спекулирует эфемерный key share в ClientHello, сервер отвечает своим, обе стороны сразу выводят ключи, и остаток рукопожатия — включая сертификат — идёт зашифрованно. CertificateVerify — несущий шаг: подпись над транскриптом рукопожатия, доказывающая, что сервер владеет приватным ключом, а не просто копией публичного сертификата. Сертификаты образуют цепочку — лист → промежуточный → корень, — потому что корни держат офлайн и почти не трогают, тогда как промежуточные делают повседневную подпись и могут ротироваться. Браузер принимает сертификат только когда проходят все пять проверок: цепочка закреплена в корне из trust store, все подписи сходятся, даты в окне, SAN совпадает с именем хоста и сертификат не отозван. Теперь, когда клиент говорит ‘unknown authority’, ты знаешь, что это проблема якоря доверия, — и фикс в том, чтобы доверять якорю, а не отключать проверку.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.