Всем привет. Представлюсь - меня зовут Евгений Думчев и я 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-сервисом, а все криптографические операции выполняются внутри защищённого контейнера.
Все решения применимы на практике. Выбор подхода зависит от ваших потребностей, ограничений, инфраструктуры и требований безопасности.