OpenSSL + ГИС ЖКХ

от автора

Это ода данному посту.

Реализация описана для PHP, но подходит для всех.

Конфиги

Начнём с контейнера, из которого будем общаться с ГИС ЖКХ. Тут приведён конфиг контейнера с продакшена, поэтому есть лишние (для вас) пакеты

Пока просто посмотрим, пояснения будут после кода

FROM php:8.1-fpm-alpine  # это не критично, но мне нравится zsh RUN apk add zsh  # пакеты для работы openssl RUN apk add \     git \     alpine-sdk \     cmake \     wget \     bash \     libxml2-dev \     libssl1.1 # пакеты для работы с zip-архивами; будут нужны для работы с xml-файлами RUN apk add \     libzip-dev \     zip \     unzip # пакеты для использования gd RUN apk add \     libpng \     libpng-dev \     freetype-dev # postgres RUN apk add \     postgresql-dev  # общение с oracle ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ RUN chmod +x /usr/local/bin/install-php-extensions RUN install-php-extensions oci8  # кофигурируем php RUN docker-php-ext-configure zip RUN docker-php-ext-configure gd --with-freetype RUN docker-php-ext-install soap pdo pdo_pgsql zip bcmath gd intl  # composer RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" RUN php composer-setup.php RUN php -r "unlink('composer-setup.php');" RUN mv composer.phar /usr/local/bin/composer  # установка движка ГОСТ для openssl WORKDIR /opt COPY docker/production/php/openssl_gost.conf /opt/openssl_gost.conf RUN git clone --branch=openssl_1_1_1 https://github.com/gost-engine/engine.git gost-engine WORKDIR /opt/gost-engine RUN mkdir build WORKDIR /opt/gost-engine/build RUN cmake -DCMAKE_BUILD_TYPE=Release -DOPENSSL_ROOT_DIR=/usr/ssl -DOPENSSL_LIBRARIES=/usr/ssl/lib -DOPENSSL_ENGINES_DIR=/usr/ssl/lib/engines-3 .. RUN cmake --build . --config Release RUN cmake --build . --target install --config Release # на моей машине openssl лежит в папке /etc/ssl, на проде в папке /etc/ssl1.1 - не стал разбираться почему RUN OPENSSL_DIRECTORY="$([[ -d /etc/ssl1.1 ]] && echo '/etc/ssl1.1' || echo '/etc/ssl')" && \     sed -i '1s;^;openssl_conf = openssl_def\n;' "$OPENSSL_DIRECTORY/openssl.cnf" && \     cat /opt/openssl_gost.conf >> "$OPENSSL_DIRECTORY/openssl.cnf"  # ставим сертификаты для stunnel COPY docker/production/php/stunnel.conf /etc/stunnel.conf RUN mkdir -p /etc/crypto # публичный ключ стенда ГИС ЖКХ COPY gis/certs/ca-ppak.pem /etc/crypto/ca-ppak.pem # публичный ключ вашего сертификата COPY gis/certs/certificate.pem /etc/crypto/certificate.pem # приватный ключ вашего сертификата COPY gis/certs/private.key /etc/crypto/private.key RUN chmod -R 700 /etc/crypto  # ставим сам stunnel WORKDIR /opt RUN wget https://www.stunnel.org/downloads/stunnel-5.66.tar.gz -O stunnel.tar.gz RUN tar -xvf stunnel.tar.gz WORKDIR /opt/stunnel-5.66 RUN sed -i '4745 i SSL_library_init();' src/options.c RUN ./configure --disable-libwrap RUN make && make install  # бутстрапим cron COPY docker/production/php/crontab /opt/crontab RUN chmod 0644 /opt/crontab && crontab /opt/crontab RUN crond  WORKDIR /app  # запускаем все нужные процессы COPY docker/production/php/startup.sh /opt/startup.sh RUN chmod +x /opt/startup.sh CMD ["/opt/startup.sh"] 

На строке 47 ссылаюсь на openssl_gost.conf, вот он:

# gost support [openssl_def] engines = engine_section  [engine_section] gost = gost_section  [gost_section] engine_id = gost dynamic_path = /usr/ssl/lib/engines-3/gost.so default_algorithms = ALL CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet

На строке 89 ссылаюсь на /opt/startup.sh, вот он:

stunnel /etc/stunnel.conf php-fpm

Где stunnel.conf это:

