Суть Читай реальный вывод openssl s_client, конфиг ticket-ключа Nginx, обработчик Early-Data и трассу key_share, затем выбирай диагноз и самое рычажное исправление.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Проблемы TLS диагностируются в трассах handshake, конфигах сервера и заголовках запросов — а не в учебных схемах. Прочитай каждый артефакт, предскажи поведение и выбери исправление, которое senior-инженер сделает первым.
Цель
Отработай цикл, который ты запускаешь в каждом инциденте TLS: прочитай вывод openssl или конфиг, найди нарушенный инвариант handshake и потянись за самым рычажным исправлением.
Сниппет 1 — проверка цепочки в openssl s_client
% openssl s_client -connect api.example.com:443 -tls1_3depth=0 CN = api.example.comverify error:num=20:unable to get local issuer certificateverify error:num=21:unable to verify the first certificate---Certificate chain 0 s:CN = api.example.com i:CN = R3 Issuing CA---New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384Verify return code: 21 (unable to verify the first certificate)
Викторина
Completed
Handshake завершается и cipher согласован, но verify return code равен 21. Что сконфигурировано неверно?
Heads-up Истечение даёт ошибку 10 (certificate has expired), а не 20/21. Ошибка 20/21 означает неполную цепочку issuer — intermediate отсутствует в том, что прислал сервер.
Heads-up Это стандартный suite TLS 1.3, и handshake успешно его согласовал. Ключи вывелись нормально; сбой — чисто построение цепочки, отдельный шаг от обмена ключами.
Heads-up openssl s_client шлёт SNI по умолчанию, и возвращённый CN совпадает с хостом. Цепочка для правильного хоста — ей просто не хватает intermediate, нужного клиенту для достижения корня.
Сниппет 2 — конфиг ticket-ключа сессии в Nginx
server { listen 443 ssl; ssl_protocols TLSv1.3; ssl_session_ticket_key /etc/nginx/ticket.key; # generated once at install ssl_early_data on; # ...}
Викторина
Completed
Этот конфиг работает без изменений год. Каков самый серьёзный дефект безопасности, учитывая, что ssl_early_data включён?
Heads-up Early data 0-RTT приемлема для идемпотентных маршрутов с защитой от replay. Глубже здесь дефект — никогда не ротируемый, лежащий на диске STEK, который подрывает forward secrecy возобновления независимо от early data.
Heads-up Ограничение до TLS 1.3 — это выбор в пользу безопасности, а не дефект. Проблема безопасности — статичный, лежащий на диске ticket-ключ.
Heads-up Абсолютность пути несущественна для безопасности TLS. Настоящая проблема в том, что ключ статичен и на диске — STEK должны ротироваться и оставаться в памяти.
Сниппет 3 — обработчик запроса Early-Data
app.post("/transfer", (req, res) => { // money movement if (req.headers["early-data"] === "1") { // request arrived in TLS 0-RTT early data } doTransfer(req.body); res.send("ok");});
Викторина
Completed
Заголовок Early-Data читается, но обработчик всё равно вызывает doTransfer в любом случае. Что пойдёт не так при replay и каково верное исправление?
Heads-up Чтение заголовка само по себе ничего не меняет; код всё ещё выполняет мутацию. Нужно отклонять с 425 Too Early на изменяющих маршрутах при Early-Data равном 1, заставляя повтор после handshake.
Heads-up Early data несёт полный запрос, включая тело; он обрабатывается нормально. Опасность именно в том, что он обрабатывается — и может быть переигран, чтобы обработаться дважды.
Heads-up TLS доставляет early data приложению; он не знает, что твой маршрут не идемпотентен. Обеспечение идемпотентности — задача уровня приложения через 425 Too Early.
Клиент предложил x25519 и гибридный X25519MLKEM768, но сервер прислал HelloRetryRequest с запросом secp256r1. Что произошло и какова цена?
Heads-up HRR касается чисто согласования группы key_share, а не аутентификации. Он шлётся, когда ни одна из предложенных клиентом групп не совпадает с тем, что использует сервер.
Heads-up X25519MLKEM768 — валидная гибридная named group; сервер просто её не поддерживает. Handshake продолжается через HRR с группой, принимаемой обеими сторонами, стоит одного RTT, а не обрывается.
Heads-up HRR остаётся внутри TLS 1.3 и лишь пересогласует группу key_share. Версию протокола он не меняет; попытки понижения сломали бы проверку transcript в Finished.
Итог
Каждый инцидент TLS читается в артефактах: код verify 20/21 в openssl означает неполную цепочку — отправь intermediate; статичный лежащий на диске ssl_session_ticket_key разрушает forward secrecy возобновления — ротируй STEK ежечасно в памяти; обработчик Early-Data, который мутирует без возврата 425 Too Early, поддаётся replay — гейти изменяющие маршруты по заголовку Early-Data; а HelloRetryRequest с именем другой группы означает, что предложенные key_share не попали — предлагай x25519 плюс P-256 сразу, чтобы избежать лишнего RTT. Прочитай артефакт, найди нарушенный инвариант, исправь причину.