Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Взаимодействие с федеральными сервисами. Формирование jwt токена с использованием GOST3410. #49

Closed
AlekseyDev opened this issue Jan 20, 2024 · 7 comments

Comments

@AlekseyDev
Copy link

AlekseyDev commented Jan 20, 2024

День добрый!

Используем Вашу библиотеку, наверное единственное что удалось найти на просторах интернета для подписи с использованием сертификата GOST 3410 в среде Windows. Задача: осуществить взаимодействие с фед. сервисами.
В описании указано:

Требования к формированию токена, подписанного сертификатом ГОСТ Р 34.10-2012
1. Подпись токена (signature) должна быть сформирована в формате pkcs#7. При этом для подписи необходимо оставить только блок SignerInfo и убрать из нее данные сертификата;
2. В заголовке токена (Header) в блок “x5c” не нужно помещать всю цепочку сертификатов так как это влечет за собой превышение лимита длины заголовка запроса.

В итоге получаем при любом раскладе ошибку "GW-059: Неверное значение закодированной формы токена".
Более детальной информации, к сожалению не удается узнать, что не так в формируемом токене.

Окружение:

  • сервис на C# (NetFramework 4.7.2)
  • нугет библиотеки: GostCryptography 2.0.9.
  • Установлен КриптоПРО CSP 4.0.9963

За основу взят пример https://github.com/AlexMAS/GostCryptography/blob/master/Source/GostCryptography.Tests/Pkcs/SignedCmsSignTest.cs

Исходный код (для облегчение и сокращения кода, часть кода убрана, суть остается):

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using GostCryptography.Base;
using GostCryptography.Gost_R3410;

