Сертификаты для localhost

Последнее обновление: | Вся документация

Иногда разработчикам нужен сертификат для доменного имени “localhost” - для локальной разработки, или для распространения внутри нативных приложений для взаимодействия с web-приложением. Let’s Encrypt не предоставляет сертификатов для “localhost”, т.к. во-первых, у этого доменного имени нет определённого владельца, и во-вторых, нет домена первого уровня - например, “.com” или “.net”. Теоретически, возможно настроить доменное имя так, чтобы оно резолвилось на адрес 127.0.0.1, и выпустить для него сертификат после прохождения проверки DNS. Тем не менее, есть более удачные решения.

Для локальной разработки

При разработке web-приложения обычно запускают локальный web-сервер (Apache, Nginx), настроенный на http://localhost:8000/. Однако, браузеры по-разному обрабатывают HTTP- и HTTPS-запросы. На HTTPS-странице попытка загрузить Javascript по HTTP-протоколу будет заблокирована. Поэтому, при локальной разработке, используя HTTP, скрипты будут загружаться нормально, но после выкладки на боевые HTTPS-сервера возникнут проблемы. Чтобы избежать такой ситуации, нужно настроить доступ по HTTPS на локальном web-сервере. Но как избавиться от постоянных сообщений об ошибке сертификата, как увидеть “зелёный замок” в адресной строке?

Лучшим решением будет создание собственного сертификата - самоподписанного, или подписанного локальным корневым сертификатом - и добавление в доверенное хранилище операционной системы. Подробности см.ниже.

Для нативных приложений, взаимодействующих с web-приложениями

Время от времени, разработчики вынуждены выпускать загружаемые нативные приложения, для расширения функциональности и совместного использования с web-приложениями. Например, десктоп-приложения Dropbox и Spotify умеют сканировать файлы на дисках компьютера, что невозможно для web-приложений. Общий подход в реализации таких нативных приложений состоит в запуске локального web-сервера, и обмену данными с web-приложением через XMLHTTPRequest (XHR) или WebSockets. Web-приложения, как правило, используют HTTPS, поэтому XHR- или WebSockets-запросы по небезопасному протоколу HTTP будут отклонены. Это называется “блокировка смешаного контента” (Mixed Content Blocking). Для взаимодействия с web-приложением, нативное приложение должно быть безопасным.

С одной стороны, современные браузеры считают http://127.0.0.1:8000/ “потенциально заслуживающим доверие” URL-ом, потому что он локальный. Отправленный на 127.0.0.1 трафик гарантированно не уйдёт за пределы компьютера, соответственно, считается безопасным для перехвата по сети. Это означает, что если web-приложение использует HTTPS, а нативное приложение запущено на 127.0.0.1, то обе программы могут успешно взаимодействовать по XHR. С другой стороны, для localhost это ещё не работает. А WebSocket-ы игнорируют и 127.0.0.1, и localhost.

Возможно, вы захотите обойти эти ограничения, настроив резолв произвольного доменного имени в глобальном DNS на адрес 127.0.0.1 (например, localhost.example.com), выпустив сертификат для этого домена, распространяя сертификат и соответствующий ему закрытый ключ внутри нативного приложения, и настроив взаимодействие по https://localhost.example.com:8000/ вместо http://127.0.0.1:8000/. Не делайте этого! Это подвергнет пользователей риску, и сертификат может быть отозван.

Используя доменное имя вместо IP-адреса, вы позволяете злоумышленникам запустить атаку Man in the Middle (MitM) в процессе поиска IP-адреса по доменному имени (DNS Lookup), и внедрить ответ, который укажет на другой IP-адрес. Атакующий может притвориться нативным приложением, подделывая запросы к web-приложению, что скомпрометирует аккаунт в web-приложении.

Успех атаки MitM возможен потому, что вы вынуждены распространять закрытый ключ для сертификата вместе с нативным приложением. Соответственно, любой, кто скачает это приложение, получит копию ключа. Этим вы скомпрометируете закрытый ключ, и Удостоверяющий Центр (УЦ) отзовёт сертификат, как только узнает об этом. У множества нативных приложений были отозваны сертификаты по причине распространения закрытых ключей.

К сожалению, это сужает список безопасных способов взаимодействия нативных и web-приложений. И ситуация может ещё больше усложниться в недалёком будущем, если браузеры продолжат затруднять доступ к localhost.

Так же нужно отметить, что web-сервисы с доступом к нативному API изначально небезопасны, потому что сайты, которые вы не намеревались авторизовать, могут получить доступ к этому API. Если решите углубиться в изучение проблемы, обратите внимание на Cross-Origin Resource Sharing, использование заголовка ответа Access-Control-Allow-Origin, и надёжного HTTP-парсера. Потому как даже серверы, не прошедшие подтверждение, могут посылать предварительные запросы, эксплуатирующие уязвимости в HTTP-парсере.

Создание и поверка собственных сертификатов

Любой может выпустить собственный сертификат без обращения к УЦ. Единственное различие будет в том, что выпущенные вами сертификаты не будут приниматься кем-либо ещё. Для локальной разработки этого достаточно.

Простейший способ сгенерировать закрытый ключ и самоподписанный сертификат для localhost - выполнить следующую команду из пакета openssl:

openssl req -x509 -out localhost.crt -keyout localhost.key \
  -newkey rsa:2048 -nodes -sha256 \
  -subj '/CN=localhost' -extensions EXT -config <( \
   printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")

Вы можете сконфигурировать локальный web-сервер, используя файлы localhost.crt и localhost.key, добавив localhost.crt в список доверенных корневых сертификатов.

Если вам требуется чуть больше реализма в сертификатах для локальной разработки, попробуйте minica для создания собственного корневого сертификата, и выпуска конечных сертификатов, подписанных корневым. В итоге вы будете импортировать корневой сертификат вместо самоподписанных конечных сертификатов.

Также, вы можете использовать доменное имя с точками внутри (например, www.localhost), добавив в файл /etc/hosts как алиас адреса 127.0.0.1. Этот подход чуть изменит способ обработки браузерами хранилища для cookie.