socket=lsocket=l:TCP_NODELAY=1 socket=r:TCP_NODELAY=1  CAFile=/etc/crypto/ca-ppak.pem engine=gost sslVersion=TLSv1 engineDefault=ALL  output=/var/log/stunnel.log DEBUG=7  client=yes  [pseudo-https] accept=127.0.0.1:3000 connect=api.dom.gosuslugi.ru:443 ciphers=GOST2012-GOST8912-GOST8912 verify=0 TIMEOUTclose=0 cert=/etc/crypto/certificate.pem key=/etc/crypto/private.keyTCP_NODELAY=1socket=r:TCP_NODELAY=1CAFile=/etc/crypto/ca-ppak.pemengine=gostsslVersion=TLSv1engineDefault=ALLoutput=/var/log/stunnel.logDEBUG=7client=yes[pseudo-https]accept=127.0.0.1:3000connect=api.dom.gosuslugi.ru:443ciphers=GOST2012-GOST8912-GOST8912verify=0TIMEOUTclose=0cert=/etc/crypto/certificate.pemkey=/etc/crypto/private.key

Примечания к конфигам

  • Пути до файлов, указанные относительно корня проекта:

    • docker/production/php — путь до Dockerfile контейнера

    • gis/certs — путь до папок с сертификатом

  • В вашем контейнере, с которого вы будете делать запросы должен быть openssl v1.1.1, с версией 3.0 у меня нет времени разбираться) может добрые люди в комментах расскажут как это делается

  • Если контейнер не запускается и ругается на то, что не может найти stunnel, то поднимите версию, чекнув текущую на официальном сайте. К сожалению, у них нет ссылки на самую свежую стабильную версию и если выходит новая версия, то ссылки со старыми начинают выдавать 404.

Примечания к сертификатам

Нам нужны открытый и закрытый ключ. Из КриптоПРО можно выгрузить сертификат в формате cert.000. После этого идём сюда и, следуя инструкциям, делаем себе приватный ключ — https://github.com/ddruganov/get-cpcert. Публичный ключ можно достать через openssl x509 -in cert.crt -out cert.pem -outform PEM

Примечание к криптотуннелю

Для того, чтобы проксировать все запросы через криптотуннель, в php нужно допилить SoapClient:

final class CustomSoapClient extends SoapClient {     private Closure $requestHandlerCallback;      public function __construct(string $wsdl, Closure $requestHandlerCallback)     {         $this->requestHandlerCallback = $requestHandlerCallback;          parent::__construct($wsdl, [             'trace' => true,             'exceptions' => false,             'use' => SOAP_LITERAL,             'style' => SOAP_DOCUMENT,         ]);     }      public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false): ?string     {         $request = ($this->requestHandlerCallback)($request);          $location = str_replace('https://api.dom.gosuslugi.ru', Yii::$app->params['gis']['tlsTunnelAddress'], $location);          $ch = curl_init();         curl_setopt_array($ch, [             CURLOPT_URL => $location,             CURLOPT_RETURNTRANSFER => true,             CURLOPT_POST => true,             CURLOPT_POSTFIELDS => $request,             CURLOPT_CONNECTTIMEOUT => 20,             CURLOPT_TIMEOUT => 20,             CURLOPT_HTTPHEADER => [                 "SOAPAction: $action"             ]         ]);          $response = curl_exec($ch);          curl_close($ch);          return $response;     } }

В этом коде самое важное это строка 21, где в запросе по soap идёт замена прямого адреса ГИС ЖКХ на адрес криптотуннеля

Заключение

Запуская этот контейнер вы получаете готовое решение, в котором настроен криптотуннель и openssl+gost и в принципе, это всё что требуется, чтобы начать работать с ГИС ЖКХ

Мой подход уже успешно работает на продакшене около 10 месяцев, обрабатывая до 60к запросов в месяц с крайне вариативной по дням загрузкой, за это время сбоев не было (хотя вообще-то откуда им быть, это не бизнес-логика)

Ещё одна важная часть всего взаимодействия это подпись запросов по XMLDSig при помощи полученных сертификатов, но об этом чуть позже, так как там больше прикладного кода и неплохо бы его оформить в репозиторий для наглядности.

Если есть примечания или что-то непонятно — обязательно пишите, я обновлю гайд и всё поясню. Я сам долго всё это собирал, я знаю, что огромное количество людей страдает с этой интеграцией постоянно (https://gitter.im/springjazzy/GIS_JKH_Integration)


ссылка на оригинал статьи https://habr.com/ru/post/710462/