var certStore = new X509Store(StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
var collection = certStore.Certificates;
var searchRes = collection.Find(X509FindType.FindBySerialNumber, serialNumber, false);
var cert = searchRes[0];

var alg = string.Empty;
switch (cert.PublicKey.Oid.Value)
{
case "1.2.643.2.2.19":
alg = "GOST3410-2001";
break;
case "1.2.643.7.1.1.1.1":
alg = "ECGOST3410-2012";
break;
case "1.2.643.7.1.1.1.2":
alg = "ECGOST3410-2012";
break;
}

JwtHeader header = new JwtHeader()
{
{ "alg", alg },
{ "x5c", Convert.ToBase64String(cert.GetRawCertData()) },
};

JwtPayload payload = new JwtPayload()
{
{ "sub", "CD28DB17-8A5E-4DDC-8C0B-0F6C10C9B041" }, /* произвольный гуид */
{ "aud", "http://ya.ru" }, /* произвольный адрес */
{ "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
{ "exp", DateTimeOffset.UtcNow.Add(new TimeSpan(0, 10, 0)).ToUnixTimeSeconds() }
};

JwtSecurityToken token = new JwtSecurityToken(header, payload);
string message = token.EncodedHeader + "." + token.EncodedPayload;

// Создание объекта для подписи сообщения
ContentInfo contentInfo = new ContentInfo(Encoding.UTF8.GetBytes(message));
GostCryptography.Pkcs.GostSignedCms signedCms = new GostCryptography.Pkcs.GostSignedCms(contentInfo);
// Создание объект с информацией о подписчике
CmsSigner cmsSigner = new CmsSigner(cert);
// Включение информации только о конечном сертификате (только для теста)
cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;
// Создание подписи для сообщения CMS/PKCS#7
signedCms.ComputeSignature(cmsSigner);
// Создание сообщения CMS/PKCS#7
byte[] signature = signedCms.Encode();

var encodedSignature = Microsoft.IdentityModel.Tokens.Base64UrlEncoder.Encode(signature);

var jwtToken = $"{message}.{encodedSignature}";

/* в итоге получаем токен из трех составляющих
token.EncodedHeader + "." + token.EncodedPayload + "." + encodedSignature */

И при обращении, при любом раскладе получаем ошибку:
"GW-059: Неверное значение закодированной формы токена".
Что конкретно не так в токене, выяснить не удается.

В описании имеется фраза: "и убрать из нее данные сертификата;" - пробовали делать signedCms.Certificates.Clear(); перед вызовом signedCms.Encode();, не помогает.

Не можем понять что может быть. Может у нас что-то не так недонастроено, или какой-то строки кода не хватает.
Или всё же пытаться выяснять что не так в формате.

Подскажите пожалуйста, что может быть, по какому пути идти ?

@AlexMAS
Copy link
Owner

AlexMAS commented Jan 28, 2024

Здравствуйте!

Я могу ошибаться, но мне кажется, что проблема в формате сообщения, которое подписывается и отправляется (переменная message). Попробуйте посмотреть рабочие примеры интеграции, если доступны. Или запросить описание причин ошибки у вызваемой стороны.

Чтобы убедиться, что на вашей стороне все ОК, попробуйте проверить свою подпись на своей же стороне, как это сделано в тесте. Или с помощью иного иструмента.

@AlekseyDev
Copy link
Author

Спасибо огромное за ответ!

Проверка проходит.

Попробовал сделать message следующим образом, передать в него вместо string сертификата, List сертификатов, т.е.

Вместо:
JwtHeader header = new JwtHeader()
{
{ "alg", alg },
{ "x5c", Convert.ToBase64String(cert.GetRawCertData()) },
};

Теперь передаю:
var arr = new List<string>();
arr.Add(Convert.ToBase64String(cert.GetRawCertData()));'
JwtHeader header = new JwtHeader()
{
{ "alg", alg },
{ "x5c", arr },
};

Токен генерится, обращаюсь к внешней системе, получаю:

400 Request Header Or Cookie Too Large

В документации (от внешней системы) имеется

Подпись токена (signature) должна быть сформирована в формате pkcs#7.
При этом для подписи необходимо оставить только блок SignerInfo
и убрать из нее данные сертификата;

Т.е. остается вопрос, как убрать из нее данные сертификата;.

Воспользовался другой библиотекой (но сразу оговорюсь, она на Net6, и на данный момент нам не подходит), но попробовал на ней.
Библиотека: LibCore (https://github.com/CryptoPro/libcore)

using LibCore.Security.Cryptography.X509Certificates;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Text;

LibCore.Initializer.Initialize();
var certificate = GetCertificate(serialNumber);

var arr = new List<string>();
arr.Add(Convert.ToBase64String(certificate.GetRawCertData()));
JwtHeader header = new JwtHeader()
{
{ "alg", "ECGOST3410-2012" },
{ "x5c", arr }
};

JwtPayload payload = new JwtPayload()
{
{ "sub", "конкретный_гуид" },
{ "aud", "http адрес" },
{ "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
{ "exp", DateTimeOffset.UtcNow.Add(new TimeSpan(0, 10, 0)).ToUnixTimeSeconds() }
};

JwtSecurityToken token = new JwtSecurityToken(header, payload);

string message = token.EncodedHeader + "." + token.EncodedPayload;

var contentInfo = new ContentInfo(Encoding.UTF8.GetBytes(message));
var signedCms = new SignedCms(contentInfo, true);
CmsSigner cmsSigner = new CmsSigner(certificate)
{
IncludeOption = X509IncludeOption.EndCertOnly,
};

signedCms.ComputeSignature(cmsSigner);
signedCms.RemoveCertificate(certificate);
var signature = signedCms.Encode();

var encodedSignature = Microsoft.IdentityModel.Tokens.Base64UrlEncoder.Encode(signature);
var jwtToken = $"{message}.{encodedSignature}";

При данном подходе, токен отработал и Успешно осуществился вызов API внешнего сервиса!
Но, повторюсь, данная библиотека нам не подходит.

И как раз тут имеется:
signedCms.RemoveCertificate(certificate);

Просьба, вопрос, получится ли добавить к библиотеке GostCryptography
К классу: GostCryptography.Pkcs.GostSignedCms
метод RemoveCertificate(certificate);

т.е. чтоб можно было бы вызвать аналогично:
signedCms.RemoveCertificate(certificate);

Огромная просьба, пожалуйста, сообщите, реально ли, получится ли это сделать?

@AlexMAS
Copy link
Owner

AlexMAS commented Jan 29, 2024

Метод удаления сертификата SignedCms.RemoveCertificate() имеется только в .NET/.NET Core, но отсутствует в .NET Framework. В последних версиях реализация многих классов переработана так, что многие операции осущетсвляются самим .NET в managed-коде, в то время, как .NET Framework в большинстве своем использует Windows Crypto API. Насколько я понимаю, в этом причина, по которой аналогичная функциональность отсутствует в .NET Framework. В Windows Crypto API сертификат можно удалить только по его индексу в структуре сообщения. Чтобы в будущем не ломать обратную совместимость я добавил метод GostSignedCms.RemoveCertificates(). Эту операцию можно реализовать как в старом .NET Framework, так и в новом. Данный метод удалят из сообщения все сертификаты.

Я пока не вливал ветку, т.к. у меня сейчас нет возможности протестировать данную функциональность. Если у вас есть возможность, переключитесь на ветку remove-certs-from-signedcms и попробуйте повторить эксперимент. Дадите обратную связь, вольем изменения/выпустим новую версию или попробуем доработать метод. ;)

@AlekseyDev
Copy link
Author

Скачал исходники. Собрал, подключил напрямую библиотеку, но не запустился.

Следующая ошибка:

Attempt by method 'GostCryptography.Reflection.SignedCmsHelper.RemoveCertificates(System.Security.Cryptography.Pkcs.SignedCms)' to access method 'GostCryptography.Reflection.SignedCmsHelper.GetMessageHandle(System.Security.Cryptography.Pkcs.SignedCms)' failed.

@AlexMAS
Copy link
Owner

AlexMAS commented Jan 30, 2024

Спасибо. Попробуйте обновиться на последний коммит в ветке remove-certs-from-signedcms и повторить попытку.

@AlekseyDev
Copy link
Author

Спасибо огромное! Всё заработало!

@AlexMAS
Copy link
Owner

AlexMAS commented Jan 31, 2024

Отлично!

Выпустил версию 2.0.10.

В рамках версии добавлены два метода:

  • GostSignedCms.RemoveCertificate(X509Certificate2 certificate)
  • GostSignedCms.RemoveCertificates()

@AlexMAS AlexMAS closed this as completed Jan 31, 2024
AlexMAS added a commit that referenced this issue Jun 12, 2024
#49

Add needed security attributes

Add unit test for SignedCms.RemoveCertificates()

v2.0.10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants