HTTPS: имя, сертификат, срок
Чтение и практика ~75 мин
вы называете, какую из проверок HTTPS не прошёл запрос — достижимость адреса и порта, доверие к цепочке, имя в сертификате или срок действия; различаете роли имени из URL (DNS, SNI, проверка сертификата, заголовок Host); не принимаете -k за ремонт и выбираете решение, зная цену и откат.
Лаборатория и подготовка — архив один для всех пяти глав. Перед новым сценарием остановите прежний через Ctrl+C.
Что нового
Раньше запрос шёл так: имя → адрес → соединение → HTTP. У HTTPS между соединением и HTTP появляется рукопожатие TLS, и клиент проверяет сервер, прежде чем отправить хоть байт HTTP:
имя ──▶ адрес:порт ──▶ TLS: SNI, сертификат, проверки ──▶ HTTP: Host ──▶ приложение
(DNS) (TCP) (участок 3) (участок 4)
Какие роли играет имя из URL. Одно и то же имя notes.lab.test используется четыре раза:
- DNS превращает его в адрес.
curl --resolve ИМЯ:ПОРТ:АДРЕСподменяет только это. - SNI — поле в начале рукопожатия: клиент сообщает серверу, какое имя ему нужно, чтобы сервер, обслуживающий много имён, выбрал нужный сертификат. По документации curl, имя для SNI берётся только из URL.
- Проверка сертификата. Клиент проверяет, что сертификат подписан доверенной CA и что он выпущен на имя из URL
(страница curl о сертификатах: «crafted for the server name provided in the URL»). Имя ищется в списке имён сертификата
(SAN,
subjectAltName), а не в «владельце» (subject). - Заголовок
Host— часть HTTP-запроса, уже внутри защищённого канала.curl -H 'Host: …'меняет только его.
Поэтому для HTTPS «-H Host не помогает»: документация curl прямо говорит, что для HTTPS заголовка Host недостаточно; в
лаборатории это показано запуском.
Сроки. У сертификата есть период действия (notBefore … notAfter). Клиент сравнивает его с текущим временем своих часов.
Страница curl о сертификатах про даты не говорит; что просроченный сертификат отвергается, в этой главе — наблюдение
запуском (curl 8.5.0, OpenSSL 3.0.13), и формулировка ошибки зависит от версии.
Про -k / --insecure. Флаг отключает проверку сертификата целиком: и цепочки, и имени, и срока. Для диагностики («жив ли
сервер за сертификатом?») его можно использовать один раз и сказать об этом вслух; приёмкой ремонта он быть не может:
запрос проходит и до ремонта, и после.
Код выхода curl 60 — «сертификат сервера не прошёл проверку»; причина — в сообщении. Формулировки зависят от версии
curl и библиотеки TLS; в главе — из curl 8.5.0.
Инструменты (порядок выбираете сами):
python3 lab/netlab.py fetch --cacert CA URL # настоящий curl; учебная CA доверена только этому вызову (в системе не прописывается)
python3 lab/netlab.py fetch --verbose … # что curl успел узнать о сертификате до отказа
python3 lab/netlab.py certinfo URL # что отдаёт сервер: субъект, имена (SAN), издатель, срок, время проверки; вердикта нет
curl --noproxy '*' --resolve ИМЯ:ПОРТ:АДРЕС --cacert CA [-H 'Host: …'] URL # то же без fetch, если нужен свой набор ключей curl
openssl s_client -connect АДРЕС:ПОРТ -servername ИМЯ -CAfile CA # рукопожатие и код проверки цепочки
openssl s_client … -verify_hostname ИМЯ # то же, но имя проверяется (по умолчанию — нет!)
openssl verify -CAfile CA [-verify_hostname ИМЯ] [-attime СЕКУНДЫ] ФАЙЛ # проверка файла сертификата; -attime — «как если бы» время было другим
python3 lab/netlab.py config / logs --source tls / logs --source app # владелец службы: конфигурация и журналы
Путь к учебной CA лаборатория печатает при запуске и в config. Системные часы, файл hosts, хранилище доверия и браузер не
меняются нигде: «если бы время было другим» делает -attime, а не перевод часов.
Подготовка
Нужна команда openssl (проверьте openssl version). Из папки курса, в первом окне: python3 lab/netlab.py start ch5. Лаборатория
создаёт ключи и сертификаты во временной папке и удаляет её при остановке (Ctrl+C); порт HTTPS — 18443, только 127.0.0.1.
Если порт занят или openssl нет — лаборатория откажет и ничего не изменит. Остальное — во втором окне.
Ситуация
Суббота, 17 октября, 15:00. «Заметки» переехали с имени notes-old.lab.test на notes.lab.test и теперь работают по HTTPS:
https://notes.lab.test:18443/api/notes. Пользователи и скрипты получают ошибку сертификата. Дежурный Пётр:
Порт открыт, сервер отвечает, сертификат свежий — я смотрел дату, срок ещё не вышел. Значит, клиенты придираются. Добавим всем
-k, а тем, кто не может, — заголовокHost: notes-old.lab.test: заработает.
Воспроизведите симптом (путь к CA — в первом окне):
python3 lab/netlab.py fetch --cacert /tmp/…/ca.pem https://notes.lab.test:18443/api/notes
Мини-задача
Задача: выяснить, какая проверка HTTPS не пройдена и почему, и вернуть работоспособный проверенный путь пользователя, не потеряв заметки. Готовой цепочки команд нет: инструменты названы выше, порядок выбираете сами.
До любого наблюдения, кроме повтора симптома, запишите:
- Не меньше двух гипотез, какая проверка не пройдена (одна — слова Петра); для каждой — что подтвердило бы её и что бы исключило.
- Предсказание: что вы увидите в выводе, если верна ваша главная гипотеза.
- Одно наблюдение, которое различает гипотезы. Выполните его, запишите вывод и то, какие гипотезы оно исключило.
Затем: причина одним предложением и две строки вывода (сообщение клиента и то, что отдаёт сервер).
Разбор мини-задачи
Откройте, когда записали свои ответы
Гипотезы.
| Гипотеза | Если верна | Что исключает |
|---|---|---|
| Адрес или порт недоступны | код 7 или тайм-аут, нет рукопожатия | curl дошёл до проверки сертификата: код 60, в --verbose — SSL connection using TLSv1.3 |
| Клиент не доверяет CA | цепочка не проверяется: «unable to get local issuer certificate» | openssl s_client -CAfile CA без -verify_hostname: Verify return code: 0 (ok) — цепочка в порядке |
| Срок истёк | notAfter в прошлом; «certificate has expired» | в certinfo notAfter позже времени проверки; сообщение клиента про имя, не про срок |
| Сертификат выпущен не на это имя | в SAN нет notes.lab.test; клиент называет имя | в SAN — notes.lab.test (тогда причина другая) |
Различающее наблюдение — сравнить имя из URL с именами в сертификате, который отдаёт сервер. certinfo (или
openssl s_client … | openssl x509 -noout -ext subjectAltName) показывает DNS:notes-old.lab.test; в URL — notes.lab.test.
Срок в порядке (notAfter позже времени проверки — это видно в самом выводе certinfo); цепочка в порядке (строка Verify return code: 0 (ok) в выводе s_client), хотя s_client имя не
проверяет — с -verify_hostname notes.lab.test строка будет Verify return code: 62 (hostname mismatch). Это строка о результате проверки, а не код
завершения команды: без -verify_return_error s_client продолжает обмен и после ошибки проверки и завершается с кодом 0 (проверено запуском; с
-verify_return_error обрыв рукопожатия и код 1).
Причина. Сервер отдаёт сертификат, выпущенный на notes-old.lab.test; клиент требует сертификат на notes.lab.test и
отказывается (код 60). Адрес, порт и цепочка в порядке: приложение живо — по имени notes-old.lab.test тот же сервер отвечает
200 (fetch --cacert … https://notes-old.lab.test:18443/api/notes). Журнал приложения при отказе пуст: до HTTP не дошло, Host
никто не прочитал.
Слова Петра. «Сертификат свежий» — верно и неважно: срок — одна проверка, имя — другая. -H 'Host: notes-old.lab.test' не
помогает: проверяется имя из URL, а не заголовок (код 60 остаётся). -k даёт 200 — потому что отключена проверка, из-за которой
всё и обнаружилось.
Развилка: 15:30, что делать
Сертификат на сервере ведёте вы; клиенты — разные команды. Решения (порядок случайный):
- А. Добавить
-kво все скрипты и приложения. - Б. Передать клиентам заголовок
Host: notes-old.lab.test(-H). - В. Выпустить и поставить сертификат на имя
notes.lab.test:python3 lab/netlab.py set cert_name=notes.lab.test. - Г. Временно перевести клиентов на старый адрес
https://notes-old.lab.test:18443/…— сертификат ему подходит. Это восстановление доступности, но не завершение переезда: у переезда другой признак готовности (см. «Приёмка»). - Д. Добавить учебную CA в хранилище доверия компьютера.
- Е. Перезапустить сервер (остановить
startи запустить заново). - Ж. Передать разработчикам: «HTTPS не работает».
Перед ремонтом можно один раз посмотреть список заметок с -k — только как диагностику, сказав об этом вслух — и запомнить
число заметок: после ремонта оно не должно измениться.
Для выбранного решения: что увидит пользователь, что сломается у тех, кто ходил по прежнему пути, как вернуть назад.
Последствия
- А. Вредное: проверка выключена совсем (цепочка, имя, срок); подмена сервера станет незаметной. «Работает» и до, и после — ничего не доказывает. Откат — убрать флаг в каждом клиенте, о которых мы, скорее всего, забудем.
- Б. Бесполезное: для HTTPS проверяется имя из URL; код 60 остаётся (проверено запуском).
- В. Допустимо, устраняет причину: 200 с включённой проверкой, заметки на месте. Цена: сертификат на одно имя —
notes-old.lab.testтеперь не проходит проверку, кто ещё ходит по старому имени, получит код 60. Откат:set cert_name=notes-old.lab.test. Можно рассмотреть сертификат с обоими именами; в лаборатории он не смоделирован. - Г. Допустимая временная мера, восстанавливающая доступность: по
notes-old.lab.testзапрос проходит с включённой проверкой (200, три заметки). Но переезд не завершён: по новому имениnotes.lab.testпо-прежнему код 60, и приёмка по новому адресу (ниже) не выполнена. Цена: правка всех клиентов, потом их возврат; старое имя по сюжету уходит. Условие отката — сертификат на новое имя поставлен (В) и новый адрес проверен. - Д. Вредное и бесполезное: цепочка не была причиной (
Verify return code: 0), а доверие к своей CA на всём компьютере — решение для всех сайтов этой CA; в лаборатории намеренно не меняется. - Е. Бесполезное: сертификат тот же; заметки в лабе возвращаются к трём.
- Ж. Бесполезное: причина названа и исправима вами.
Приёмка
- Гипотезы и предсказание записаны до наблюдений; названо различающее наблюдение и что оно исключило.
- Причина названа с двумя строками вывода: имя из URL и имя в сертификате.
- Роль имени из URL названа верно: что меняет
--resolve, что-H Host, что не меняет ни то ни другое (SNI и проверку). - Выбранное решение называет цену и откат.
- Завершение переезда (окончательная приёмка). Новый адрес пользователя с включённой проверкой —
fetch --cacert … https://notes.lab.test:18443/api/notes— 200,X-App-Version: 3.0, JSON, три заметки на месте; запись через этот канал — 201 (тогда четыре). - Если выбрана только временная мера Г: запишите отдельно, что доступность восстановлена (то же по
notes-old.lab.test: 200 с проверкой, три заметки), а переезд не завершён — окончательная приёмка по новому адресу остаётся открытым пунктом с условием его закрытия. Старый адрес не заменяет новый. - Не принято за ремонт:
-k,-H Host, обращение по адресу вместо имени, доверие CA для всего компьютера.
Подсказки
Подсказка 1 — направление
У HTTPS клиент проверяет сервер ещё до отправки запроса. Какие проверки он делает — и о какой из них говорит сообщение
curl? Что проверил Пётр, а что — нет?
Подсказка 2 — нужное наблюдение
Посмотрите, какой сертификат отдаёт сервер: на какие имена он выпущен и на какой срок (certinfo или
openssl s_client + openssl x509). Сравните это с именем в URL и со временем проверки. Если сомневаетесь,
проверяет ли s_client имя, — запустите его с -verify_hostname и без.
Подсказка 3 — разбор
Адрес и порт в порядке, цепочка в порядке (Verify return code: 0), срок в порядке (notAfter позже времени проверки), но сертификат выпущен на
notes-old.lab.test, а клиент ходит на notes.lab.test — отсюда код 60 и «no alternative certificate subject name matches».
Допустимы В (сертификат на нужное имя; цена — старое имя) и Г как временная мера: Г восстанавливает доступность, но переезд завершает только новый адрес с проверкой. А отключает проверку, Б не меняет проверяемого имени,
Д чинит то, что не сломано, Е не меняет сертификат.
Второй вариант
Воскресенье, 18 октября, 09:00. Сертификат на нужное имя notes.lab.test поставлен. Но у пользователей снова код 60, и сообщение
другое. Пётр: «Вчера это лечилось выпуском сертификата на notes.lab.test — сделайте то же самое». Перезапустите лабораторию со
сценарием ch5b (python3 lab/netlab.py start ch5b) и действуйте так же, как в основной части.
Условия приёмки те же, плюс три:
- Объясните, почему ремонт из основной части здесь не помогает — и покажите запуском (что изменится, если «сделать то же самое»).
- Сравните срок действия сертификата со временем проверки, не меняя часов компьютера: назовите оба момента и покажите, как
узнать, что было бы при другом времени (
-attime). - Объясните, почему
-kздесь «работает» и почему приёмкой быть не может.
Решения (порядок случайный; ключи — для действий после диагностики):
- А. Повторить прежний ремонт:
set cert_name=notes.lab.test. - Б. Перевести часы компьютера назад, «чтобы сертификат был действителен».
- В. Заменить сертификат на действующий:
set cert_valid=fresh. - Г. Добавить
-kв клиенты на время. - Д. Перенести клиентов на
https://notes-old.lab.test:18443/…. - Е. Срочно поставить действующий сертификат (В) и завести напоминание о сроке следующего.
Подсказка 1 — направление
Имя в сертификате вчера уже исправили. Что ещё есть у сертификата, кроме имён? Посмотрите на сообщение клиента — о чём оно?
Подсказка 2 — нужное наблюдение
Запишите период действия сертификата (notBefore … notAfter) и время проверки по часам компьютера и сравните.
Проверьте себя, не трогая часы: openssl verify -attime с моментом внутри периода и после него.
Подсказка 3 — разбор
Имя верное (SAN — notes.lab.test), цепочка верна, но notAfter в прошлом: срок истёк, клиент отказывает (код 60,
«certificate has expired»). Повтор А ничего не меняет: сертификат уже на нужное имя. Допустимо В (действующий сертификат; цена —
замена и перезагрузка, откат — вернуть прежний, что бессмысленно) и Е (то же плюс предотвращение повторения). Б — вредное:
часы одни на всю систему, и истёк срок не только у этого сертификата; Г отключает проверку и скрывает причину; Д не поможет, пока
сертификат просрочен для любого имени.
Источники
- curl, «SSL certificates»: проверка подписи доверенной CA и имени сервера из URL. Про проверку сроков действия страница не говорит — в главе это наблюдение запуском (curl 8.5.0, OpenSSL 3.0.13).
- Everything curl, «Name resolving»:
Host:недостаточно для HTTPS-сервера; имя для SNI берётся только из URL;--resolveподменяет адрес для пары «имя–порт», проверяемое имя остаётся именем из URL. - OpenSSL 3.0, «Verification options»: проверка периода действия
сертификата и
-attime(«как если бы» время было другим) — документированы. Там же и в справкеopenssl-verify—-verify_hostname. - OpenSSL 3.0,
openssl-s_client: без-verify_return_errorдиагностическийs_clientпродолжает обмен после ошибки проверки; строкаVerify return code— результат проверки, не код завершения команды. - Коды в строке
Verify return code— 10 (срок истёк), 62 (имя не совпало), 21 (издатель не найден, цепочка не проверена) — и формулировки ошибокcurlв главе — наблюдения запуском (curl 8.5.0, OpenSSL 3.0.13), не обещания документации; в других версиях текст может отличаться.
Прочитали и разобрались? Отметьте урок — прогресс сохранится в этом браузере.