Как стать автором
Обновить
44.22

Опыт интеграции с КриптоПро DSS поверх ГОСТ TLS

Время на прочтение6 мин
Количество просмотров5.8K

Всем привет! Я Максим, бэкенд-разработчик команды MSB (корпоративная сервисная шина), занимаюсь интеграциями систем для внутренних нужд компании Tele2, и в этом посте хочу поделиться опытом интеграции с “КриптоПро DSS” поверх ГОСТ TLS.

Введение

В связи с экономией на бумаге ростом цифровизации бизнес-процессов, в частности, с постепенным уходом от традиционных бумажных документов к электронному документообороту, возникла потребность реализовать электронную подпись документов у нас в компании.

В качестве сервера электронной подписи используется комплекс "КриптоПро DSS", имеющий возможность встроить двухфакторное подтверждение операций подписания в мобильное приложение, посредством своего DSS SDK.

Мы встроили данный SDK в наше корпоративное мобильное приложение, о котором писала моя коллега в своей статье на Хабре.

Но мой рассказ связан с опытом решения задачи со стороны бэкенда, и мы рассмотрим эту задачу подробнее.

Описание проблемы и её решение

В нашей схеме подключение к серверу электронной подписи осуществляется по ГОСТ TLS с аутентификацией клиента по сертификату.

Но не секрет, что стандартные платформы, а именно горячо любимый мной .NET, не поддерживают российские криптошифры по ГОСТу.

В качестве эксперимента пробовал подключить rtengine, но он не завёлся, и, помимо прочего, он не является сертифицированным средством защиты информации. В таких случаях “КриптоПро” советует использовать "КриптоПро Stunnel".

Изначально, stunnel – это open-source приложение, выступающее в роли шлюза, который принимает незашифрованный трафик и пересылает его на целевой сервер поверх TLS. Часто используется, когда клиент сам не поддерживает TLS-шифрование.

А Stunnel от “КриптоПро” – это практически тот же stunnel, но с поддержкой ГОСТ TLS, а значит, он замечательно подходит для решения нашей проблемы.

Представленная выше схема рабочая, если бы не одно но: согласно политикам безопасности в компании, все запросы во внешнюю сеть Интернет могут осуществляться только через корпоративный прокси. Ванильный stunnel из коробки умеет делать запросы через прокси, но “КриптоПро” эту фичу выпилил в своей редакции.

Чтобы обойти это ограничение, в схему было решено добавить еще одно известное Linux-администраторам приложение socat (еще один шлюз, в своем роде), который умеет делать подключения через HTTP-прокси. Важное условие – HTTP-прокси должен разрешать подключения через метод CONNECT.

В итоге схема станет такой:

Docker

Для упрощения было решено пренебречь правилом “один контейнер – один процесс” и запускать “КриптоПро Stunnel” и socat в одном контейнере. Данный контейнер поднимается в виде sidecar рядом с основным контейнером микросервиса. Это позволяет нашему микросервису общаться с “КриптоПро DSS” так, как будто бы они общались по http-протоколу, а вопросы шифрования трафика по ГОСТ TLS отдаются на откуп контейнеру с stunnel и socat.

Чтобы подготовить образ контейнера, нужно скачать deb-пакет с “КриптоПро CSP” (именно в составе этого дистрибутива и состоит “КриптоПро Stunnel”). К сожалению, скачать пакет нельзя по прямой ссылке, которую можно было бы прописать в Dockerfile (иначе бы статья получилась в два раза короче). Для скачивания нужно пройти регистрацию на сайте “КриптоПро”, и только потом будет дана возможность скачать пакет.

Ниже приведен пример Dockerfile, скриптов инициализации и конфига для “КриптоПро Stunnel”. 

Рабочий пример можно также посмотреть здесь.

Dockerfile
FROM debian:buster-slim

EXPOSE 80/tcp

ARG TZ=Europe/Moscow
ENV PATH="/opt/cprocsp/bin/amd64:/opt/cprocsp/sbin/amd64:${PATH}"

# stunnel settings
ENV STUNNEL_HOST="example.cryptopro.ru:4430"
ENV STUNNEL_HTTP_PROXY=
ENV STUNNEL_HTTP_PROXY_PORT=80
ENV STUNNEL_HTTP_PROXY_CREDENTIALS=
ENV STUNNEL_DEBUG_LEVEL=5
ENV STUNNEL_CERTIFICATE_PFX_FILE=/etc/stunnel/certificate.pfx
ENV STUNNEL_CERTIFICATE_PIN_CODE=

