30 сентября 2013

Настраиваем HTTPS-сервер на nginx

Информационная безопасностьРазработка веб-сайтов
Tutorial
Из песочницы

Для чего я это пишу?


В последнее время в связи с кучей факторов (АНБ, 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 необходимо добавить:
ssl_session_cache   shared:SSL:10m;
ssl_session_timeout 5m;
ssl_prefer_server_ciphers on;
ssl_stapling on;
resolver 8.8.8.8;


Секция server же получится приблизительно такая:
server {
    listen       443 ssl;
    server_name  www.site.ru;
    .......

    keepalive_timeout   60;
    ssl_certificate      certificate_bundled.crt;
    ssl_certificate_key  privatekey.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers  "HIGH:!RC4:!aNULL:!MD5:!kEDH";
    add_header Strict-Transport-Security 'max-age=604800';

	.......
    location ~ \.php$ {
	.......
        fastcgi_param HTTPS on; # Для php-fpm
	.......
    }
}


В данном примере не используются DH-алгоритмы, т.е. нет Forward Secrecy. Из улучшений тут можно опустить поддержку SSLv3 (убрав его из ssl_ciphers), таким образом перестанет поддерживаться IE 6 и ниже, поскольку он не поддерживает TLS, а так же увеличить время STS, но об этом ниже.
Без SSLv3 такая настройка дает оценку 100-95-100-90 в тесте SSL.

Пройдемся по параметрам


ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;

«Задаёт тип и размеры кэшей для хранения параметров сессий.» (nginx.org) Кеш необходим для возможности повторного использования ключей сессии, таким образом при установлении нового соединения будут использоваться старые ключи, т.е. не будет повторно производиться хендшейк. Особенно актуально при использовании кодировки DHE (например в бразуере Opera 12), поскольку время загрузки страницы со всеми элементами сильно увеличивается при отсутствии кеша, а DHE еще и использует больше ресурсов и времени (относительно EECDH и RC4). Параметр shared задает общий для всех рабочих процессов nginx кеш, 10m — объем кеша (10 МБ, при этом 1 МБ~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) можно получить следующей командой:
cat certificate.crt sub.class1.server.ca.pem > certificate_bundled.crt

Где ваш сертификат — 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 имеют критические уязвимости.

ssl_ciphers «HIGH:!RC4:!aNULL:!MD5:!kEDH»;
Указывает используемые шифры. Собственно за счет изменения набора шифров и настраивается Forward Secrecy. От стандартного набора, предлагаемого nginx, отличается только параметром !kEDH,

Forward Secrecy


Для включения Forward Secrecy можно использовать например такой набор шифров:
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";


Кроме того, необходимо настроить приоритет шифров OpenSSL:
openssl ciphers -V 'EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA256 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EDH+aRSA EECDH !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS'

В данном варианте не запрещается использование RC4 для сохранения совместимости с некоторыми браузерами, однако не так давно обнаружились уязвимости и в нем, хоть и практически трудно реализуемые.

Для усиления шифрования можно увеличить стойкость DH-шифров, создав файл параметров DH-шифров (создание файла займет некоторое время!), скажем длинной 4096 бит:
openssl dhparam -out dh4096.pem 4096

И добавив в конфиг nginx директиву
ssl_dhparam dh4096.pem;

Это можно делать для скажем для веб-интерфейсов управления сервером/службами, однако хендшейк будет происходить еще дольше, поэтому не стоит делать это на обычном сайте.

Про CDN-сервисы


В обсуждении инструкций по настройке Forward Secrecy было замечено, что по крайней мере CDN Amazon CloudFront не поддерживает обмен с вашим сервером в DH-кодировках, да и RC4 вроде тоже, что не радует. Возможно что и с другими CDN тоже не все идеально, но я лично пока с ними не сталкивался, поэтому ничего сказать не могу.

Полезные ссылки


Тестирование настроек https-вебсервера
Настройки Apache и nginx для Forward Secrecy
Теги:nginxsslhttpsopensslssllabsstartssl
Хабы: Информационная безопасность Разработка веб-сайтов
+49
180,3k 803
Комментарии 55
Лучшие публикации за сутки