Хабрахабр

Docker-образы с поддержкой ГОСТ-сертификатов в openssl, curl, php, nginx

10-2001 (устарел) и ГОСТ Р 34. В этой статье я расскажу о том, как я решал задачу об интеграции в тестовом режиме с сервисами, которые работают с использованием алгоритмов, определенных ГОСТ Р 34. Приведу примеры некоторых проблем, с которыми столкнулся при решении задачи, дам ссылки на готовое решение и покажу несколько примеров их использования. 10-2012.

Причины

Мне же нужно было реализовать взаимодействие в тестовом (девелоперском) окружении на Linux, покупать для этого лицензии не очень-то удобно, но что хуже, в открытом доступе отсутствуют документации по этому вопросу. Для начала стоит сказать, что для продакшн-окружения существуют сертифицированные средства от компаний «Криптоком», «Крипто-Про», «Сигнал-КОМ» и других. Среди них упоминается использование OpenSSL + Крипто CSP, я не помню деталей, но эта связка у меня не завелась с поддержкой ГОСТ Р 34. По запросу «https гост» не так много решений. Другим результатом, который часто встречался, было предложение использовать OpenSSL 1. 10-2012. 1. 0, в котором GOST-engine встроен внутрь, но такое решение тоже не содержало в себе поддержку GOST2012-GOST8912-GOST8912.
Рабочим решением оказалась сборка OpenSSL 1. В интернете часто встречается упоминание, что некая российская компания приложила усилия по его разработке, но в самом репозитории нет сведений об авторах продукта. 0g + вынесенный в отдельно подключаемый динамический движок GOST-engine. На этой странице сказано, что до OpenSSL 1. Пользуясь случаем, выражаю авторам благодарность за движок в open source. 0 использовался встроенный движок GOST, сейчас для GOST используется сторонний от OpenSSL продукт. 1. Судя по конфигурации, скорее всего, это одна и та же библиотека.

Сборка OpenSSL, GOST-engine, cURL

Для сборки OpenSSL, GOST-engine и cURL пришлось разобраться с кучей опций и перепробовать несколько комбинаций версий. Сборка стороннего продукта для тех, кто делает это редко, может быть нетривиальной задачей. Например, я собирал OpenSSL 1. Если в Dockerfile вы заметите странное, то, скорее всего, это осталось от таких экспериментов.
Я зафиксировал все версии проектов для сборки, чтобы исключить ситуацию, что из-за обновления что-то перестанет работать. 0h + GOST-engine и команда openssl ciphers не содержала GOST-алгоритмов, хотя openssl engine показывала GOST-engine в списке. 1. 1. Указав предыдущую версию OpenSSL 1. Это было очень странно, я повторил это еще раз, потом пытался выяснить причины, но в итоге решил остаться на 1. 0g, все работало, как ожидалось. 0g.
Собирая сам GOST-engine, я не сразу обратил внимание на наличие файла INSTALL.md в master-ветке, потому что собирал из ветки openssl_1_1_0 по неизвестно откуда взятой документации. 1. Та версия собиралась с кастомной сборкой OpenSSL командой

cmake -DCMAKE_C_FLAGS='-I/usr/local/ssl/include -L/usr/local/ssl/lib' ..

В итоге решение было найдено и зафиксировано.
Для сборки cURL с кастомной сборкой OpenSSL гораздо больше документации, тем не менее, документация успевала устаревать, и с опциями пришлось много экспериментировать.
Результат работы выложен здесь: https://github.com/rnixik/docker-openssl-gost
Образ запушен в Docker Hub: https://hub.docker.com/r/rnix/openssl-gost/ Но master-ветка так собираться перестала, появились ошибки об отсутствии DOPENSSL_ROOT_DIR и подобное.

Примеры использования OpenSSL + GOST-engine

Не буду сильно вдаваться в подробности работы с докер-образами, лишь приведу одну команду для начала работы внутри образа:

docker run --rm -i -t rnix/openssl-gost bash

Для начала можно убедиться, что алгоритмы GOST2012-GOST8912-GOST8912 и GOST2001-GOST89-GOST89 есть в списке поддерживаемых:

openssl ciphers

Если вам известен хост, который работает по HTTPS на основе ГОСТ-алгоритмов, то посмотреть его сертификат и попробовать подключиться можно командой:

openssl s_client -connect gost.example.com:443 -showcerts

10-2012: Создать закрытый ключ и сертификат по ГОСТ Р 34.

openssl req -x509 -newkey gost2012_256 -pkeyopt paramset:A -nodes -keyout key.pem -out cert.pem

Подписать файл ранее созданными сертификатами:

openssl cms -sign -signer cert.pem -inkey key.pem -binary -in file.txt -nodetach -outform DER -nocerts -noattr -out signed.sgn

Извлечь содержимое подписанного файла, сертификатом, который был подписан самим собой:

