DarkRiDDeR16 мин

TLS-сертификат: почему «curl работает» не закрывает проверку

SecurityHTTP

Проблема обычно выглядит противоречиво: браузер открывает адрес, а сервисный клиент получает certificate error; либо один контейнер подключается, а второй — нет. Цена ошибки — отключить проверку TLS «временно», потерять имя хоста в диагностике и превратить сетевую проблему в уязвимость.

Причина в том, что «сертификат валиден» — это не одна проверка. Клиент строит цепочку до доверенного корня, проверяет период действия, имя назначения и ограничения сертификата. Разные хранилища корней, SNI, proxy и часы системы меняют результат. Нужно разделить слой протокола, X.509-структуру и локальную политику доверия.

Что проверяет клиент до HTTP

TLS handshake создаёт защищённый канал, но доверие к peer не появляется из шифрования автоматически. Сертификат содержит открытый ключ, имя и подпись издателя; клиент проверяет цепочку и применимость к назначенному хосту. Если запрос идёт на api.example.test, сертификат только для admin.example.test не должен считаться подходящим из-за того, что ключ технически рабочий.

Период действия проверяется по часам клиента. Ошибка в системном времени даёт симптом «сертификат ещё не действителен» или «истёк», хотя сервер ничего не менял. Переход на другой контейнер может поменять корневое хранилище и набор промежуточных сертификатов. Поэтому при сравнении сред нужно собирать не только URL, но и hostname, SNI, trust store, время и цепочку.

Минимальная проверка сертификата
ПроверкаВопросОтказЧто собрать
Срокnow между notBefore и notAfter?not-yet-valid / expiredUTC-время клиента и поля сертификата
Имяhost есть в SAN?hostname mismatchSNI, hostname и SAN
Цепочкаесть путь до доверенного корня?unknown issuerleaf, intermediate, trust store
Подписьалгоритм и ключ разрешены?signature/algorithm errorTLS policy и negotiated version
Отзывполитика проверяет статус?revoked/unknownOCSP/CRL policy и доступность

Почему отключение verify ухудшает диагностику

Флаг вроде insecure меняет вопрос с «можно ли доверять peer» на «зашифрован ли канал до кого-то». Запрос начинает проходить, но факт успеха перестаёт говорить о подлинности сервера. Если потом этот флаг попадёт в общий клиент или пример конфигурации, временная отладка станет постоянной дырой.

Надёжнее вывести диагностическую информацию без обхода проверки: имя хоста, SNI, цепочку, срок, код ошибки и идентификатор корня. В тестовой среде можно добавить собственный CA в доверенное хранилище или передать его явно. Такой путь сохраняет настоящую проверку и делает отличие среды видимым.

Матрица TLS-проверки: сертификат проходит срок действия, имя SAN, цепочку доверия и алгоритмическую политику до начала HTTP-обмена.
Диаграмма разделяет данные сертификата и локальную политику. Успешный запрос одного клиента не является доказательством для другого trust store.

Runnable-пример: срок и SAN как отдельные причины

Функция ниже не строит X.509-цепочку и не заменяет TLS-библиотеку. Она принимает ISO-даты, hostname и список SAN, затем показывает две базовые проверки, которые полезно видеть в тестах и диагностическом выводе. Входы специально простые; ожидаемый результат различает успех, истёкший сертификат и несовпадение имени.

import { validateCertificateWindow } from './upgrade-2027-10.mjs';

const common = {
  host: 'api.example.test',
  sans: ['api.example.test', 'api.internal.test'],
  notBefore: '2026-01-01T00:00:00Z',
  notAfter: '2027-01-01T00:00:00Z',
};

console.log(validateCertificateWindow({ ...common, now: '2026-06-01T00:00:00Z' }));
console.log(validateCertificateWindow({ ...common, host: 'cdn.example.test', now: '2026-06-01T00:00:00Z' }).reason);
// { ok: true, reason: 'certificate-window-and-san-match' }
// host-not-listed-in-san

Порядок проверки в среде

  1. Зафиксируйте точный hostname и порт, который видит TLS-клиент. IP-адрес в логе не заменяет имя для проверки SAN.
  2. Проверьте часы контейнера и узла в UTC. Ошибку времени нельзя лечить повторной загрузкой сертификата.
  3. Снимите leaf и intermediate без отключения verify. Сравните цепочку с trust store конкретного процесса.
  4. Проверьте SAN, SNI и redirect. Сертификат для исходного адреса не обязан подходить для нового host после перенаправления.
  5. Разделите ошибку доверия, имени, срока и алгоритма. Для каждой причины оставьте отдельный тестовый fixture.
  6. Исправляйте trust store или цепочку на сервере; флаг обхода проверки не используйте как решение.

Цепочка не равна доверию

Наличие intermediate в файле сервера не означает, что клиент доверяет корню. И наоборот, локальный trust store может содержать корень, но сервер не отправить промежуточный сертификат. Успешная проверка строит путь по подписи и ограничениям, а не по совпадению строк в PEM-файле. Это объясняет, почему «в браузере работает» может быть правдой одновременно с ошибкой минимального контейнера.

Сертификат также не сообщает всю эксплуатационную политику. Клиент может проверять отзыв, запрещать старый алгоритм или требовать минимальную версию TLS. Если диагностический отчёт пишет только «certificate valid», он скрывает полезную часть причины. Сохраняйте код ошибки библиотеки и параметры соединения, а секретный ключ и полное содержимое лишний раз не логируйте.

Ограничения и следующий шаг

Учебная функция не проверяет подпись, CRL, OCSP, wildcard-правила, DNS и реальное TLS-согласование. Она не является security scanner. Её роль — сделать две часто потерянные проверки явными и тестируемыми без сетевой зависимости.

Следующий шаг — воспроизвести ошибку в том же контейнере, где работает сервис, собрать hostname/SNI, цепочку, время и код ошибки, а затем исправить конкретный слой. После исправления оставьте regression test на истёкший срок и неверный SAN, чтобы повторное «временное» отключение доверия стало заметным.

Проверяемые источники

  • RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3 — IETF, август 2018 года, RFC 8446, Standards Track. Применение: Описывает TLS 1.3 как протокол защищённого канала и задаёт место проверки сертификата внутри handshake. Граница: Не перечисляет доверенные корни конкретной ОС и не гарантирует настройку вашего клиента.
  • RFC 5280 — Internet X.509 Public Key Infrastructure Certificate and CRL Profile — IETF, май 2008 года, RFC 5280, Standards Track. Применение: Описывает X.509-поля, цепочку сертификации, расширения и правила проверки имени/срока. Граница: Не заменяет системное хранилище доверия, отзыв сертификата и политику конкретного домена.