TLS 1.2 после Heartbleed: отключаем SSLv3, чистим cipher suite, включаем HSTS
После Heartbleed самое время привести TLS-конфиги в порядок: выкидываем SSLv3 и TLS 1.0, ограничиваем шифры, включаем HSTS. Готовые шаблоны для nginx и Apache.
После Heartbleed индустрия активно переходит на TLS 1.2, отказываясь от SSLv3 и слабых шифров - момент чтобы привести конфиги в порядок
Heartbleed закрыли. Сертификаты перевыпустили. Кажется, можно выдохнуть. Но пока инфраструктура была открыта и все занимались OpenSSL - в логах и конфигах осталось кое-что, на что теперь самое время посмотреть трезво.
Речь про сам TLS-стек: протоколы, которые разрешены, шифры, которые используются, и заголовки, которых нет. Мы прошлись по конфигам клиентов в рамках администрирования managed-серверов и увидели картину, которая в 2014 году уже не должна быть нормой.
Что не так с типичным конфигом
Типичный nginx «из пакета» на Ubuntu или CentOS разрешает примерно следующее:
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
По факту это значит: SSLv3 - жив, TLS 1.0 - жив, RC4 - возможно присутствует, EXPORT-шифры - зависит от версии OpenSSL и дефолтов. Проверить через openssl ciphers -v 'HIGH:!aNULL:!MD5' - список будет длинным и неожиданным.
SSLv3 - это протокол 1996 года, и у него есть структурные проблемы, которые патчами не лечатся. TLS 1.0 тоже несёт ряд ограничений - в частности, CBC-атаки типа BEAST. TLS 1.1 добавил защиту от BEAST, TLS 1.2 - нормальные AEAD-шифры (AES-GCM). Всё, что ниже 1.2, - это компромисс ради совместимости с клиентами, у которых нет другого варианта.
Конфиг для nginx
Вот шаблон, который мы сейчас раскатываем на клиентских серверах:
ssl_protocols TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK;
ssl_prefer_server_ciphers on;
# HSTS - говорим браузеру: только HTTPS, год вперёд
add_header Strict-Transport-Security "max-age=31536000";
# OCSP Stapling (нужен ssl_trusted_certificate с цепочкой CA)
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/ca-chain.crt;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
Что здесь происходит:
- SSLv3 и TLS 1.0/1.1 выброшены - только TLS 1.2. Да, IE 6 и некоторые Android 2.x перестанут работать. Если аудитория это допускает - без компромиссов.
- Cipher suite - только ECDHE и DHE (forward secrecy), только AES-GCM или AES-SHA256/384. Без RC4, без 3DES, без экспортных шифров, без анонимных.
ssl_prefer_server_ciphers on- сервер навязывает порядок шифров, а не клиент. Это важно: без этого клиент может договориться о слабом шифре даже если сервер его поддерживает.- HSTS - заголовок, который говорит браузеру запомнить: этот домен - только HTTPS, никакого HTTP даже при попытке.
max-age=31536000- год.
Конфиг для Apache
На Apache 2.4 примерно то же самое, только синтаксис другой:
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
SSLHonorCipherOrder on
Header set Strict-Transport-Security "max-age=31536000"
На Apache 2.2 ситуация хуже: SSLProtocol all -SSLv2 -SSLv3 закроет только SSLv3, TLS 1.0 отключить через директиву там нельзя - нужна версия минимум 2.4. Это один из аргументов в пользу обновления: на 2.2 нормально ограничить протоколы не выйдет.
DH-параметры - про что обычно забывают
Если в cipher suite есть DHE (а он должен быть - это forward secrecy), нужен файл с параметрами Диффи-Хеллмана. По умолчанию OpenSSL использует стандартные группы 1024 бит, что слабовато.
# Генерируем 2048-битные параметры (занимает несколько минут)
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
В nginx добавить:
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
Без этого DHE-шифры работают, но на параметрах, которые сервер выбрал сам, и там может быть 1024 бит.
Как проверить результат
Быстрая проверка через openssl прямо с сервера:
# Проверяем, что SSLv3 не работает
openssl s_client -connect localhost:443 -ssl3 2>&1 | grep -i "handshake\|error"
# Смотрим, что согласовалось
openssl s_client -connect localhost:443 2>&1 | grep -E "Protocol|Cipher"
Если ssl3 вернул handshake failure - хорошо. Если согласовался - SSLv3 ещё живёт в конфиге.
Внешнюю проверку даёт Qualys SSL Labs (ssllabs.com/ssltest) - это бесплатный инструмент, который показывает полную картину: поддерживаемые протоколы, шифры, наличие forward secrecy, HSTS, OCSP и выставляет оценку от A до F. После применения конфига выше типичный результат - A или A-.
Про совместимость
Отказ от TLS 1.0 отрезает часть старых клиентов. IE на Windows XP поддерживает только TLS 1.0 и ниже - с чистым TLS 1.2 работать не будет. Android до версии 4.1 - аналогично. Java 6 - тоже TLS 1.0.
Это реальный trade-off, и его нужно оценивать по аудитории конкретного сервиса. Для внутренних сервисов и API с контролируемыми клиентами - только TLS 1.2 без вопросов. Для публичного B2C с неизвестной базой клиентов - стоит сначала посмотреть в логи на долю старых User-Agent.
Компромиссный вариант для переходного периода - оставить TLS 1.1, убрав только SSLv3 и TLS 1.0. BEAST-атака на TLS 1.0 требует MITM-позиции и определённых условий, а не просто факта поддержки протокола. Но SSLv3 - убирать без вариантов, там структурно плохо.
Мы сейчас в процессе: где аудитория позволяет - только TLS 1.2. Где нет - TLS 1.1 как минимум, с планом перехода на 1.2 по мере обновления клиентской базы.