openssl cms -verify -in signed.sgn -certfile cert.pem -CAfile cert.pem -inform der -out data.txt

Пока мне не попался сервис, у которого сертификат выдан доверенным центром сертификации. С подписями самих сертификатов все немного сложнее. Это можно сделать глобально для системы (в образе), либо явно указывать в каждой команде.
Программа cURL в использовании не изменилась, просто поддерживает хосты с ГОСТ-сертификатами. Обычно, требуется дополнительно указывать сертификат удостоверяющего центра при работе с выданными им сертификатами.

Использование образа в работе с языками программирования

Например: Если язык программирования позволяет исполнять установленные в системе программы, то задача использования ГОСТ-алгоритмов проще всего решается копированием бинарников собранных openssl и curl в конце Dockerfile языка программирования с использованием multi-stage build.

FROM rnix/openssl-gost AS openssl-gost # Replace with any other image based on Debian x86_64
FROM debian:stretch-slim COPY --from=openssl-gost /usr/local/ssl /usr/local/ssl
COPY --from=openssl-gost /usr/local/ssl/bin/openssl /usr/bin/openssl
COPY --from=openssl-gost /usr/local/curl /usr/local/curl
COPY --from=openssl-gost /usr/local/curl/bin/curl /usr/bin/curl
COPY --from=openssl-gost /usr/local/bin/gostsum /usr/local/bin/gostsum
COPY --from=openssl-gost /usr/local/bin/gost12sum /usr/local/bin/gost12sum

Даже необязательно копировать в /usr/bin, это можно сделать в любой каталог, а затем вызывать из вашей программы, передав полный путь и все аргументы.

Поддержка ГОСТ-алгоритмов в PHP

