OpenSSL 1.0.2 на продакшне: ALPN, честный ECDH и сборка DEB без сюрпризов
Обновляем OpenSSL до 1.0.2 на серверах под управлением: проверяем nginx и Postfix, включаем ALPN, чиним цепочку сборки DEB-пакетов.
OpenSSL 1.0.2 выходит с поддержкой ALPN, улучшенным ECDH и новыми API для TLS-расширений
OpenSSL 1.0.2 вышел в январе, но мы не торопились: после FREAK и череды патчей хотелось дать релизу отстояться пару месяцев, посмотреть, не полетят ли hotfix-ы один за другим. Не полетели. Плюс к нам начали поступать вопросы про HTTP/2 и ALPN - и тут стало понятно, что откладывать дальше нет смысла.
Зачем вообще 1.0.2
Три причины, которые реально влияли на наши серверы.
ALPN (Application-Layer Protocol Negotiation). Без поддержки ALPN на стороне OpenSSL nginx не может согласовать HTTP/2 по TLS - протокол согласования выбирается на этапе handshake, и без нужного расширения сервер просто не объявит h2 в списке. С 1.0.2 это работает нормально, npn_protocols (для SPDY/NPN) уступит место http2 в строке listen - как только nginx доберётся до нативного HTTP/2. Пока используем как подготовительный шаг.
Честный ECDH с per-session ключами. В 1.0.1 для настройки ECDH надо было явно вызывать SSL_CTX_set_tmp_ecdh с захардкоженной кривой - и nginx до определённых версий просто не умел автоматически выбирать кривую под параметры клиента. В 1.0.2 добавлен SSL_CTX_set_ecdh_auto - сервер сам подбирает кривую из набора, согласованного с клиентом. Forward secrecy работает корректно без дополнительных костылей в конфиге.
API для TLS-расширений. Это скорее история для тех, кто пишет своё на OpenSSL, нам напрямую не нужно - но несколько библиотек в стеке клиентов от этого выигрывают.
Совместимость с nginx
С nginx проблем меньше, чем ожидали - при условии, что собираешь OpenSSL сам, а не берёшь пакет из репозитория дистрибутива. Ubuntu 14.04 тащит 1.0.1f, Debian Wheezy - 1.0.1e, и ни там ни там 1.0.2 не будет через apt-get upgrade.
Конфиг nginx при переходе на 1.0.2 менять почти не пришлось. Одно изменение: убрали явный ssl_ecdh_curve prime256v1 - теперь это ломает auto-selection. Оставили просто:
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE+AESGCM:DHE+AESGCM:ECDHE+AES:DHE+AES:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
Проверка через openssl s_client -connect host:443 показывает ECDHE-RSA-AES128-GCM-SHA256 в качестве согласованного шифра - всё как надо.
Postfix: тут было веселее
Postfix линкуется с системным OpenSSL напрямую, и при замене разделяемой библиотеки иногда ведёт себя непредсказуемо. На одном сервере после обновления Postfix начал выдавать в лог:
warning: TLS library problem: error:1408A0C1:SSL routines:ssl3_get_client_hello:no shared cipher
Причина оказалась в том, что на этом сервере в smtpd_tls_mandatory_ciphers стоял high, а шифры из группы HIGH пересчитались при смене версии OpenSSL - несколько старых суитов туда перестали попадать. Некоторые клиенты (в основном старые MTA) не могли согласовать шифр и падали с ошибкой.
Решение: переключить на явный список шифров вместо символических групп, то же самое что делаем для nginx:
smtpd_tls_mandatory_ciphers = medium
smtpd_tls_ciphers = medium
smtpd_tls_exclude_ciphers = aNULL,eNULL,EXPORT,DES,RC4,3DES,MD5
После этого почта пошла нормально, и шифры получились разумными - без совсем дырявых суитов, но достаточно широкий выбор, чтобы не отвалились легитимные корреспонденты.
Сборка DEB-пакетов: цепочка сломалась
Это был самый трудоёмкий момент. У нас для нескольких клиентов собраны кастомные DEB-пакеты nginx, собранные с нужной версией OpenSSL как статической зависимостью. Стандартный pipeline: скачать OpenSSL, собрать статику, собрать nginx с --with-openssl=.
При переходе на 1.0.2 сборка nginx упала на этапе компиляции с ошибкой в ssl_lib.c - конфликт объявлений функций. Покопавшись, нашли две причины:
Первое - в 1.0.2 изменилась сигнатура нескольких внутренних функций ECDH, и старый патч для nginx (из нашей репы) прилетал поверх несовместимых хедеров. Патч пришлось выкинуть - в актуальном nginx он уже не нужен, функциональность включена нативно.
Второе - сборочный скрипт вызывал ./config вместо ./Configure с явным таргетом. На Ubuntu 14.04 ./config автоматически выбирал linux-x86_64, и раньше это работало. С 1.0.2 поведение изменилось - скрипт стал подтягивать дополнительные флаги компилятора, несовместимые с версией GCC на билд-хосте. Фикс простой: явно указать таргет:
./Configure linux-x86_64 --prefix=/opt/openssl-1.0.2 no-shared no-idea no-mdc2 no-rc5
make && make install_sw
После этого nginx собрался без вопросов, пакет подписан, выложен во внутренний apt-репозиторий, раскатан через Ansible.
Что проверяли после
После обновления прогоняли testssl.sh и смотрели на Qualys SSL Labs. Оценка не изменилась - была A, осталась A. Но в деталях по ECDH стало чище: Labs показывает параметры кривой и флаг forward secrecy по каждому шифру отдельно, и теперь там везде зелёный.
На сопровождении держим несколько десятков серверов с разным стеком, и OpenSSL 1.0.2 пока прошёл без инцидентов на всех, где обновили. Postfix-история повторилась на двух серверах - теперь это пункт в нашем чеклисте обновления: после смены OpenSSL всегда проверяем почтовые логи на no shared cipher.
ALPN для HTTP/2 включили на двух тестовых серверах клиентов - nginx 1.7.x с поддержкой SPDY/NPN, переход на h2 пока в работе. Это отдельная история.