Вы обновили сертификат. Файлы на сервере. Дата истечения в новом сертификате комфортно в будущем. И всё же: некоторые пользователи сообщают о предупреждениях, ваше мобильное приложение выбрасывает ошибки TLS, curl отказывается подключаться, или браузер весело показывает сертификат, истёкший на прошлой неделе.
Эта категория проблем расстраивает именно потому, что то, что вы изменили, действительно корректно. Сертификат в порядке. Проблема почти всегда где-то в разрыве между «файлом на диске» и «тем, что клиент реально получает и проверяет».
Вот как этот разрыв открывается, и как найти, где находится ваш.
Цепочка, и почему она существует
Сертификат сам по себе ничего не доказывает. То, что делает его заслуживающим доверия — это цепочка подписей, ведущая обратно к чему-то, чему клиент уже доверяет.
Есть три уровня:
Листовой сертификат. Ваш. Он говорит «это yourdomain.com», и он подписан промежуточным.
Промежуточный сертификат. Принадлежит вашему удостоверяющему центру. Он подписал ваш лист, и сам подписан корнем. CA используют промежуточные, чтобы приватный ключ корня мог оставаться запертым в офлайн-хранилище — если промежуточный скомпрометирован, его отзывают, не сжигая корень.
Корневой сертификат. Самоподписанный, и доверенный, потому что он предустановлен в хранилище доверия клиента — наборе CA, поставляемом с вашей ОС или браузером. Никто не ручается за корень. Ему доверяют по декрету.
Проверка идёт вверх: лист подписан промежуточным, промежуточный подписан корнем, корень в моём хранилище доверия, следовательно листу доверяют.
Теперь критическая часть, и источник большинства багов с цепочкой:
Ваш сервер должен отправлять лист и промежуточные. Клиент сам предоставляет корень.
Корень уже на клиенте. Отправлять его безвредно, но бессмысленно. Промежуточных нет на клиенте, и если ваш сервер их не отправляет, у клиента есть лист, который он не может подключить ни к чему, чему доверяет — и проверка проваливается.
Баг, который ломается только для некоторых людей
Вот где это становится по-настоящему изматывающим. Вы неправильно настраиваете цепочку, опуская промежуточный. Затем:
- Chrome работает нормально.
- Firefox работает нормально, обычно.
curlпроваливается.- Ваш Java-сервис проваливается.
- Старые Android-устройства проваливаются.
- Ваш мониторинг говорит, что всё зелёное.
Тот же сервер, тот же сертификат, совершенно разные вердикты. Выглядит как бессмыслица. Не является.
Chrome и Windows сами заберут отсутствующий промежуточный. Сертификаты содержат расширение под названием Authority Information Access (AIA), которое включает URL, по которому можно скачать выпускающий сертификат. Когда цепочка неполна, эти клиенты тихо идут забрать его и завершают цепочку сами. Соединение проходит успешно, и вы никогда не узнаёте, что что-то не так.
Firefox кэширует промежуточные, которые он видел раньше. Если он встречал этот промежуточный на любом другом сайте, он уже есть у него, и он будет его использовать — так что у вас работает, а у того, чей браузер его ещё не видел, не работает. Именно поэтому «у меня на машине работает» так живуче здесь: ваш браузер годами накапливал промежуточные.
curl, OpenSSL, Java, Go и большинство небраузерных клиентов не делают ничего из этого. Они строго проверяют ровно то, что отправил сервер. Если промежуточный отсутствует, они проваливаются. Правильно.
Так что отсутствующий промежуточный — это не сломанный сайт, это сайт, который работает в браузерах и проваливается для каждого API-клиента, мобильного приложения, потребителя вебхуков и платёжной интеграции, которые у вас есть. Что обычно хуже, чем быть откровенно сломанным, потому что замечается дольше.
Урок: никогда не проверяйте цепочку сертификата с помощью браузера. Браузеры слишком услужливы. Проверяйте чем-то, что сообщает точно, что реально отправил сервер, например проверка SSL-сертификата, которая показывает доставленную цепочку, а не восстановленную.
«Он обновился, но я всё ещё вижу старый сертификат»
Пять реальных причин, примерно в порядке проверки:
Сервер никогда не перезагружался. Классика. Ваше обновление записало новые файлы, и ничто не сообщило работающему процессу. nginx, Apache, HAProxy и подобные читают сертификат в память при запуске и держат его там бессрочно. Файл изменился; процесс не заметил. Требуется перезагрузка — и если хук перезагрузки вашей автоматизации обновления тихо провалится, вы пройдёте мимо срока истечения со свежими файлами на диске и мёртвым сертификатом в памяти.
Отвечает другой блок сервера. С SNI один IP может обслуживать много сертификатов, выбираемых по имени хоста, которое запрашивает клиент. Если у вас несколько блоков сервера, а имя хоста не совпадает с тем, о котором вы думаете, вы получаете сертификат блока по умолчанию вместо этого. Обновление сертификата, который вы хотели обслуживать, не помогает, потому что это не тот, что обслуживается. Это также производит забавный симптом, когда появляется сертификат совершенно не того домена.
Прокси или CDN терминирует TLS. Если вы за Cloudflare или подобным, сертификат, который видит пользователь, — это сертификат edge, не ваш. Ваш origin-сертификат — отдельная вещь на отдельном жизненном цикле, актуальная только для соединения между CDN и вашим сервером. Обновление origin ничего не делает с тем, что видят пользователи, и наоборот. Два сертификата, две даты истечения, два режима отказа — и люди регулярно обновляют один, пока другой тихо истекает.
Только некоторые бэкенды получили новый сертификат. За балансировщиком нагрузки с несколькими серверами, если обновление выполнилось на одном, вы получите перемежающиеся отказы в зависимости от того, какой бэкенд отвечает. Кажется случайным. Не является.
OCSP-степлинг обслуживает устаревший ответ. Серверы кэшируют OCSP-ответ, который они прикрепляют к handshake. Если этот закэшированный ответ ссылается на старый сертификат, или OCSP-респондер был недоступен при последнем обновлении, клиенты, которые жёстко проваливаются при проблемах со степлингом, отклонят иначе идеальный сертификат.
Отличающий вопрос для всех этих случаев один и тот же: что сервер реально отправляет прямо сейчас, по проводу? Не то, что на диске. Не то, что говорит ваша панель управления. Что приходит клиенту. Всё остальное после этого — сужение того, какой слой лжёт.
Порядок имеет значение, и истёкшие промежуточные реальны
Ещё две детали уровня цепочки стоит знать.
Цепочка должна быть по порядку. Сначала лист, затем каждый сертификат, подписавший предыдущий. Некоторые клиенты терпят перемешанную цепочку; многие нет. Если ваш файл сертификата был собран конкатенацией того, что пришло в каком порядке, это стоит проверить.
Промежуточные и корни тоже истекают, и это вызывало реальные сбои. Самый показательный пример: в сентябре 2021 года кросс-подписанный корневой сертификат, на который Let's Encrypt полагался для совместимости со старыми устройствами, истёк. Миллионы сертификатов, которые сами по себе были совершенно валидны, внезапно провалились на старых устройствах Android, OpenSSL 1.0.x, и длинном хвосте встроенных клиентов. Ничей листовой сертификат не истёк. Сертификат тремя уровнями выше истёк, и всё под ним рухнуло.
Вот форма проблемы: валидность вашего сертификата необходима, но далеко не достаточна. Весь путь должен держаться.
Порядок отладки, который работает
-
Проверьте, что реально обслуживается, а не что на диске. Проверка SSL покажет доставленную цепочку, срок истечения и покрытие имени хоста. Если это расходится с вашими файлами, вы нашли свой слой — перезагрузка, не тот блок сервера, или прокси спереди.
-
Подтвердите, что цепочка полная. Ищите конкретно промежуточный. Если лист есть, а промежуточного нет, у вас баг «работает в браузерах, проваливается везде ещё», сообщил ли о нём кто-нибудь уже или нет.
-
Проверьте покрытие имени хоста. Сертификат, валидный для
yourdomain.com, не покрываетwww.yourdomain.com, если он не перечислен в поле SAN. Поле Common Name эффективно игнорируется современными клиентами — учитываются только Subject Alternative Names. -
Проверьте конфигурацию протокола и шифра отдельно. Совершенно валидный сертификат всё равно проваливается, если клиент и сервер не могут согласовать версию TLS — клиент, говорящий только TLS 1.2, против сервера, настроенного только на 1.3. Это вообще не проблема сертификата, но проявляется как сбой соединения и ошибочно диагностируется как таковая. Сканер TLS покажет, какие версии и шифры реально согласуются.
-
Если вы подозреваете, что сертификат вообще никогда не выпускался, проверьте логи CT. Каждый публично доверенный сертификат логируется в логи Certificate Transparency при выпуске. Поиск по логам CT точно скажет вам, что было выпущено для вашего домена и когда — что окончательно решает «действительно ли произошло обновление», независимо от чего-либо на вашем сервере. Это также способ найти сертификаты, выпущенные для вашего домена, которые вы не запрашивали, что является другим и более тревожным разговором.
Мысленная модель, которую стоит сохранить
Сертификат — это не файл, который вы устанавливаете. Это утверждение, которое должно пережить цепочку проверки, выполняемую клиентом, которого вы не контролируете, использующим хранилище доверия, которое вы не выбирали, через соединение, которое может быть терминировано прокси, о котором вы забыли.
Почти каждая проблема «но я же обновил» живёт где-то в этом предложении. Файл редко является проблемой. Путь между файлом и клиентом почти всегда является — и самый быстрый способ найти его — перестать смотреть на свой сервер и начать смотреть на то, что приходит на другом конце.