Но, глядя на то, как собирается PHP-FPM в Dockerfile, мне показалось, что можно легко собрать PHP с кастомными сборками OpenSSL и cURL. PHP, конечно, позволяет делать вызовы системных команд, используя, например, exec. Всё равно собрал.
По какой-то причине, я начал с PHP-FPM 7. Как оказалось дальше, я ошибся, что это легко. Идея была в том, чтобы использовать multi-stage build. 1. Одним из значительных было то, что при сборки php 7. Для этого надо заменить в их Dockerfile инструкцию FROM на мою FROM rnix/openssl-gost AS openssl-gost, затем прописать копирования собранных openssl и curl до начала сборки самого php и, наконец, указать в опции сборки путь до этих библиотек --with-openssl-dir=/usr/local/ssl и --with-curl=/usr/local/curl заменив оригинальные.
Сюрпризы ждали отовсюду. В результате собиралось не то, что нужно. 1 используется pkg-config, а явная установка libcurl4-openssl-dev, libssl-dev прописывали в pkg-config версии из пакетов. Пришлось дополнительно копировать из кастомных сборок openssl и curl их lib/pkgconfig /*. Если убрать их установку, то /configure php падал, ссылаясь на отсутствие openssl в pkg-config. Дальше выяснилось, что зависимости устанавливали openssl, тем самым перезатирая ранее скопированный бинарник моей кастомной сборки. После десятков таких сюрпризов сборка начала проходить. Но это не всё.
В собранном php появились алгоритмы хешей openssl_get_md_methods(): GOST R 34. Пришлось дополнительно копировать его в самом конце. 11-2012 with 512 bit hash, GOST R 34. 11-2012 with 256 bit hash, GOST R 34. Это значило, что php подключил GOST-engine. 11-94. Я потратил несколько часов, пытаясь найти причину этого. А вот использование расширения curl в php почему-то говорило, что таких алгоритмов не знает, соединяясь с хостом по GOST-HTTPS. Я искал место, где могут определяться поддерживаемые алгоритмы или подключатся движки. Я смотрел исходники, как устроено расширение curl в php, как устроен сам curl, как они связываются с openssl. Тогда я вспомнил, что я собираю не последнюю версию PHP.
Я попробовал собрать PHP-FPM 7. Я искал по master-веткам, много гуглил, но ничего, что решило бы сразу проблему, не нашел. Пришлось внести некоторые правки в мой скрипт корректировки оригинального Dockerfile PHP-FPM, но сборка начала проходить без большого количества сюрпризов. 2. Каждый вызов php писал в stdout GOST engine already loaded. Главной новостью стало то, что теперь расширение curl внутри php умело общаться по ГОСТ-алгоритмам с хостами, но была одна неприятность. Я не сразу понял, кто это делает, но такую строчку нашел в исходниках GOST-engine. Очень неприятно. Но, видимо, в php 7. Я до сих пор не знаю, в какой части системы случилась ошибка: php, php-curl, curl, openssl. 2 начал подключать его дважды. 1 php-curl не подключал движок совсем, теперь в php 7. Так как все работало корректно, только был вывод, я решил его убрать правкой исходника инструкцией в Dockerfile:

sed -i 's|printf("GOST engine already loaded\\n");|goto end;|' gost_eng.c

Зато, пересобрав весь зоопарк, все начало работать, как и хотелось.
Образ с PHP-FPM + OpenSSL + GOST + cURL запушен в Docker Hub: https://hub.docker.com/r/rnix/php-fpm-gost/ Там строчкой ниже уже есть goto end;, поэтому ничего серьезного я не сделал.

Поддержка ГОСТ-сертификатов в nginx

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

  1. Легко поднимать свой веб-сервер с ГОСТ-сертификатом по HTTPS (TLS).
  2. Легко проксировать все запросы на хост с ГОСТ-сертификатом

Его нужно создать. Легко – это означает докер-образ. Открыв документацию сборки nginx, я увидел одну опцию о ssl — --with-http_ssl_module, которая просто булева. Для этого нужно в Dockerfile собрать nginx с кастомным OpenSSL и GOST-engine. Как показала практика, nginx хочет, чтобы здесь были исходники openssl, а сборкой скрипты nginx займутся сами. Но nginx популярный продукт, инструкций по сборке с openssl много, поэтому я нашел еще опцию --with-openssl=[DIR]. Я ознакомился с help-выводом сборщика nginx, но ничего, что мне там помогло бы, я не нашел.
Пришлось дублировать инструкции выкачивания исходников OpenSSL, распаковки, сборки GOST-engine, включения GOST-engine в конфиги. Это не совсем то, что я хотел бы (я хотел использовать multi-stage build). Это я проверял указанием в конфиге ssl_ciphers GOST2001-GOST89-GOST89:HIGH:MEDIUM;. Всё это начало собираться, но поддержки ГОСТ-алгоритмов в nginx все ещё не было. ./Configure выводил no-dynamic-engine [forced]. Выполнение nginx -t говорило, что не знает этого алгоритма.
Как оказалось, openssl, собранный nginx, не поддерживал динамические движки, т.е. Причина нашлась в аргументах сборки openssl, которые вызывал nginx, а именно no-shared. Здесь пришлось внимательно почитать документацию сборки OpenSSL, чтобы выяснить, что проставило forced. Пришлось править инструкцию сборки: Если это указано, то нет никаких флагов, чтобы включить поддержку загрузки движков.

sed -i 's|--prefix=$ngx_prefix no-shared|--prefix=$ngx_prefix|' auto/lib/openssl/make

1. Всё это собралось, но nginx начал ругаться, что не может найти gost.so по пути /usr/lib/x86_64-linux-gnu/, это довольно странно, потому что тот же собранный openssl ищет и находит движки совсем в другом месте, а именно там, где и собирался ./lib/engines-1. Заработало.
Рядом с основным Dockerfile в репозитории я положил демонстрационный Dockerfile, который при сборке создает себе ГОСТ-сертификаты и использует их, обрабатывая соединения на https://gost.example.com. Добавил инструкцию копирования собранных движков в /usr/lib/x86_64-linux-gnu/, чтобы угодить nginx. А вместо GOST2001-GOST89-GOST89GOST2012-GOST8912-GOST8912.
Образ с nginx + GOST запушен в Docker Hub: https://hub.docker.com/r/rnix/nginx-gost/ Придется поработать с DNS или docker network, чтобы из одного контейнера попробовать подключится к этому демонстрационному, но все это я описал в документации.
Демонстрационный хост использует ключи по gost2001, другие варианты это gost2012_256, gost2012_512.

Заключение

Решение оформлено в виде репозитория на GitHub.
Стоит сказать о безопасности использования такого решения. Мной изучена проблема работы с ГОСТ-алгоритмами в системах Linux, предоставлено решение в виде docker-образов, все это сопровождено документацией и примерами. Я все равно могу собрать образ с любыми правками всех используемых библиотек и систем и запушить его в свой Docker Hub под любым тегом. Главное, не стоит доверять образам на Docker Hub, даже если там написано Automated Build. Тем не менее, это не гарантирует, что в нем нет ошибок и уязвимостей. Поэтому рекомендую форкать репозиторий на гитхабе, пулить его себе и уже самостоятельно собирать, проверив инструкции в Dockerfile на наличие того, что используются только официальные ресурсы без подозрительных модификацией по ходу сборки.
Собирая образ самостоятельно, вы можете убедиться, что в код не попали злонамеренные правки, потому что сборка происходит только из открытого кода, который доступен для просмотра всем желающим. Использование проприетарных сертифицированных средств так же не гарантирует отсутствие ошибок и уязвимостей, но к тому же их код от вас закрыт.

Ссылки

  1. Репозиторий со всеми решениями на GitHub
  2. Образ на Docker Hub с OpenSSL + GOST + cURL
  3. ГОСТ Р 34.10-2012 на Википедии со списком сертифицированных решений
  4. Репозиторий GOST-engine
  5. О возможностях и ограничениях GOST-engine
  6. Пример того, как решают вопрос в продакшн-окружении
Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть