Всем привет. Представлюсь — меня зовут Евгений Думчев и я Team Lead .NET разработки в DDPlanet.
В какой-то момент в моей практике появилась задача по интеграции с внешним API. Для взаимодействия требовалось применять предоставленный публичный доверенный сертификат сервера .cer и клиентский .pfx сертификат. Особенность в том, что .pfx сертификат был выпущен через CryptoPro CSP — а это вносит свои тонкости в процесс интеграции.
В этой статье я расскажу, как интегрировать .NET-приложение с внешним API, требующему двусторонней TLS-аутентификации по национальным криптографическим стандартам (ГОСТ) с использованием сертификатов, выпущенных через CryptoPro CSP.
Содержание
-
Безопасность и сферы применения двустороннего TLS с CryptoPro
-
Особенности взаимодействия с API, использующим сертификат CryptoPro
Безопасность и сферы применения двустороннего TLS с CryptoPro
При работе с государственными или банковскими API в РФ часто предъявляются требования к применению национальных криптографических стандартов. В таких случаях сертификаты формируются через CryptoPro CSP и содержат ГОСТ-алгоритмы, которые отличают эти сертификаты от обычных X.509 сертификатов, применяемых в международной практике.
Более того, для повышения уровня безопасности при взаимодействии по API может применяться двусторонняя TLS-аутентификация (mTLS), которая обеспечивает повышенный уровень доверия между клиентом и сервером. Это означает, что:
-
Сервер предоставляет свой сертификат — клиент проверяет, доверять ли серверу.
-
Клиент предъявляет свой сертификат (client certificate) — сервер проверяет его подлинность.
Работает это по следующему принципу:
-
При вызове внешнего API, клиент (в нашем случае .NET Web API приложение) выполняет запрос соединения с сервером.
-
В процессе TLS-рукопожатия сервер отсылает клиенту свой публичный сертификат.
-
Клиент проверяет валидность публичного сертификата, сравнивая его с заранее известным и доверенным сертификатом сервера (например,
.cerфайлом) или по цепочке доверия к корневому УЦ. Если сертификат соответствует ожидаемому, то можно доверять этому серверу. -
Так как на стороне сервера включена двусторонняя аутентификация — он отправляет запрос о необходимости предоставления клиентского сертификата.
-
Клиент отправляет публичный сертификат (и при необходимости цепочку) из контейнера
.pfxи подтверждает владение приватным ключом из контейнера.pfx. -
Сервер проверяет подпись и цепочку доверия, а также сверяет, выдан ли сертификат от доверенного УЦ.
-
Если все проверки пройдены успешно — устанавливается защищенное соединение — API становится доступным и клиенту можно выполнять обработку запросов.
Сертификат .pfx (Personal Information Exchange) применяется из соображений безопасности. Это стандартизированный двоичный формат контейнера сертификатов PKCS #12 (Public-Key Cryptography Standards). Он представляет собой единый файл, который включает в себя закрытый ключ (private key), открытый ключ / сертификат (public key) и цепочку доверия (промежуточные и корневые сертификаты). К особенностям можно отнести то, что содержимое .pfx-файла шифруется и защищается паролем.
Дополнительно .pfx-файл может быть экспортирован с включенной опцией «расширенной защиты», что позволяет повысить уровень безопасности хранения закрытого ключа внутри контейнера. Это достигается посредством применения более стойкого шифрования и добавления ограничения на экспорт — даже после импорта сертификата его нельзя будет снова выгрузить с приватным ключом.
Получаем следующую схему взаимодействия:
С таким уровнем безопасности с применением CryptoPro чаще всего можно столкнуться при взаимодействии с отечественным ПО в следующих сферах:
-
Системы электронного документооборота (ЭДО).
-
Порталы государственных услуг.
-
Банковские и финансовые API.
-
Торговые площадки, работающие с ЭЦП (электронной подписью).
-
Юридически значимые интеграции — в которых важно соблюдение требований к квалифицированной электронной подписи (КЭП).
Особенности взаимодействия с API, использующим сертификат CryptoPro
При взаимодействии со сторонней API по REST в нашем случае должен использоваться .pfx сертификат, созданный через CryptoPro. Это означает, что алгоритмы и структуры сертификата могут содержать специфичные для ГОСТ и CryptoPro криптографические идентификаторы OID, например, 1.2.840.113549.1.12.1.80, которые не поддерживаются большинством стандартных библиотек (например, OpenSSL, curl). Что требует использования именно CryptoPro или его оберток для корректной работы.
У сторонней API предусмотрена Swagger-документация, но она так просто не доступна в браузере.
Во-первых, для доступа к API из браузера необходимо установить CryptoPro CSP. Этот инструмент позволяет установить на устройство клиентский сертификат .pfx.
Во-вторых, даже после установки сертификата Swagger-документация внешнего API становится доступна только в браузерах Chromium-Gost и Яндекс Браузер. Эти браузеры поддерживают отечественные криптографические алгоритмы ГОСТ, используемые в сертификатах, выпущенных через CryptoPro. И на этих браузерах должен быть установлен КриптоПро ЭЦП Browser Plugin.
Наконец, при открытии url /swagger в подходящем браузере появляется модальное окно CryptoPro CSP для выбора нужного сертификата, применяемого для доступа к ресурсу. И после выбора нужного сертификата — победа, Swagger-документация становится доступна и позволяет успешно отправлять REST запросы на сервер.
Но как же выполнять взаимодействие с API по REST из приложения на .NET? Ведь в случае классической реализации выполнения запросов через HttpClient получаем в ответе HTTP статус код 400 (Bad Request) с ошибкой:
<html> <head><title>400 No required SSL certificate was sent</title></head> <body> <center><h1>400 Bad Request</h1></center> <center>No required SSL certificate was sent</center> <hr><center>nginx</center> </body> </html>
Основным решением является подключение к запросам клиентского сертификата .pfx CryptoPro и публичного доверенного сертификата сервера .cer для организации двусторонней TLS-аутентификации.
Рассмотрим следующие варианты решения:
-
для .NET приложения, запускаемом на Windows;
-
для .NET приложения, запускаемом на Linux;
-
с помощью NGINX.
Решение для .NET приложений в Windows
Самый простой способ подцепить сертификаты (клиентский .pfx и доверенный сертификат сервера .cer) из приложения на .NET под Windows заключается в том, чтобы добавить их в виде файлов в структуру проекта.
После чего эти файлы можно подгрузить по пути к ним и применить в REST запросах к целевой API. Для этого необходимо сконфигурировать типизированный HttpClient и добавить к обработке HTTP запросов логику применения сертификатов в HttpClientHandler.
public static IServiceCollection ConfigureApiHttpClient(this IServiceCollection services) { services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService<IOptions<ApiOptions>>().Value; var env = serviceProvider.GetRequiredService<IWebHostEnvironment>(); // Формируем абсолютные пути к сертификатам var publicCertPath = Path.Combine(env.ContentRootPath, "Certificates", options.CerCertificateName); var privateCertPath = Path.Combine(env.ContentRootPath, "Certificates", options.PfxCertificateName); return new ApiCertificates { //Загружаем серверный публичный сертификат (.cer) PublicCert = new X509Certificate2(publicCertPath), //Загружаем клиентский сертификат с приватным ключом (.pfx) PrivateCert = new X509Certificate2(privateCertPath, options.PfxCertificatePassword) }; }); services.AddHttpClient<IApiHttpClient, ApiHttpClient>() .ConfigurePrimaryHttpMessageHandler((serviceProvider) => { var apiCertificates = serviceProvider.GetRequiredService<ApiCertificates>(); var handler = new HttpClientHandler(); handler.ClientCertificateOptions = ClientCertificateOption.Manual; //клиент будет отправлять свой сертификат при TLS-рукопожатии handler.ClientCertificates.Add(apiCertificates.PrivateCert); // Опционально: настройка проверки серверного сертификата handler.ServerCertificateCustomValidationCallback = (_, cert, _, sslPolicyErrors) => { // Проверяем, совпадает ли сертификат с ожидаемым if (cert?.Thumbprint?.Equals(apiCertificates.PublicCert.Thumbprint, StringComparison.OrdinalIgnoreCase) == true) return true; // Если сертификат не совпадает, можно по желанию разрешить стандартную проверку TLS return sslPolicyErrors == SslPolicyErrors.None; }; return handler; }); return services; }
После запуска приложения и выполнения целевого запроса вызывается криптопровайдер CryptoPro CSP и появляется всплывающее окно запроса пароля. Такое поведение проявляется при использовании сертификатов с пометкой «требовать защищенную сессию». В этом случае CryptoPro всегда запрашивает ввод пароля или подтверждение доступа, даже если вы уже указали пароль при загрузке X509Certificate2.
После указания учетных данных запросы успешно доходят до целевого API.
Но в данном случае появляется проблема в виде всплывающего окна установки контейнера с паролем, что блокирует работу приложения.
Можно воспользоваться альтернативным вариантом и предварительно установить все эти сертификаты на устройство и искать целевые сертификаты из хранилища сертификатов.
Правильнее проводить поиск сертификатов в хранилище по Thumbprint (отпечатку) — это считается наиболее надежным и безопасным способом идентификации сертификатов в хранилище. По сути, это уникальный SHA-1 хэш содержимого сертификата, который гарантирует точное совпадение и устойчивость к дубликатам.
Тогда получим следующий код:
private static X509Certificate2? FindCertInStore(string thumbprint, StoreName storeName, StoreLocation storeLocation) { using var store = new X509Store(storeName, storeLocation); store.Open(OpenFlags.ReadOnly); return store.Certificates .Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) .FirstOrDefault(); } public static IServiceCollection ConfigureApiHttpClient(this IServiceCollection services) { services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService<IOptions<ApiOptions>>().Value; return new ApiCertificates { //Загружаем серверный публичный сертификат (.cer) PublicCert = FindCertInStore(options.CerCertificateThumbprint, StoreName.My, StoreLocation.CurrentUser)!, //Загружаем клиентский сертификат с приватным ключом (.pfx) PrivateCert = FindCertInStore(options.PfxCertificateThumbprint, StoreName.My, StoreLocation.CurrentUser)! }; }); services.AddHttpClient<IApiHttpClient, ApiHttpClient>() .ConfigurePrimaryHttpMessageHandler((serviceProvider) => { var apiCertificates = serviceProvider.GetRequiredService<ApiCertificates>(); var handler = new HttpClientHandler(); handler.ClientCertificateOptions = ClientCertificateOption.Manual; //клиент будет отправлять свой сертификат при TLS-рукопожатии handler.ClientCertificates.Add(apiCertificates.PrivateCert); // Опционально: настройка проверки серверного сертификата handler.ServerCertificateCustomValidationCallback = (_, cert, _, sslPolicyErrors) => { // Проверяем, совпадает ли сертификат с ожидаемым if (cert?.Thumbprint?.Equals(apiCertificates.PublicCert.Thumbprint, StringComparison.OrdinalIgnoreCase) == true) return true; // Если сертификат не совпадает, можно по желанию разрешить стандартную проверку TLS return sslPolicyErrors == SslPolicyErrors.None; }; return handler; }); return services; }
В данном случае сертификат CryptoPro успешно достается из хранилища сертификатов Windows и применяется в запросах на сервер без всплывающего окна и запроса пароля. Это работает за счет того, что криптопровайдер CryptoPro интегрируется в инфраструктуру Windows как один из поддерживаемых провайдеров. Соответственно, сертификаты, установленные через CryptoPro, регистрируются в стандартных хранилищах Windows — например, в StoreName.My и StoreLocation.CurrentUser или LocalMachine и доступны через X509Store.
Решение для .NET приложений в Linux
Для запуска приложения в Linux соберем и запустим docker образ.
# этап сборки проекта FROM mcr.microsoft.com/dotnet/sdk:8.0 AS publish WORKDIR /src COPY . . WORKDIR "/src/Cert.Client.Api" RUN dotnet restore "Cert.Client.Api.csproj" --verbosity Minimal --use-current-runtime RUN dotnet publish "Cert.Client.Api.csproj" --no-restore -c Release -o /app/publish /p:UseAppHost=false # этап подготовки финального образа FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final RUN sed -i '/\[openssl_init\]/a ssl_conf = ssl_sect' /etc/ssl/openssl.cnf RUN printf "\n[ssl_sect]\nsystem_default = system_default_sect\n" >> /etc/ssl/openssl.cnf RUN printf "\n[system_default_sect]\nMinProtocol = TLSv1.2\nCipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf USER $APP_UID WORKDIR /app EXPOSE 8080 EXPOSE 8081 ENV ASPNETCORE_ENVIRONMENT=Development WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "Cert.Client.Api.dll"]
Если воспользоваться логикой подключения сертификатов напрямую из структуры проекта, то при запуске получим ошибку:
System.Security.Cryptography.CryptographicException: The certificate data cannot be read with the provided password, the password may be incorrect. ---> System.Security.Cryptography.CryptographicException: The EncryptedPrivateKeyInfo structure was decoded but was not successfully interpreted, the password may be incorrect. ---> System.Security.Cryptography.CryptographicException: The algorithm identified by '1.2.840.113549.1.12.1.80' is unknown, not valid for the requested usage, or was not handled. at System.Security.Cryptography.X509Certificates.OpenSslX509CertificateReader.FromFile(String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
Ошибка возникает из-за того, что OpenSSL не может корректно прочитать сертификат, поскольку в нем используется алгоритм шифрования от CryptoPro, который не поддерживается в стандартной сборке OpenSSL. То есть в Linux такой сертификат не типизировать в X509Certificate2, как это работало для Windows «из коробки».
Установить сертификат на Linux с помощью команд OpenSSL не получится — формат PFX сертификатов не поддерживается в операционной системе Linux. Однако есть возможность конвертировать его в формат PEM с помощью OpenSSL:
# Конвертация .pfx в .pem openssl pkcs12 -in private-cert.pfx -out private-cert.pem -nodes
Также можно попытаться разобрать .pfx на составляющие, что небезопасно, так как приватный ключ будет в незашифрованном виде:
# Извлечь ключ openssl pkcs12 -in private-cert.pfx -nocerts -nodes -out private.key # Извлечь сертификат openssl pkcs12 -in private-cert.pfx -clcerts -nokeys -out certificate.crt # Извлечь CA-цепочку openssl pkcs12 -in private-cert.pfx -cacerts -nokeys -out ca-chain.crt
Все это не сработает для .pfx сертификата CryptoPro по причине неизвестного алгоритма шифрования.
Error outputting keys and certificates C8650000:error:03000079:digital envelope routines:EVP_PBE_CipherInit_ex:unknown pbe algorithm:crypto\evp\evp_pbe.c:116:TYPE=1.2.840.113549.1.12.1.80
Одним из вариантов решения является установка модифицированной версии OpenSSL, например, gost-engine. Но я бы не рекомендовал подобный вариант, так как сторонние реализации не гарантируют поддержку и безопасность, а также могут не поддерживать актуальные сертификаты или ключи.
Правильным вариантом решения является использование сертифицированного CryptoPro CSP для Linux. И установить его в docker-образ. Скачать необходимый дистрибутив можно по ссылке. В нашем случае используется образ mcr.microsoft.com/dotnet/aspnet:8.0, который базируется на debian образе, поэтому нужно установить дистрибутив linux-amd64_deb.tgz.
CryptoPro CSP является набором инструментов по работе с криптографией, предназначенных для реализации российских алгоритмов ГОСТ, создания и проверки электронной подписи, шифрования, управления сертификатами и контейнерами закрытых ключей. Часть полезных инструментов, содержащихся в сборке linux-amd64_deb.tgz:
-
csptest— инструмент для тестирования функций CSP: проверка алгоритмов, ключей, сертификатов, подписей и шифрования. -
certmgt— утилита управления сертификатами: установка, удаление, экспорт и просмотр сертификатов из хранилищ CSP. -
cprodiag— диагностический инструмент для сбора информации о конфигурации CryptoPro CSP, лицензии, хранилищах и возможных ошибках. -
cpverify— проверка целостности и корректности установки CryptoPro CSP: сверка контрольных сумм и доступности компонентов. -
cryptcp— утилита для подписания, шифрования и проверки данных. -
cpconfig— утилита настройки компонентов CryptoPro CSP: используется для активации и просмотра лицензий, управления параметрами криптопровайдера и системными настройками. -
cpnginx— модифицированная версия NGINX с поддержкой ГОСТ TLS через интерфейс SSPI, встроенная в CryptoPro CSP.
Внесем изменения в Dockerfile — добавим команды по установке CryptoPro CSP. Для соблюдения правил использования необходимо указать лицензионный ключ. И с помощью инструментария CryptoPro CSP нужно установить сертификаты .pfx и .cer. Скорректированный Dockerfile:
# этап сборки проекта FROM mcr.microsoft.com/dotnet/sdk:8.0 AS publish WORKDIR /src COPY . . WORKDIR "/src/Cert.Client.Api" RUN dotnet restore "Cert.Client.Api.csproj" --verbosity Minimal --use-current-runtime RUN dotnet publish "Cert.Client.Api.csproj" --no-restore -c Release -o /app/publish /p:UseAppHost=false # этап подготовки финального образа FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final # установка CryptoPro WORKDIR /cryptopro ADD Cert.Client.Api/CryptoPro/linux-amd64_deb.tgz . RUN ./linux-amd64_deb/install.sh # добавление лицензионного ключа CryptoPro ARG CRYPTO_PRO_LICENSE RUN /opt/cprocsp/sbin/amd64/cpconfig -license -set ${CRYPTO_PRO_LICENSE} # копирование сертификатов CryptoPro ADD Cert.Client.Api/Certificates ./certificates # установка доверенного сертификата сервера .cer в CryptoPro RUN /opt/cprocsp/bin/amd64/certmgr -install -file ./certificates/public-cert.cer -silent # установка клиентского сертификата .pfx в CryptoPro ARG PRIVATE_CERT_PASS RUN /opt/cprocsp/bin/amd64/certmgr -install -pfx -file ./certificates/private-cert.pfx -pin ${PRIVATE_CERT_PASS} -silent # установка сертификатов из контейнеров в CryptoPro RUN /opt/cprocsp/bin/amd64/csptest -absorb -certs -autoprov WORKDIR /app RUN rm -R /cryptopro RUN sed -i '/\[openssl_init\]/a ssl_conf = ssl_sect' /etc/ssl/openssl.cnf RUN printf "\n[ssl_sect]\nsystem_default = system_default_sect\n" >> /etc/ssl/openssl.cnf RUN printf "\n[system_default_sect]\nMinProtocol = TLSv1.2\nCipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf USER $APP_UID WORKDIR /app EXPOSE 8080 EXPOSE 8081 ENV ASPNETCORE_ENVIRONMENT=Development WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "Cert.Client.Api.dll"]
После выполнения команды установки сертификата .pfx его содержимое импортируется в систему CryptoPro и преобразуется — разбивается на отдельные компоненты, сохраненные в формате с расширением .key:
В основном хранилище ключей keys появляется наш преобразованный .pfx контейнер в формате cert.000 с шестью файлами:
-
primary.key,primary2.key— основные ключи. -
name.key— содержит имя или идентификатор контейнера. -
masks.key,masks2.key— используется для маскировки/шифрования ключей. -
header.key— содержит метаинформацию (заголовок, версию и т. п.).
Поскольку в Linux .pfx сертификат CryptoPro не типизировать в X509Certificate2, то ни вариант с загрузкой сертификата напрямую из структуры проекта, ни вариант с получением из хранилища сертификатов X509Store не сработает — нам не удастся найти целевой .pfx сертификат и, соответственно, не получится выполнить TLS-рукопожатие. Получим следующую ошибку:
InvokeGetRequest Error. System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. ---> System.IO.IOException: Received an unexpected EOF or 0 bytes from the transport stream. at System.Net.Security.SslStream.ReceiveHandshakeFrameAsync[TIOAdapter](CancellationToken cancellationToken) at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](Boolean receiveFirst, Byte[] reAuthenticationData, CancellationToken cancellationToken) at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
В Linux предусмотрена возможность получения сертификатов из специализированного хранилища сертификатов CryptoPro. Для этого необходимо воспользоваться официальным кроссплатформенным решением CryptoPro .NET, доступным по ссылке.
Решение включает в себя четыре NuGet-пакета, которые необходимо подключить к проекту. Это можно сделать одним из следующих способов:
-
Локальное подключение пакетов.
-
Загрузка пакетов в онлайн-репозиторий, например Nexus, Azure DevOps и другие.
В .csproj нужно добавить все эти пакеты:
<PackageReference Include="CryptoPro.Net.Security" Version="2025.4.17" /> <PackageReference Include="CryptoPro.Security.Cryptography" Version="2025.4.17" /> <PackageReference Include="CryptoPro.Security.Cryptography.Pkcs" Version="2025.4.17" /> <PackageReference Include="CryptoPro.Security.Cryptography.Xml" Version="2025.4.17" />
Теперь с помощью библиотек CryptoPro можно получить сертификаты с типом CpX509Certificate2 из хранилища сертификатов CpX509Store. Получим следующий код:
private static CpX509Certificate2? FindCertInCryptoProStore(string thumbprint, StoreName storeName, StoreLocation storeLocation) { using var store = new CpX509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly); return store.Certificates .Find(X509FindType.FindByThumbprint, thumbprint, false) .FirstOrDefault(); } public static IServiceCollection ConfigureApiHttpClient(this IServiceCollection services) { services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService<IOptions<ApiOptions>>().Value; return new ApiCertificates { //Загружаем серверный публичный сертификат (.cer) PublicCert = FindCertInCryptoProStore(options.CerCertificateThumbprint, StoreName.My, StoreLocation.CurrentUser)!, //Загружаем клиентский сертификат с приватным ключом (.pfx) PrivateCert = FindCertInCryptoProStore(options.PfxCertificateThumbprint, StoreName.My, StoreLocation.CurrentUser)! }; }); services.AddHttpClient<IApiHttpClient, ApiHttpClient>() .ConfigurePrimaryHttpMessageHandler((serviceProvider) => { var apiCertificates = serviceProvider.GetRequiredService<ApiCertificates>(); return new CpHttpHandler { SslOptions = new() { ClientCertificates = [apiCertificates.PrivateCert], RemoteCertificateValidationCallback = (_, cert, _, sslPolicyErrors) => { // Проверяем, совпадает ли сертификат с ожидаемым if (cert?.Thumbprint?.Equals(apiCertificates.PublicCert.Thumbprint, StringComparison.OrdinalIgnoreCase) == true) return true; // Если сертификат не совпадает, можно по желанию разрешить стандартную проверку TLS return sslPolicyErrors == SslPolicyErrors.None; } } }; }); return services; }
В данном случае сертификат CryptoPro успешно достается из хранилища сертификатов CryptoPro и применяется в запросах на сервер. Вдобавок это решение работает и для Windows тоже.
Решение с помощью NGINX
Вместо интеграции ГОСТ TLS напрямую в .NET-приложение, можно вынести TLS-обвязку на уровень NGINX. Это снимает с .NET приложения (или приложения на любом другом языке программирования) необходимость подключения CryptoPro-сертификатов и реализации TLS взаимодействия, что упрощает код, повышает кроссплатформенность и масштабируемость решения.
Чтобы это работало с сертификатом CryptoPro необходимо воспользоваться cpnginx — специальной версией NGINX от CryptoPro с поддержкой ГОСТ TLS. Это решение содержится в linux-дистрибутивах, доступных на официальной странице загрузки CryptoPro CSP.
Получаем следующую схему взаимодействия с внешним API:
Таким образом:
-
.NET-приложение посылает обычные HTTP-запросы на cpnginx;
-
cpnginx выступает в роли клиента TLS, обрабатывая криптографию через CryptoPro CSP;
-
cpnginx проксирует запрос на сервер и возвращает ответ приложению.
Для настройки CryptoPro Nginx необходимо создать внешний конфигурационный файл api-tls.conf. Этот конфиг необходимо поместить в директорию /etc/opt/cprocsp/cpnginx/conf.d/api-tls.conf. Все *.conf-файлы из этой директории автоматически подключаются к основному конфигурационному файлу cpnginx.
server { listen 8080; # cpnginx слушает обычный HTTP-порт 8080 #listen 443 sspi; # если нужен https #sspi_certificate 0x7C00013D42201F55870674C594000100013D421; # Сертификат сервера NGINX (если https) server_name localhost; # Имя сервера sspi_protocols TLSv1.2; # Используемый TLS-протокол location / { proxy_pass https://external-api.ru; # API на который проксировать запрос proxy_http_version 1.1; # Используем HTTP/1.1 при проксировании proxy_sspi on; # Включаем использование SSPI - ГОСТ TLS при подключении proxy_ssl_verify on; # Включаем проверку подлинности TLS-сертификата сервера API proxy_ssl_server_name on; # Включаем передачу SNI (Server Name Indication) при TLS proxy_ssl_certificate 0x0552A7B13510B2E8AE4C322F78CAB170BC; # Серийный номер клиентского сертификата .pfx proxy_ssl_trusted_certificate Root; # Указываем хранилище, откуда cpnginx возьмет корневой (доверенный) сертификат для валидации сервера proxy_ssl_verify_local_crl_only on; # Проверка на отзыв сертификата производится только по локальному CRL } }
Для запуска cpnginx приложения в Linux соберем и запустим docker образ:
# Используем базовый образ Debian FROM debian:bullseye # Обновляем систему и устанавливаем необходимые пакеты RUN apt-get update && apt-get install -y \ wget curl unzip gnupg lsb-release sudo \ libcap2-bin ca-certificates systemd libssl1.1 \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Создаем пользователя cpnginx RUN useradd -r -m -d /var/opt/cprocsp/cpnginx -s /bin/bash cpnginx # Копируем дистрибутив CryptoPro в контейнер COPY ./crypto-pro/linux-amd64_deb.tgz /tmp/linux-amd64_deb.tgz # Распаковка и установка дистрибутива CryptoPro с cpnginx WORKDIR /tmp RUN tar -xvzf linux-amd64_deb.tgz && \ cd linux-amd64_deb && \ ./install.sh cprocsp-nginx && \ rm -rf /tmp/linux-amd64_deb* # Копируем сертификаты в контейнер COPY ./certs/private-cert.pfx /certs/private-cert.pfx COPY ./certs/public-cert.cer /certs/public-cert.cer # добавление лицензионного ключа CryptoPro ARG CRYPTO_PRO_LICENSE RUN /opt/cprocsp/sbin/amd64/cpconfig -license -set ${CRYPTO_PRO_LICENSE} #Установка тестового .cer (только открытая часть) в CryptoPro RUN echo o | sudo -u cpnginx /opt/cprocsp/bin/amd64/certmgr -install -store uRoot -file /certs/public-cert.cer ARG PRIVATE_CERT_PASS #Установка production .pfx (экспортированный ключ с закрытой и открытой частью) в CryptoPro RUN sudo -u cpnginx /opt/cprocsp/bin/amd64/certmgr -install -pfx -file /certs/private-cert.pfx -pin ${PRIVATE_CERT_PASS} -silent #Установка сертификатов из контейнеров в CryptoPro RUN sudo -u cpnginx /opt/cprocsp/bin/amd64/csptest -absorb -certs -autoprov # Копируем конфигурацию cpnginx COPY ./config/sspi.conf /etc/opt/cprocsp/cpnginx/conf.d/api-tls.conf # Устанавливаем права доступа к ключам RUN chown -R cpnginx:cpnginx /var/opt/cprocsp/keys/cpnginx && \ chmod -R go= /var/opt/cprocsp/keys/cpnginx # Открываем порты EXPOSE 8080 # Запускаем cpnginx в режиме "foreground" CMD ["/opt/cprocsp/sbin/amd64/cpnginx", "-g", "daemon off;"]
NGINX успешно запускается и принимает входящие HTTP-запросы на http://localhost:8080 (настраивается) и проксирует их на внешний API с использованием CryptoPro TLS. Все запросы и ошибки логируются в /var/log/cpnginx/access.log и /var/log/cpnginx/error.log.
В приложении .NET конфигурация HttpClient упрощается. Остается только указать адрес nginx — куда посылать запросы:
private static IServiceCollection ConfigureApiHttpClient(this IServiceCollection services) { services.AddHttpClient<IApiHttpClient, ApiHttpClient>(httpClient => { httpClient.BaseAddress = new Uri("http://localhost:8080"); //адрес Nginx }); return services; }
Получаем решение, которое обладает следующими преимуществами:
-
Упрощение .NET-приложения — нет необходимости подключать и настраивать ГОСТ TLS в приложении. ‑TLS реализован на nginx.
-
Независимость от языка программирования приложения.
-
Изоляция криптографии — CryptoPro CSP используется только внутри контейнера cpnginx.
-
Легкое масштабирование — cpnginx можно масштабировать независимо от самого приложения.
Как безопасно хранить сертификаты и пароли к ним
При работе с чувствительными данными, особенно такими как.pfx сертификаты с приватным ключом, крайне важно соблюдать правила безопасного хранения. На этапе разработки многие допускают ошибку — хранят сертификаты и пароли в открытом виде прямо в проекте, включая их в git‑репозиторий или конфигурационные файлы. Это недопустимо даже в условиях ограниченного доступа к репозиторию.
Правильным подходом является хранение сертификатов и секретов вне исходного кода. Кроме того, сертификаты не должны включаться в Docker‑образ напрямую. Вместо этого они монтируются во время запуска контейнера через Docker volume или persistent volume в Kubernetes. Пароли к ним считываются в runtime, не попадая в историю образа.
Для исключения чувствительных данных можно воспользоваться следующими безопасными механизмами хранения:
-
Переменные окружения, определенные в CI/CD или на хост‑машине при запуске.
-
Secrets‑хранилища: например, Azure Key Vault, AWS Secrets Manager, HashiCorp Vault.
-
Docker Secrets / Kubernetes Secrets — для защищенной передачи секретов в контейнеры.
Такой подход позволяет обеспечить высокий уровень безопасности на всех этапах жизненного цикла приложения — от сборки до развертывания в production среде.
Заключение
Интеграция с API, использующим сертификаты CryptoPro и ГОСТ TLS, может показаться непростой задачей, особенно в кроссплатформенных проектах. В статье рассмотрено три рабочих варианта: реализацию для.NET‑приложений на Windows и на Linux, а также подход с использованием cpnginx — модифицированного NGINX с поддержкой ГОСТ TLS.
Наиболее универсальным и масштабируемым решением становится cpnginx. Он полностью изолирует криптографическую обвязку от логики приложения, устраняет зависимость от ОС, упрощает сопровождение и снижает риски, связанные с безопасностью. С его помощью.NET‑приложение может работать с API как с обычным HTTP‑сервисом, а все криптографические операции выполняются внутри защищённого контейнера.
Все решения применимы на практике. Выбор подхода зависит от ваших потребностей, ограничений, инфраструктуры и требований безопасности.
ссылка на оригинал статьи https://habr.com/ru/articles/938244/
Добавить комментарий