Подпись строки при помощи сертификата и флешки с Рутокен 3.0 ЭЦП на C#

Приветствую всех!
Стоит задача подписать строку аналогично тому, как реализовано в онлайн-инструменте РуТокен по подписанию через плагин в браузере (оно точно подписывает верно, мы проверяли в ручном режиме, взяв имеющийся сертификат, флешку с Рутокен 3.0 ЭЦП и подписав строку). Имеется сертификат и флешка Рутокен 3.0 ЭЦП. Пытаюсь разобраться, как воспользоваться (и возможно ли оно в принципе) SDK RuToken, чтобы подписать PKCS.7 входящую строку в коде на C# (версия .NET-фреймворка 8). Либо, возможно, есть альтернативные пути.
Заранее благодарю за ответы.
С уважением,
Александр.

Re: Подпись строки при помощи сертификата и флешки с Рутокен 3.0 ЭЦП на C#

Добрый день!
В репозитории компании на github есть нужные вам примеры: https://github.com/AktivCo/RutokenPkcs1 … 012-256.cs

Re: Подпись строки при помощи сертификата и флешки с Рутокен 3.0 ЭЦП на C#

Евгений Мироненко пишет:

Добрый день!
В репозитории компании на github есть нужные вам примеры: https://github.com/AktivCo/RutokenPkcs1 … 012-256.cs

Уважаемый Евгений, огромное спасибо за ссылку и информацию! Обязательно изучу и попробую применить.
С уважением,
Александр

Re: Подпись строки при помощи сертификата и флешки с Рутокен 3.0 ЭЦП на C#

Евгений, приветствую!

Ваш пример очень сильно помог в разработке, действительно удалось подключить библиотеки и подписать текст сертификатом с флешки.
Но при выполнении кода мы столкнулись с другим моментом, мешающим нам полноценно использовать полученную подпись.

Подпись не принимает сторонняя система, с которой мы интегрируемся, причём, строка, подписанная в форме на сайте https://ra.rutoken.ru/devices/signature, принимается  (после удаления переноса строк).

Код формирования подписи представлен ниже:
   

private const uint PKCS7_DETACHED_SIGNATURE = 0x01;

private const string RutokenEcpDllDefaultPath = "rtpkcs11ecp.dll";

public static byte[]? Sign(byte[] array, string pin)
{
    // Шаблон для поиска закрытого ключа ГОСТ Р 34.10-2012 (256 бит).
    var privateKeyAttributes = new List<ObjectAttribute>
        {
            // Объект закрытого ключа.
            new(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY),
            // Закрытый ключ является объектом токена.
            new(CKA.CKA_TOKEN, true),
        };

    // Шаблон для поиска сертификата ключа подписи.
    var certificateAttributes = new List<ObjectAttribute>
        {
            // Объект сертификата.
            new(CKA.CKA_CLASS, CKO.CKO_CERTIFICATE),
            // Сертификат является объектом токена.
            new(CKA.CKA_TOKEN, true),
            // Тип сертификата - X.509
            new(CKA.CKA_CERTIFICATE_TYPE, CKC.CKC_X_509),
        };

    // Инициализировать библиотеку.
    Console.WriteLine("Library initialization.");
    using var pkcs11 = new Pkcs11(RutokenEcpDllDefaultPath, AppType.MultiThreaded);

    // Получить доступный слот.
    Console.WriteLine("Checking tokens available.");
    var slot = Helpers.GetUsableSlot(pkcs11);

    // Открыть RW сессию в первом доступном слоте.
    Console.WriteLine("Opening RW session.");
    using var session = slot.OpenSession(SessionType.ReadWrite);

    // Выполнить аутентификацию Пользователя.
    Console.WriteLine("User authentication.");
    session.Login(CKU.CKU_USER, pin);

    try
    {
        // Формирование подписи.
        Console.WriteLine("Signing...");

        // Поиск закрытого ключа на токене.
        Console.WriteLine(" Getting private key...");
        var privateKeys = session.FindAllObjects(privateKeyAttributes);

        // Поиск сертификата на токене.
        Console.WriteLine(" Getting certificate...");
        var certificates = session.FindAllObjects(certificateAttributes);

        // Подпись данных.
        var signature = session.PKCS7Sign(array,
            certificates[0], privateKeys[0], null, PKCS7_DETACHED_SIGNATURE);
        return signature;
    }
    finally
    {
        // Сбросить права доступа как в случае исключения,
        // так и в случае успеха.
        // Сессия закрывается автоматически.
        session.Logout();
    }
} 

В результате выполнения метода

var accessTokenAsBytes = Encoding.UTF8.GetBytes(accessToken);
var signatureAsBytes = Sign(accessTokenAsBytes, pin);

мы получаем base64-строку с подписью, подпись декодируется через КриптоПро CSP.
Мы предполагаем, что проблема с исходными данными или кодированием в byte[] для session.PKCS7Sign().

Прошу Вас подсказать, если есть такая возможность, как вызывать метод PKCS7Sign() таким образом, чтобы результат подписи получался такой же, как при вызове формы на сайте.

Re: Подпись строки при помощи сертификата и флешки с Рутокен 3.0 ЭЦП на C#

Добрый день!

Вас не затруднит предоставить следующие данные?

1. Подписываемые данные
2. Значение подписи, принимаемое сторонней системой
3. Значение подписи, получаемое в результате выполнения вашего кода на C#.

Обычно мы в таких случаях смотрим на структуру PKCS7-сообщений с помощью ASN.1-парсера (например, https://lapo.it/asn1js/), сопоставляем элементы структуры с RFC5652 (https://datatracker.ietf.org/doc/html/rfc5652) и сравниваем в принимаемой и отклоняемой подписи:
* используемые алгоритмы хеширования и подписи,
* значения хеша от данных,
* состав подписанных атрибутов --
и, таким образом, выявляем различия. Как минимум, часть различий может быть устранима правильным применением функции PKCS7Sign.

Re: Подпись строки при помощи сертификата и флешки с Рутокен 3.0 ЭЦП на C#

Евгений Мироненко пишет:

Добрый день!

Вас не затруднит предоставить следующие данные?

1. Подписываемые данные
2. Значение подписи, принимаемое сторонней системой
3. Значение подписи, получаемое в результате выполнения вашего кода на C#.

Обычно мы в таких случаях смотрим на структуру PKCS7-сообщений с помощью ASN.1-парсера (например, https://lapo.it/asn1js/), сопоставляем элементы структуры с RFC5652 (https://datatracker.ietf.org/doc/html/rfc5652) и сравниваем в принимаемой и отклоняемой подписи:
* используемые алгоритмы хеширования и подписи,
* значения хеша от данных,
* состав подписанных атрибутов --
и, таким образом, выявляем различия. Как минимум, часть различий может быть устранима правильным применением функции PKCS7Sign.

Евгений, приветствую!

Выяснилось, что проблема была в другом параметре, а не в самом алгоритме поиска. В любом случае, премного благодарим Вас за помощь!