# dependencies
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
	&& echo $TZ > /etc/timezone \
	&& apt-get update \
	&& apt-get -y install lsb-base curl socat \
	&& rm -rf /var/lib/apt/lists/*
	
# install cryptopro csp
WORKDIR /dist
COPY dist/csp_deb.tgz csp_deb.tgz
RUN tar -zxvf csp_deb.tgz --strip-components=1 \
	&& ./install.sh cprocsp-stunnel
	
WORKDIR /

COPY conf/ /etc/stunnel
COPY bin/docker-entrypoint.sh docker-entrypoint.sh
COPY bin/stunnel-socat.sh stunnel-socat.sh

RUN chmod +x /docker-entrypoint.sh /stunnel-socat.sh
	
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["stunnel_thread", "/etc/stunnel/stunnel.conf"]

docker-entrypoint.sh
#!/bin/bash
# скрипт инициализации

# ---------------------------------
# настройка csp
echo "Configuring CryptoPro CSP..."

# импорт сертификата с закрытым ключом
if [[ ! -f "$STUNNEL_CERTIFICATE_PFX_FILE" ]]; then
    echo "Client certificate not found in ${STUNNEL_CERTIFICATE_PFX_FILE}"
    exit 1
fi

certmgr -install -pfx -file "${STUNNEL_CERTIFICATE_PFX_FILE}" -pin "${STUNNEL_CERTIFICATE_PIN_CODE}" -silent || exit 1
echo "Certificate was imported."
echo

# определение контейнера-хранилища закрытых ключей
containerName=$(csptest -keys -enum -verifyc -fqcn -un | grep 'HDIMAGE' | awk -F'|' '{print $2}' | head -1)
if [[ -z "$containerName" ]]; then
    echo "Keys container not found"
    exit 1
fi

# установка сертификата клиента
certmgr -inst -cont "${containerName}" -silent || exit 1

# экспорт сертификата для stunnel
exportResult=$(certmgr -export -dest /etc/stunnel/client.crt -container "${containerName}")
if [[ ! -f "/etc/stunnel/client.crt" ]]; then
    echo "Error on export client certificate"
    echo "$result"
    exit 1
fi

echo "CSP configured."
echo

# ---------------------------------
# запуск socat
echo "Starting socat..."
nohup bash /stunnel-socat.sh </dev/null >&1 2>&1 &

# ---------------------------------
# запуск stunnel
echo "Configuring stunnel..."
sed -i "s/^debug=.*$/debug=$STUNNEL_DEBUG_LEVEL/g" /etc/stunnel/stunnel.conf

echo "Starting stunnel"
exec "$@"

stunnel-socat.sh
#!/bin/bash
echo Configuring socat...

socatParameters="TCP:${STUNNEL_HOST}"

if [[ -n "$STUNNEL_HTTP_PROXY" ]]; then
    # если указан http-прокси, подключение будет происходить через него
    socatParameters="PROXY:${STUNNEL_HTTP_PROXY}:${STUNNEL_HOST},proxyport=${STUNNEL_HTTP_PROXY_PORT}"

    if [[ -n "$STUNNEL_HTTP_PROXY_CREDENTIALS" ]]; then
        socatParameters="${socatParameters},proxyauth=${STUNNEL_HTTP_PROXY_CREDENTIALS}"
    fi
fi

socatCmd="socat UNIX-LISTEN:/var/run/socat.sock,reuseaddr,fork ${socatParameters}"

while true; do
    rm -f /var/run/socat.sock
    echo $(date) "Start socat instance."
    ${socatCmd}
    sleep 1
done

stunnel.conf
foreground=yes
pid=/var/opt/cprocsp/tmp/stunnel_cli.pid
output=/dev/stdout
debug=5

[https]
client=yes
accept=80
cert=/etc/stunnel/client.crt
verify=0
connect=/var/run/socat.sock

Про Dockerfile рассказывать не буду, он достаточно тривиален, а вот скрипт инициализации docker-entrypoint.sh интереснее. Первым делом скрипт импортирует сертификат с закрытым ключом в хранилище ключей, так как “КриптоПро Stunnel” для работы необходим закрытый ключ. Затем из хранилища экспортируется сертификат с открытым ключом в формате DER. В дальнейшем по этому сертификату “КриптоПро Stunnel” будет получать закрытый ключ из хранилища ключей.

После инициализации хранилища ключей происходит настройка и запуск socat. Для конфигурирования socat добавлены переменные окружения, которые позволяют указать, через какой HTTP-прокси необходимо выполнять запросы. Не буду останавливаться на этих переменных – их описание есть в репозитории. Однако не лишним будет уточнить, что, если переменные не указаны, socat будет самостоятельно выполнять TCP-запросы до целевого сервера. Для получения входящих запросов socat открывает unix-сокет, на который и будет обращаться “КриптоПро Stunnel”.

Финальным шагом в скрипте являются конфигурирование Stunnel и его последующий запуск. 

“КриптоПро Stunnel” при запуске начинает прослушивать порт 80, то есть принимать голый HTTP-трафик. HTTP-трафик будет шифроваться по ГОСТу и пересылаться на unix-сокет, который слушает socat. Socat, в свою очередь, откроет соединение с целевым сервером, напрямую или через HTTP-прокси, и отправит уже шифрованный запрос. 

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

Вместо заключения

К сожалению, документация по отечественным решениям зачастую достаточно скромна. К примеру, на попытки заставить работать “КриптоПро Stunnel” через HTTP-прокси ушло много времени, пока не пришло понимание, что “КриптоПро Stunnel” прокси не поддерживает и что без еще одного инструмента не обойтись.
Данная статья призвана помочь сберечь ваше время, надеюсь, описанное окажется полезным.

Бонус

В качестве бонуса хотелось бы поделиться несколькими советами:

  • При выполнении запроса через Stunnel всегда добавляйте HTTP-заголовок “Host: example.service.ru” с указанием целевого сервера. Если заголовок не будет указан, сервер может возвращать код 404, т. к. неясно, к какому домену относится запрос.

  • Помимо заголовка Host, необходимо передавать заголовок Content-Length, иначе stunnel некорректно обрабатывает запрос.

  • Об этом часто забывают, но нужно помнить, что URL чувствителен к регистру. Например, https://example.service.ru/url1 не равен https://example.service.ru/URL1, и результат запроса будет зависеть от реализации веб-сервера, в частности “КриптоПро DSS” требователен к регистру.

Список ресурсов

Теги:
Хабы:
+2
Комментарии5

Публикации

Информация

Сайт
ru.tele2.ru
Дата регистрации
Дата основания
Численность
5 001–10 000 человек
Местоположение
Россия