Настраиваем HTTPS-сервер на nginx
Для чего я это пишу?
В последнее время в связи с кучей факторов (АНБ, DPI с рекламой и другое) у меня начала просыпаться паранойя и я подумал полностью перевести свой небольшой сайт на https. На хабре было несколько статей с техническими подробностями работы SSL/TLS, однако поискав информацию на тему настройки https-вебсервера обнаружил традиционное деление статей — либо это статьи «Делайте вот так», где просто даны настройки без каких-либо разъяснений и вариантов использования, либо это большие теоретические статьи, где обсуждаются различные схемы использования, но без практически применимых готовых вариантов. На хабре была статья о настройке, однако в ней нет информации про DH-кодировки, да и некоторые параметры не описаны. Подумал, что стоит упорядочить найденное в виде статьи, которая будет полезна тем, кто хотел бы развернуть https у себя на сервере, но не слишком углубляться в дебри SSL.
Повествование будет вестись с учетом того, что веб-сервером выступает nginx (и в одном месте будет параметр для php-fpm).
Сертификат
У меня уже был сертификат от StartSSL. О нем уже писали на хабре, так что на этом шаге задерживаться не буду. Скажу только, что в течении первых двух-трех дней браузеры, проверяющие сертификат на сервере, могут на него ругаться (у меня такое происходило с Opera 12 и Firefox), видимо у StartCom кеши валидных сертификатов обновляются не так часто. Про установку же будет сказано ниже
О вариантах настройки
Nginx из коробки в новых версиях предлагает практически актуальные, но все же требующие шлифовки параметры, однако актуальные параметры появились в стандартном конфиге не так давно, поэтому в некоторых случаях стандартный пример HTTPS-сервера в конфиге будет не актуален.
В общем случае есть два актуальных на данный момент варианта настройки — с Forward Secrecy и без него. При настройке различие только в наборе кодировок (директива ssl_ciphers), однако тут стоит задуматься, что же вы хотите от https.
О Forward Secrecy можно почитать скажем тут. В двух словах, суть заключается в том, что для актуального на данный момент алгоритма RC4 ключи сессии генерируются на основе приватного ключа сервера. Таким образом, если приватный ключ будет скомпрометирован, появится возможность расшифровать все сессии (если они были записаны). В случае же использования DH-кодировок, каждая сессия имеет свой набор ключей, которые сессии никак не зависят от приватного ключа. Однако в этом случае тратится гораздо больше процессорного времени на хендшейк, что увеличивает нагрузку и время открытия страницы.
Тут стоит задуматься, для чего нужен https конкретно у вас на сайте. При большом количестве посетителей использование DH-алгоритмов шифрования может прилично увеличить нагрузку (которая в любом случае повысится при переходе на HTTPS), в некоторых случаях придется увеличить тариф на VDS и т.п. В большинстве случаев RC4 достаточно, однако многим хочется чтобы все было «по высшему классу», так почему бы не сделать, если ресурсы позволяют?
Настройка nginx
В результате настройки у меня сформировался приблизительно такой конфиг, суть параметров поясню ниже.
В секции http необходимо добавить:
Секция server же получится приблизительно такая:
В данном примере не используются DH-алгоритмы, т.е. нет Forward Secrecy. Из улучшений тут можно опустить поддержку SSLv3 (убрав его из ssl_ciphers), таким образом перестанет поддерживаться IE 6 и ниже, поскольку он не поддерживает TLS, а так же увеличить время STS, но об этом ниже.
Без SSLv3 такая настройка дает оценку 100-95-100-90 в тесте SSL.
Пройдемся по параметрам
4000 сессий, таким образом при этих настройках можно хранить до 40 тысяч сессий), 5m — таймаут сессии в кеше (5 минут).
ssl_prefer_server_ciphers on;
«Указывает, чтобы при использовании протоколов SSLv3 и TLS серверные шифры были более приоритетны, чем клиентские.» (nginx.org) — клиентские шифры (CBC) уязвимы к некоторым типам атак.
ssl_stapling on;
Позволяет серверу прикреплять OCSP-ответы, тем самым уменьшая время загрузки страниц у пользователей. ЗДесь имеются ввиду ответы о валидности сертификата (при проверке на отозванность). С точки зрения безопасности пользователя не важно, кто передает ответы — веб-сервер или сервер CA — ведь ответ в любом случае подписан и валидность ответа тоже можно проверить, а ответ включает в себя свой срок действия.
Для работы этой функции нужно указать DNS-сервер, что и делается директивой resolver.
keepalive_timeout — думаю в описании не нуждается, не стоит выключать или ставить слишком малым для уменьшения нагрузки из-за повторного установления соединения.
ssl_certificate и ssl_certificate_key указывают на файл сертфиката и файл приватного ключа для него. Так как я рассказываю на примере сертификата от StartSSL, то здесь допущу небольшой комментарий относительно инструкций StartSSL по установке сертификата — не нужно добавлять сертификат Root CA в обобщенный файл сертификата, поскольку это не имеет смысла и только увеличивает, хоть и не на много, размер передаваемых данных. Достаточно иметь в файле последовательно личный сертификат и сертификат промежуточного центра сертификации. Готовый файл сертификата для nginx (для сетификата StartSSL) можно получить следующей командой:
Где ваш сертификат — certificate.crt, а промежуточный сертификат — www.startssl.com/certs/sub.class1.server.ca.pem
add_header Strict-Transport-Security ‘max-age=604800’;
Strict-Transport-Secutiry — заголовок, указывающий браузеру на то, что сайт доступен только по https. Это предотвращает возможность перехода обратно на http-версию для последующей атаки через незашифрованное соединение. Кстати данный параметр еще удобен тем, что при наличии в коде страницы «забытого» подключения ресурса (картинки/скрипта/стиля/. ) с того же сайта по http, браузер сам пойдет на https-версию и не будет ругаться на частично незашифрованное соединение. Конечно же это не сработает для внешних ресурсов. Время — неделя. Многие рекомендуют ставить 1 год, однако в случае решения в будущем отказаться от использования https это может доставить проблемы некоторым пользователям. Время обновляется при каждой передаче этого заголовка, т.е. при каждом заходе на сайт.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
Указывает поддерживаемые протоколы. SSLv2 и v3 имеют критические уязвимости.
Forward Secrecy
Для включения Forward Secrecy можно использовать например такой набор шифров:
Кроме того, необходимо настроить приоритет шифров OpenSSL:
В данном варианте не запрещается использование RC4 для сохранения совместимости с некоторыми браузерами, однако не так давно обнаружились уязвимости и в нем, хоть и практически трудно реализуемые.
Для усиления шифрования можно увеличить стойкость DH-шифров, создав файл параметров DH-шифров (создание файла займет некоторое время!), скажем длинной 4096 бит:
И добавив в конфиг nginx директиву
Это можно делать для скажем для веб-интерфейсов управления сервером/службами, однако хендшейк будет происходить еще дольше, поэтому не стоит делать это на обычном сайте.
Про CDN-сервисы
В обсуждении инструкций по настройке Forward Secrecy было замечено, что по крайней мере CDN Amazon CloudFront не поддерживает обмен с вашим сервером в DH-кодировках, да и RC4 вроде тоже, что не радует. Возможно что и с другими CDN тоже не все идеально, но я лично пока с ними не сталкивался, поэтому ничего сказать не могу.
Модуль ngx_http_ssl_module
Модуль ngx_http_ssl_module обеспечивает работу по протоколу HTTPS.
Для сборки и работы этого модуля нужна библиотека OpenSSL.
Пример конфигурации
Для уменьшения загрузки процессора рекомендуется
Директивы
Эта директива устарела в версии 1.15.0. Вместо неё следует использовать параметр ssl директивы listen.
Эта директива появилась в версии 1.5.9.
Задаёт размер буфера, используемого при отправке данных.
По умолчанию размер буфера равен 16k, что соответствует минимальным накладным расходам при передаче больших ответов. С целью минимизации времени получения начала ответа (Time To First Byte) может быть полезно использовать меньшие значения, например:
Указывает файл с сертификатом в формате PEM для данного виртуального сервера. Если вместе с основным сертификатом нужно указать промежуточные, то они должны находиться в этом же файле в следующем порядке: сначала основной сертификат, а затем промежуточные. В этом же файле может находиться секретный ключ в формате PEM.
Начиная с версии 1.11.0 эта директива может быть указана несколько раз для загрузки сертификатов разных типов, например RSA и ECDSA:
Возможность задавать отдельные цепочки сертификатов для разных сертификатов есть только в OpenSSL 1.0.2 и выше. Для более старых версий следует указывать только одну цепочку сертификатов.
Начиная с версии 1.15.9 в имени файла можно использовать переменные при использовании OpenSSL 1.0.2 и выше:
Однако нужно учитывать, что при использовании переменных сертификат загружается при каждой операции SSL handshake, что может отрицательно влиять на производительность.
Вместо файла можно указать значение data : $переменная (1.15.10), при котором сертификат загружается из переменной без использования промежуточных файлов. При этом следует учитывать, что ненадлежащее использование подобного синтаксиса может быть небезопасно, например данные секретного ключа могут попасть в лог ошибок.
Нужно иметь в виду, что из-за ограничения протокола HTTPS для максимальной совместимости виртуальные серверы должны слушать на разных IP-адресах.
Указывает файл с секретным ключом в формате PEM для данного виртуального сервера.
Вместо файла можно указать значение data : $переменная (1.15.10), при котором секретный ключ загружается из переменной без использования промежуточных файлов. При этом следует учитывать, что ненадлежащее использование подобного синтаксиса может быть небезопасно, например данные секретного ключа могут попасть в лог ошибок.
Начиная с версии 1.15.9 в имени файла можно использовать переменные при использовании OpenSSL 1.0.2 и выше.
Описывает разрешённые шифры. Шифры задаются в формате, поддерживаемом библиотекой OpenSSL, например:
Полный список можно посмотреть с помощью команды “ openssl ciphers ”.
В предыдущих версиях nginx по умолчанию использовались другие шифры.
Указывает файл с доверенными сертификатами CA в формате PEM, которые используются для проверки клиентских сертификатов и ответов OCSP, если включён ssl_stapling.
Список сертификатов будет отправляться клиентам. Если это нежелательно, можно воспользоваться директивой ssl_trusted_certificate.
Эта директива появилась в версии 1.19.4.
Задаёт произвольные конфигурационные команды OpenSSL.
Директива поддерживается при использовании OpenSSL 1.0.2 и выше.
На одном уровне может быть указано несколько директив ssl_conf_command :
Следует учитывать, что изменение настроек OpenSSL напрямую может привести к неожиданному поведению.
Эта директива появилась в версии 0.8.7.
Указывает файл с отозванными сертификатами (CRL) в формате PEM, используемыми для проверки клиентских сертификатов.
Эта директива появилась в версии 0.7.2.
Указывает файл с параметрами для DHE-шифров.
По умолчанию параметры не заданы, и соответственно DHE-шифры не будут использоваться.
До версии 1.11.0 по умолчанию использовались встроенные параметры.
Эта директива появилась в версии 1.15.3.
Разрешает или запрещает TLS 1.3 early data.
Директива поддерживается при использовании OpenSSL 1.1.1 и выше (1.15.4) или BoringSSL.
Эта директива появилась в версиях 1.1.0 и 1.0.6.
Задаёт кривую для ECDHE-шифров.
При использовании OpenSSL 1.0.2 и выше можно указывать несколько кривых (1.11.0), например:
Специальное значение auto (1.11.0) соответствует встроенному в библиотеку OpenSSL списку кривых для OpenSSL 1.0.2 и выше, или prime256v1 для более старых версий.
При использовании OpenSSL 1.0.2 и выше директива задаёт список кривых, поддерживаемых сервером. Поэтому для работы ECDSA-сертификатов важно, чтобы список включал кривые, используемые в сертификатах.
Эта директива появилась в версии 1.19.0.
Включает проверку OCSP для цепочки клиентских сертификатов. Параметр leaf включает проверку только клиентского сертификата.
Для преобразования имени хоста OCSP responder’а в адрес необходимо дополнительно задать директиву resolver.
Эта директива появилась в версии 1.19.0.
Задаёт имя и размер кэша, который хранит статус клиентских сертификатов для проверки OCSP-ответов. Кэш разделяется между всеми рабочими процессами. Кэш с одинаковым названием может использоваться в нескольких виртуальных серверах.
Параметр off запрещает использование кэша.
Эта директива появилась в версии 1.19.0.
Переопределяет URL OCSP responder’а, указанный в расширении сертификата “Authority Information Access” для проверки клиентских сертификатов.
Поддерживаются только “ http:// ” OCSP responder’ы:
Эта директива появилась в версии 1.7.3.
Задаёт файл с паролями от секретных ключей, где каждый пароль указан на отдельной строке. Пароли применяются по очереди в момент загрузки ключа.
Указывает, чтобы при использовании протоколов SSLv3 и TLS серверные шифры были более приоритетны, чем клиентские.
Разрешает указанные протоколы.
Параметры TLSv1.1 и TLSv1.2 (1.1.13, 1.0.12) работают только при использовании OpenSSL 1.0.1 и выше.
Параметр TLSv1.3 (1.13.0) работает только при использовании OpenSSL 1.1.1 и выше.
Эта директива появилась в версии 1.19.4.
Если разрешено, то операции SSL handshake в блоке server будут отклонены.
Например в этой конфигурации отклоняются все операции SSL handshake с именем сервера, отличным от example.com :
Задаёт тип и размеры кэшей для хранения параметров сессий. Тип кэша может быть следующим:
off жёсткое запрещение использования кэша сессий: nginx явно сообщает клиенту, что сессии не могут использоваться повторно. none мягкое запрещение использования кэша сессий: nginx сообщает клиенту, что сессии могут использоваться повторно, но на самом деле не хранит параметры сессии в кэше. builtin встроенный в OpenSSL кэш, используется в рамках только одного рабочего процесса. Размер кэша задаётся в сессиях. Если размер не задан, то он равен 20480 сессиям. Использование встроенного кэша может вести к фрагментации памяти. shared кэш, разделяемый между всеми рабочими процессами. Размер кэша задаётся в байтах, в 1 мегабайт может поместиться около 4000 сессий. У каждого разделяемого кэша должно быть произвольное название. Кэш с одинаковым названием может использоваться в нескольких виртуальных серверах.
Можно использовать одновременно оба типа кэша, например:
однако использование только разделяемого кэша без встроенного должно быть более эффективным.
Эта директива появилась в версии 1.5.7.
Задаёт файл с секретным ключом, применяемым при шифровании и расшифровании TLS session tickets. Директива необходима, если один и тот же ключ нужно использовать на нескольких серверах. По умолчанию используется случайно сгенерированный ключ.
Если указано несколько ключей, то только первый ключ используется для шифрования TLS session tickets. Это позволяет настроить ротацию ключей, например:
Файл должен содержать 80 или 48 байт случайных данных и может быть создан следующей командой:
В зависимости от размера файла для шифрования будет использоваться либо AES256 (для 80-байтных ключей, 1.11.8), либо AES128 (для 48-байтных ключей).
Эта директива появилась в версии 1.5.9.
Разрешает или запрещает возобновление сессий при помощи TLS session tickets.
Задаёт время, в течение которого клиент может повторно использовать параметры сессии.
Эта директива появилась в версии 1.3.7.
Разрешает или запрещает прикрепление OCSP-ответов сервером. Пример:
Для работы OCSP stapling’а должен быть известен сертификат издателя сертификата сервера. Если в заданном директивой ssl_certificate файле не содержится промежуточных сертификатов, то сертификат издателя сертификата сервера следует поместить в файл, заданный директивой ssl_trusted_certificate.
Для преобразования имени хоста OCSP responder’а в адрес необходимо дополнительно задать директиву resolver.
Эта директива появилась в версии 1.3.7.
Ответ должен быть в формате DER и может быть сгенерирован командой “ openssl ocsp ”.
Эта директива появилась в версии 1.3.7.
Переопределяет URL OCSP responder’а, указанный в расширении сертификата “Authority Information Access”.
Поддерживаются только “ http:// ” OCSP responder’ы:
Эта директива появилась в версии 1.3.7.
Разрешает или запрещает проверку сервером ответов OCSP.
Для работоспособности проверки сертификат издателя сертификата сервера, корневой сертификат и все промежуточные сертификаты должны быть указаны как доверенные с помощью директивы ssl_trusted_certificate.
Эта директива появилась в версии 1.3.7.
Задаёт файл с доверенными сертификатами CA в формате PEM, которые используются для проверки клиентских сертификатов и ответов OCSP, если включён ssl_stapling.
В отличие от ssl_client_certificate, список этих сертификатов не будет отправляться клиентам.
Параметр optional (0.8.7+) запрашивает клиентский сертификат, и если сертификат был предоставлен, проверяет его.
Устанавливает глубину проверки в цепочке клиентских сертификатов.
Обработка ошибок
Модуль ngx_http_ssl_module поддерживает несколько нестандартных кодов ошибок, которые можно использовать для перенаправления с помощью директивы error_page:
495 при проверке клиентского сертификата произошла ошибка; 496 клиент не предоставил требуемый сертификат; 497 обычный запрос был послан на порт HTTPS.
Встроенные переменные
Модуль ngx_http_ssl_module поддерживает встроенные переменные:
Переменная полностью поддерживается при использовании OpenSSL версии 1.0.2 и выше. При использовании более старых версий переменная доступна только для новых сессий и может содержать только известные шифры.
Переменная поддерживается при использовании OpenSSL версии 1.0.2 и выше. При использовании более старых версий значением переменной будет пустая строка.
Переменная доступна только для новых сессий.
OpenSSL, шифры и nginx без магии
Много кто писал о том, как подбирать шифры для nginx для получения высокой оценки по тесту от Qualys. При этом практически везде директивы ssl_ciphers и подобные даются как эдакие магические строки, которые нужно просто вставить, и надеяться на удачу. Объяснения почему именно такая строка и почему шифры именно в такой последовательности, как правило, не дается. Моя статья призвана исправить это недоразумение. Директива ssl_ciphers потеряет для вас всякую магию, а ECDHE и AES будут как друзья-братья.
Подготовка
Работать будем с Debian стабильного выпуска. Если у вас другой дистрибутив, то версия OpenSSL должна быть такая же или более новая.
Конфиг для сайта используем минимальный. Ничего лишнего и всё по умолчанию.
Слабые параметры DH
С настройками по умолчанию результат на четвёрку, если по-нашему. Ругаются на слабые параметры для обмена ключами по алгоритму Диффи — Хеллмана (далее просто DH). Слабые параметры (пара специально выбранных простых чисел) принципиально позволяют АНБ, ЦРУ и подобным организациям с неограниченным ресурсом прочитать ваш трафик. Это не то, что вы хотите.
Можно было бы сделать усиленные параметры, но такие параметры не помогают ускорить соединение, и некоторыми старыми клиентами поддерживаются лишь ограниченно. У протокола DH на эллиптических кривых (ECDHE) таких недостатков нет, значит мы ничего не теряем и даже приобретаем в скорости, если мы полностью откажемся от медленных EDH шифров в пользу ECDHE, рекомендуемых в том числе создателями теста.
Protocol Support
Для получения 100% оценки по графе Protocol Support следовало бы отключить протоколы ниже TLS 1.2, но мы этого делать не будем, так как есть целая группа клиентов, которые не поддерживают ничего старше TLS 1.0, а значит не смогут достучаться до сайта без TLS 1.0.
Их легко видно в разделе Handshake Simulation: это IE любых версий младше 11, Java до SE 8, Android до версии 4.3 и даже некоторые версии Safari. Про всевозможный софт, собранный с OpenSSL версии 0.9.8 и младше, даже и не говорю.
Key Exchange
Для получения 100% оценки по графе Key Exchange следовало бы получить ключи на 4096 бит.
Мы этого не будем делать потому такие ключи непосредственно влияют на скорость установки соединения, при этом не слишком лучше ключей на 2048 бит.
Cipher Strength
Для получения 100% оценки по графе Cipher Strength мы должны были избавиться от всех шифров слабей 256 бит, остановившись на следующих для сертификата с RSA ключем.
И выбрать другую, более надежную, кривую.
В используемой версии OpenSSL можно выбрать только одну.
Мы ни того, ни другого делать не будем. Шифры на 256 бит не помогают ускорить соединение, что особенно касается мобильных устройств с маломощными процессорами и прочих клиентов без AES-NI (аппаратного ускорения AES).
На Celeron 1007U шифр ChaCha20/Poly1305 будет в два раза быстрее AES-128, а последний на треть быстрее AES-256. На Core i7 с AES-NI картина другая: ChaCha20/Poly1305 будет медленнее AES-128 на треть, а AES-256 лишь немного медленнее AES-128. При шифровании картина примерно та же.
Обмен ключами с используемой по умолчанию кривой prime256v1 (она же secp256r1 или NIST P-256) будет существенно быстрее, чем с усиленной кривой secp384r1 (NIST P-384).
Работа с P-384 требует в 7-9 раз больше времени в зависимости от операции.
Если сравнить ECDSA и RSA, то видно что в случае ECDSA вычислительная нагрузка ложится больше на клиента, чем на сервер, что может быть интересным аргументом для перехода на ECDSA сертификаты.
Принцип поиска шифров
Будет лучше выбирать шифры по ключевым словам или тегам, каждое из которых соответствуют какой-то группе шифров. Ключевые слова в разных версиях OpenSSL отличаются, но мы всегда можем их проверить.
Например, тег EECDH соответствует всем шифрам с обменом одноразовыми (эфемерными) ключами по алгориму DH с эллиптическими кривыми.
Шифры показываются в списке согласно приоритету: клиент выберет первый подходщящий шифр, просматривая список в указанном порядке.
Если бы у нас была самая последняя версия OpenSSL, то для получения того же списка мы бы использовали понятный тег ECDHE, дающий тот же самый список и соответствующий префиксу шифров. В текущей версии такого тега нет, потому используем какой есть.
Теги можно сочетать, получая пересечения множеств шифров. Получим шифры, сочетающие ECDHE, AES-256 и GCM.
Группы шифров можно переместить ниже по приоритету, указав с плюсом в начале. Понизим приоритет у более требовательных шифров с AES-256, не удаляя их.
Можно временно убрать какую-то группу шифров, чтобы потом добавить её в другом виде. Исключим из всех ECDHE шифров шифры с 3DES, а затем вернём их обратно, но только в сочетании с обменом ключами RSA. Такие слабые шифры должны оказаться в самом конце списка.
Выбираем шифры
Теперь у нас есть всё, чтобы составить список шифров для nginx, которые не будет требовать дополнения при переходе на новые версии OpenSSL, теряя старые шифры и приобретая новые без какого-либо участия с нашей стороны. Мы учём необходимость поддержки старых браузеров и требования к скорости.
Начнём с получения самых сильных шифров из рекомендуемых, но не будем исключать шифры для сертификатов c EC на случай если мы когда-нибудь захотим использовать такой вид сертификатов.
На деле нам нужнен не столько конкретно AES, а сколько не нужны слабые шифры 3DES и RC4. Последний исключим полностью и навсегда.
Отдадим приоритет более быстрым шифрам, понизив его для AES-256. Совсем удалять AES-256 не будем на случай если кому-то очень будет нужен именно AES-256.
Такая строка шифров не исключит ChaCha20/Poly1305 в следующей версии OpenSSL.
Для некоторых устаревших клиентов вернём AES без эфемерных ключей.
Наконец, вернём 3DES без эфемерных ключей чисто для IE8/XP.
Итого
С шифрами закончили. Для оценки с плюсом добавим HSTS.
Проверим, что никакой ошибки в настройке шифров и протоколов нет.
Шифры на месте, хоть и не в том порядке. Но это ожидаемо. Можно запускать тест.
С таким конфигом мы получаем ровно такой результат как у Google на начало 2017 года.
При этом мы точно знаем что у всех клиентов сайт открывается настолько быстро, насколько это возможно, и без использования слабых шифров: все современные браузеры используют ECDHE.
Также вам обязательно нужно настроить OCSP stapling и, опционально, кеш TLS сессий.
Возможные проблемы
Что делать если всё сделано по инструкции, но оценка всё равно не A+ или 5 с плюсом если по нашему?



