Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр

Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр Электронная цифровая подпись

Основной этап

Основной этап можно разделить на работу со слотами и работу внутри сессии

Этап работы со слотами

Слот — это дескриптор виртуального интерфейса, куда подключен токен. Конкретно в нашем примере весь этап работы со слотами уместился в определение одной функции get_slot_list:

int get_slot_list(CK_SLOT_ID_PTR* slots_ptr, CK_ULONG_PTR slotCount)
{
    CK_RV rv;
    int errorCode = 1;

    /*************************************************************************
    * Получить количество слотов c подключенными токенами                    *
    *************************************************************************/
    rv = functionList->C_GetSlotList(CK_TRUE, NULL_PTR, slotCount);
    CHECK_AND_LOG(" C_GetSlotList (number of slots)", rv == CKR_OK, rvToStr(rv), exit);

    CHECK_AND_LOG(" Checking available tokens", *slotCount > 0, " No tokens available", exit);

    /*************************************************************************
    * Получить список слотов c подключенными токенами                        *
    *************************************************************************/
    *slots_ptr = (CK_SLOT_ID_PTR)malloc(*slotCount * sizeof(CK_SLOT_ID));
    CHECK(" Memory allocation for slots", *slots_ptr != NULL_PTR, exit);

    rv = functionList->C_GetSlotList(CK_TRUE, *slots_ptr, slotCount);
    CHECK_AND_LOG(" C_GetSlotList", rv == CKR_OK, rvToStr(rv), free_slots);
    printf(" Slots available: %dn", (int)*slotCount);

    /*************************************************************************
    * Выставить признак успешного завершения программы                       *
    *************************************************************************/
    errorCode = 0;

free_slots:
    if (errorCode)
    {
        free(*slots_ptr);
    }

exit:
    return errorCode;
}

Работа со слотами происходит примерно в такой последовательности:

  1. Получение списка слотов с помощью функции C_GetSlotList или через функцию ожидания событий, связанных со слотами (пример будет рассмотрен ниже).

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

Этап работы внутри сессии

Сессия — это дескриптор контекста выполнения последовательности операций. Обычно во время сессий выполняются основные функции работы с токеном или смарт-картой: шифрование, подпись и т.п. В нашем случае выполняется смена PIN-кода:

Rutokens и ubuntu 19.04 64bit : рб-софт

Задача: с помощью RutokenS зайти на портал Госуслуг в Ubuntu 19.04

Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр
Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр

Итак, приступаем: 

1. Устанавливаем FireFox 

sudo apt install firefox

2. Добавляем поддержку lsb и alien — понадобится для установки ЭЦП браузер плагин (CadesPlugin).

sudo apt install lsb lsb-core alien
sudo apt install libgtk2.0-0

3. Скачиваем свежий КриптоПро CSP 5 c https://ecpexpert.ru/products/csp/downloads#latest_csp50_linux
4. Скачиваем свежий плагин с https://www.ecpexpert.ru/products/cades/plugin/get_2_0
5. Распаковываем КриптоПро и плагин, заходим в папку с КриптоПро и запускаем установщик:

sudo ./install.sh

Убеждаемся что ошибок нет, и все зависимости удовлетворены. Если чего-то не хватает, всё есть в стандартных репах,
устанавливаем нужные пакеты, и заново. Пока не пройдет без ошибок.
 
Устанавливаем доп. пакеты для работы с ЭЦП:

sudo dpkg -i cprocsp-rdr-gui-gtk-64_5.0.11453-5_amd64.deb 
#Поддержка алгоритмов класса1 и 2
sudo dpkg -i lsb-cprocsp-kc1-64_5.0.11453-5_amd64.deb
sudo dpkg -i lsb-cprocsp-kc2-64_5.0.11453-5_amd64.deb
#Обязательно установить библиотеки pkcs11
sudo dpkg -i lsb-cprocsp-pkcs11-64_5.0.11453-5_amd64.deb
sudo dpkg -i cprocsp-rsa-64_5.0.11453-5_amd64.deb
sudo dpkg -i cprocsp-rdr-pcsc-64_5.0.11453-5_amd64.deb
sudo dpkg -i cprocsp-rdr-rutoken-64_5.0.11453-5_amd64.deb
#Устанавливаем
sudo dpkg -i ifd-rutokens_1.0.4_amd64.deb

Потребует так же установить зависимости pcsc и libusb ставим из стандартных репозиториев.
 

sudo apt update
sudo apt install libccid pcscd libpcsclite1 pcsc-tools opensc
sudo apt install libgtk2.0-0
sudo apt install pcscd
sudo apt install pcsc-tools
sudo apt install motif*

Устанавливаем CadesPlugin с помощью alien

sudo alien -kci cprocsp-pki-2.0.0-amd64-cades.rpm
sudo alien -kci cprocsp-pki-2.0.0-amd64-plugin.rpm

Так как с первого раза не все файлы могут скопироваться, например не копируется /opt/cprocsp/lib/amd64/libnpcades.so , то повторяем

sudo alien -kci cprocsp-pki-2.0.0-amd64-plugin.rpm

Делаем ссылку на установленный плагин

sudo ln -s /opt/cprocsp/lib/amd64/libnpcades.so /usr/lib/firefox-addons/plugins/libnpcades.so

И ЭТО БЫЛ ОЧЕНЬ ВАЖНЫЙ МОМЕНТ!!!!

Скачиваем СВЕЖИЙ плагин IFCPlugin для работы с порталами с ЕСИА(Госуслуги) с https://ds-plugin.gosuslugi.ru/plugin/upload/Index.spr и устанавливаем:

sudo dpkg -i IFCPlugin-x86_64.deb

Если во время выполнения этой команды система предложить понизить версию установленного плагина:

Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр

то не вздумайте это делать!!! Если предложит обновить, то ставим самый свежий плагин!!!

После успешной установки заменяем дефолтный файл /etc/ifc.cfg на

log = {
    level = "DEBUG";
 }
 config = {
 cert_from_registry = "false";
 set_user_pin = "true";
 }
 params =
 (
   { name  = "Криптопровайдер VipNet CSP";
     alias = "VIPNet";
     type  = "capi";
     provider_name = "Infotecs Cryptographic Service Provider";
     provider_num  = "2";
 skip_pkcs11_list = "true";
   },
   { name  = "Криптопровайдер VipNet CSP Linux";
     alias = "VIPNet_linux";
     type  = "capi_linux";
     provider_name = "Infotecs Cryptographic Service Provider";
     provider_num  = "2";
     skip_pkcs11_list = "true";
   },
   { name  = "Криптопровайдер КриптоПро CSP";
     alias = "CryptoPro";
     type  = "capi";
     provider_name = "Crypto-Pro GOST R 34.10-2001 Cryptographic Service Provider";
     provider_num  = "75";
 skip_pkcs11_list = "false";
   },
   { name  = "Криптопровайдер Рутокен CSP";
     alias = "CryptoPro_Rutoken";
     type  = "capi";
     provider_name = "GOST R 34.10-2001 Rutoken CSP";
     provider_num  = "75";
 skip_pkcs11_list = "false";
   },
   { name  = "Криптопровайдер Signal-COM CSP";
     alias = "SignalCom";
     type  = "capi";
     provider_name = "Signal-COM CPGOST Cryptographic Provider";
     provider_num  = "75";
 skip_pkcs11_list = "false";
   },
   { name  = "Криптопровайдер  LISSI-CSP";
     alias = "LISSI-CSP";
     type  = "capi";
     provider_name = "LISSI-CSP";
     provider_num  = "75";
 skip_pkcs11_list = "false";
   },
   { name  = "Токен JaCarta";
     alias = "JaCarta";
     type  = "pkcs11";
 alg   = "gost2001";
 model = "eToken GOST,JaCarta GOST 2.0";
     lib_win   = "jcPKCS11-2.DLL";
     lib_linux = "libjcPKCS11-2.so.2.4.0";
     lib_mac   = "jcPKCS11-2";
   },
   { name  = "Рутокен ЭЦП";
     alias = "ruTokenECP";
     type  = "pkcs11";
 alg   = "gost2001";
 model = "Rutoken ECP";
     lib_win   = "rtpkcs11ecp.dll";
     lib_linux = "librtpkcs11ecp.so";
     lib_mac   = "librtpkcs11ecp.dylib";
   },
  {
         name = "CPPKCS11_2001";
         alias = "CPPKCS11_2001";
         type = "pkcs11";
         alg = "gost2001";
         model = "CPPKCS 3";
         lib_linux = "/opt/cprocsp/lib/amd64/libcppkcs11.so";
  },
  {
         name = "CPPKCS11_2021_256";
         alias = "CPPKCS11_2021_256";
         type = "pkcs11";
         alg = "gost2021_256";
         model = "CPPKCS 3";
         lib_linux = "/opt/cprocsp/lib/amd64/libcppkcs11.so";
  },
  {
         name = "CPPKCS11_2021_512";
         alias = "CPPKCS11_2021_512";
         type = "pkcs11";
         alg = "gost2021_512";
         model = "CPPKCS 3";
         lib_linux = "/opt/cprocsp/lib/amd64/libcppkcs11.so";
  }
 );

Обязательно делаем символьную ссылку на библиотеку pkcs11 из пакета CryptoPro для использования IFCPlugin, если до этого её еще не сделали

sudo ln -s /opt/cprocsp/lib/amd64/libcppkcs11.so.4.0.4 /usr/lib/mozilla/plugins/lib/libcppkcs11.so

Всё! Установку завершили по сути. Осталось теперь при первом входе на порталы установить расширения для CadeslPlugin и (Firefox сам предложит его установить при первом входе на портал где он используется)
Далее, для работы необходимо установить в CryptoPro по порядку:
— корневой сертификат
— промежуточные сертификаты
— личный с ссылкой на контейнер с закрытым ключом:
Установку личного сертификата можно производим командой

/opt/cprocsp/bin/amd64/csptestf -absorb -certs 

Если, по какой либо причине, необходимо удалить все установленные сертификаты из личного хранилища можно командой

/opt/cprocsp/bin/amd64/certmgr –delete –storeumy -all

Далее нужно убедиться что CryptoPro видит наш контейнер с закрытым ключом, обратите внимание что контейнер должен быть доступен обычному пользователю, под которым вы работаете в системе:

/opt/cprocsp/bin/amd64/csptest -keyset -enum_cont -verifycontext -fqcn

примерный положительный вывод:

Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр

Если в контейнере с ключами хранятся связанные сертификаты например на rutoken s, то можно одной командой установить всю цепочку сертификатов вместе с ссылкой на данный ключевой контейнер:

/opt/cprocsp/bin/amd64/certmgr -inst -cont `.Aktiv Co. Rutoken S 00 00xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`;

Этот xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx берем из предыдущей команды (видно на скрирншоте)  

Проверить что сертификат установлен и имеется ссылка на закрытый ключ (PrivateKey Link: Yes):  

/opt/cprocsp/bin/amd64/certmgr -list -store umy

Примерный вывод:

Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр

При установке всех компонентов обращаем внимание на то, чтобы ВСЕ компоненты были самыми свежими!

Для проверки рутокена заходим на: https://www.ecpexpert.ru/sites/default/files/products/cades/demopage/simple.html#

Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр

Итого на дружбу рутокена и ubuntu ушло час делов с фотографированием процесса установки.

Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр

После всех манипуляций заходим на портал госуслуг без проблем с помощью рутокена.

Настройка и диагностика криптопро csp

Проверим, видит ли криптографический провайдер наш токен и другие доступные типы носителей следующими командами:

/opt/cprocsp/bin/amd64/csptest -card -enum -v –v

/opt/cprocsp/bin/amd64/csptest -enum -info -type PP_ENUMREADERS | iconv -f cp1251

/opt/cprocsp/sbin/amd64/cpconfig -hardware reader -view -f cp1251

Aladdin R.D. JaCarta [SCR Interface] (000000000000) 00 00 — это наш носитель.

Следуя инструкции КриптоПро CSP для Linux. Настройка, выполняем его регистрацию в криптографическом провайдере:

/opt/cprocsp/sbin/amd64/cpconfig -hardware reader -add "Aladdin R.D. JaCarta [SCR Interface] (000000000000) 00 00"

Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр

В результате выполнения в конфигурационный файл /etc/opt/cprocsp/config64.iniв раздел [KeyDevicesPCSC] будет добавлена запись:

[KeyDevicesPCSC”Aladdin R.D. JaCarta [SCR Interface] (000000000000) 00 00″Default]

Читайте также:  Эп документа не является усовершенствованной суфд | Юр-Мастер

Чтобы выполнить требования Формуляра, Правил пользования и Руководства администратора безопасности КриптоПро CSP:

Использование СКЗИ «КриптоПро CSP» версии 4.0 с выключенным режимом усиленного контроля использования ключей не допускается. Включение данного режима описано в документах ЖТЯИ.00087-01 91 02. Руководство администратора безопасности.

Необходимо включить режим усиленного контроля использования ключей:

/opt/cprocsp/sbin/amd64/cpconfig -ini 'configparameters' -add long StrengthenedKeyUsageControl 1

Проверяем, что режим включен:

cat /etc/opt/cprocsp/config64.ini | grep StrengthenedKeyUsageControl

Выполняем перезапуск службы криптографического провайдера:

/etc/init.d/cprocsp restart
/etc/init.d/cprocsp status

После перезапуска проверяем, что ошибок в работе провайдера с ключевыми носителями нет:

/opt/cprocsp/bin/amd64/csptest -keyset –verifycontext

/opt/cprocsp/bin/amd64/csptest -keyset -verifycontext -enum –unique

CSP (Type:80) v4.0.9017 KC2 Release Ver:4.0.9944 OS:Linux CPU:AMD64 FastCode:REA
AcquireContext: OK. HCRYPTPROV: 16052291
alfa_shark1                        |SCARDJACARTA_4E3900154029304CCC00E9F6
OK.
Total: SYS: 0.000 sec USR: 0.000 sec UTC: 4.560 sec
[ErrorCode: 0x00000000]

Подготовительный этап

Мы реализовали его внутри функцииinit_pkcs11:

#include "utils.h"

CK_FUNCTION_LIST_PTR functionList;                 // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LIST
CK_FUNCTION_LIST_EXTENDED_PTR functionListEx;      // Указатель на список функций расширения PKCS#11, хранящийся в структуре CK_FUNCTION_LIST_EXTENDED
static HMODULE module;

int init_pkcs11()
{
    CK_C_GetFunctionList getFunctionList;              // Указатель на функцию C_GetFunctionList
    CK_C_EX_GetFunctionListExtended getFunctionListEx; // Указатель на функцию C_EX_GetFunctionListExtended

    /* Параметры для инициализации библиотеки: разрешаем использовать объекты синхронизации операционной системы */
    CK_C_INITIALIZE_ARGS initArgs = { NULL_PTR, NULL_PTR, NULL_PTR, NULL_PTR, CKF_OS_LOCKING_OK, NULL_PTR };

    CK_RV rv;                      // Код возврата PKCS#11 функций
    int errorCode = 1;                                 // Флаг ошибки

    /*************************************************************************
    * Выполнить действия для начала работы с библиотекой PKCS#11             *
    *************************************************************************/
    printf("Initialization...n");

    /*************************************************************************
    * Загрузить библиотеку                                                   *
    *************************************************************************/
    module = LoadLibrary(PKCS11_LIBRARY_DIR "/" PKCS11ECP_LIBRARY_NAME);
    CHECK(" LoadLibrary", module != NULL, exit);

    /*************************************************************************
    * Получить адрес функции запроса структуры с указателями на функции      *
    *************************************************************************/
    getFunctionList = (CK_C_GetFunctionList)GetProcAddress(module, "C_GetFunctionList");
    CHECK(" GetProcAddress (C_GetFunctionList)", getFunctionList != NULL, unload_pkcs11);

    /*************************************************************************
    * Получить адрес функции запроса структуры с указателями на функции      *
    * расширения стандарта PKCS#11                                           *
    *************************************************************************/
    getFunctionListEx = (CK_C_EX_GetFunctionListExtended)GetProcAddress(module, "C_EX_GetFunctionListExtended");
    CHECK(" GetProcAddress (C_EX_GetFunctionListExtended)", getFunctionList != NULL, unload_pkcs11);

    /*************************************************************************
    * Получить структуру с указателями на функции                            *
    *************************************************************************/
    rv = getFunctionList(&functionList);
    CHECK_AND_LOG(" Get function list", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

    /*************************************************************************
    * Получить структуру с указателями на функции расширения стандарта       *
    *************************************************************************/
    rv = getFunctionListEx(&functionListEx);
    CHECK_AND_LOG(" Get function list extended", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

    /*************************************************************************
    * Инициализировать библиотеку                                            *
    *************************************************************************/
    rv = functionList->C_Initialize(&initArgs);
    CHECK_AND_LOG(" C_Initialize", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

    errorCode = 0;

    /*************************************************************************
    * Выгрузить библиотеку из памяти                                         *
    *************************************************************************/
unload_pkcs11:
    if (errorCode)
        CHECK_RELEASE(" FreeLibrary", FreeLibrary(module), errorCode);
exit:
    return errorCode;
}

Здесь происходит следующее:

  1. В память процесса подгружается PKCS#11-библиотека, хранящаяся по пути PKCS11ECP_LIBRARY_NAME, с помощью функции LoadLibrary (стандартная функция для Windows-систем, для Linux-систем определена обертка).

  2. Далее из библиотеки вытаскиваются указатели на функции C_GetFunctionList и C_EX_GetFunctionListExtended. Первая функция определена в стандарте PKCS#11 и позволяет получить структуру указателей на функции библиотеки. Вторая — является специфичной для библиотеки rtpkcs11ecp и позволяет получить схожую структуру указателей на функции расширения библиотеки. О функциях расширения мы поговорим позже.

  3. Потом мы вызываем полученную функцию C_GetFunctionList и получаем уже саму структуру указателей на функции.

  4. С помощью функции C_Initialize инициализируется загруженная библиотека. Функция C_Initialize в качестве аргумента принимает параметры инициализации библиотеки. Подробнее о них можно почитать здесь, для нас же важен флаг CKF_OS_LOCKING_OK. Его необходимо использовать, если мы хотим использовать библиотеку в нескольких потоках. В нашем примере мы могли бы опустить этот флаг.

Поиск объектов и создание сырой подписи

В прошлом разделе мы сгенерировали ключевую пару. На этот раз будем считать, что у нас нет хендлов на сгенерированные ключи, но мы знаем их идентификатор – CKA_ID. Попробуем найти объект закрытого ключа на токене:

int findObjects(CK_SESSION_HANDLE session,         // Хэндл открытой сессии
                CK_ATTRIBUTE_PTR attributes,       // Массив с шаблоном для поиска
                CK_ULONG attrCount,                // Количество атрибутов в массиве поиска
                CK_OBJECT_HANDLE objects[],        // Массив для записи найденных объектов
                CK_ULONG* objectsCount             // Количество найденных объектов
                       )
{
    CK_RV rv;                                           // Код возврата. Могут быть возвращены только ошибки, определенные в PKCS#11
    int errorCode = 1;                                  // Флаг ошибки

    /*************************************************************************
    * Инициализировать операцию поиска                                       *
    *************************************************************************/
    rv = functionList->C_FindObjectsInit(session, attributes, attrCount);
    CHECK_AND_LOG("  C_FindObjectsInit", rv == CKR_OK, rvToStr(rv), exit);

    /*************************************************************************
    * Найти все объекты, соответствующие критериям поиска                    *
    *************************************************************************/

    rv = functionList->C_FindObjects(session, objects, *objectsCount, objectsCount);
    CHECK_AND_LOG("  C_FindObjects", rv == CKR_OK, rvToStr(rv), find_final);

    errorCode = 0;

    /*************************************************************************
    * Деинициализировать операцию поиска                                     *
    *************************************************************************/
find_final:
    rv = functionList->C_FindObjectsFinal(session);
    CHECK_RELEASE_AND_LOG("  C_FindObjectsFinal", rv == CKR_OK, rvToStr(rv), errorCode);

exit:
    return errorCode;
}

int find_private_key(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE_PTR privateKey)
{
        CK_BYTE keyPairIdGost2021_256[] = { "GOST R 34.10-2021 (256 bits) sample key pair ID (Aktiv Co.)" };
        CK_OBJECT_CLASS privateKeyObject = CKO_PRIVATE_KEY;

        CK_ATTRIBUTE privateKeyTemplate[] =
        {
                { CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject)},              // Класс - закрытый ключ
                { CKA_ID, &keyPairIdGost2021_256, sizeof(keyPairIdGost2021_256) - 1},   // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)
        };

        CK_ULONG cnt = 1;

        CK_RV rv;
        int errorCode = 1;

        rv = findObjects(session, privateKeyTemplate,
        arraysize(privateKeyTemplate), privateKey, &cnt);

        CHECK(" findObjects", rv == 0, exit);
        CHECK_AND_LOG(" Checking number of keys found", cnt == 1, "No objects foundn", exit);

        errorCode = 0;
exit:
        return errorCode;
}

Данный пример иллюстрирует работу с функцией поиска объекта по заданным атрибутам. Как можно заметить, операция поиска объекта на токене является составной и работа с ней сводится как минимум к вызову трёх функций: C_FindObjectsInit, C_FindObjects, C_FindObjectsFinal.

Функция C_FindObjects может вызываться по несколько раз, и каждый раз она будет возвращать следующие объекты поиска. Предпоследний аргумент функции C_FindObjects задаёт размер выходного массива объектов. А последний — количество полученных объектов после очередного поиска.

Поиск приватного ключа производился по атрибуту его класса и идентификатору. Мы рассчитывали, что найдётся хотя бы один объект по заданному шаблону и брали любой из них. Используем найденный ключ для вычисления сырой подписи:

int sign(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE privateKey)
{    
    /* OID алгоритма хеширования ГОСТ Р 34.11-2021(256)                     */
    CK_BYTE parametersGostR3411_256[] = {0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02};

    /* Механизм подписи/проверки подписи по алгоритму ГОСТ Р 34.10-2021(256) и хешированием по алгоритму ГОСТ Р 34.11-2021(256) */
    CK_MECHANISM gost3410SignWith3411Mech = { CKM_GOSTR3410_WITH_GOSTR3411_12_256, ¶metersGostR3411_256, sizeof(parametersGostR3411_256)};

    CK_BYTE data[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
               0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
               0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
               0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };

    CK_BYTE_PTR signature;                            // Указатель на буфер, содержащий цифровую подпись для данных
    CK_ULONG signatureSize;                           // Размер буфера, содержащего цифровую подпись для данных, в байтах

    CK_RV rv;
    int errorCode = 1;

    /*************************************************************************
    * Вычислить подпись от данных                                            *
    *************************************************************************/
    printf(" Signing data...n");

    /*************************************************************************
    * Инициализировать операцию подписи данных                               *
    *************************************************************************/
    rv = functionList->C_SignInit(session, &gost3410SignWith3411Mech, privateKey);
    CHECK_AND_LOG("  >C_SignInit", rv == CKR_OK, rvToStr(rv), exit);

    /*************************************************************************
    * Определить размер данных подписи                                       *
    *************************************************************************/
    rv = functionList->C_Sign(session, data, sizeof(data), NULL_PTR, &signatureSize);
    CHECK_AND_LOG("  C_Sign(get size)", rv == CKR_OK, rvToStr(rv), exit);

    /*************************************************************************
    * Подписать данные                                                       *
    *************************************************************************/

    signature = (CK_BYTE*)malloc(signatureSize * sizeof(CK_BYTE));
    CHECK("  Memory allocation for signature", signature != NULL, exit);

    rv = functionList->C_Sign(session, data, sizeof(data), signature, &signatureSize);
    CHECK_AND_LOG("  C_Sign (signing)", rv == CKR_OK, rvToStr(rv), free_signature);


    /*************************************************************************
    * Распечатать буфер, содержащий подпись                                  *
    *************************************************************************/
    printf("  Signature buffer is: n");
    printHex(signature, signatureSize);
    printf("Data has been signed successfully.n");

    errorCode = 0;

free_signature:
    free(signature);
exit:
    return errorCode;
}

В этом примере подпись и хеш можно считать одновременно. Такой вариант рекомендован для безопасности: цепочку “хеширование-подпись” лучше не «разрывать». Чтобы показать, какой алгоритм хеширования использовать, мы передали его OID.

Также имеется возможность считать сырую подпись в два этапа: сначала брать хеш от данных, а затем вычислялась подпись от хеша. Такой подход более модульный, т.к. алгоритмы хеширования и вычисления подписи могут быть любыми и их можно комбинировать. Естественно, комбинировать можно с некоторыми ограничениями, которые налагаются стандартами, например, на длину хеша.

Читайте также:  Как выглядит электронная цифровая подпись (ЭЦП) - образец

Причина 3

UPD 16.04.2021:

В процессе настройки среды и оборудования выяснилось, что носитель, первым оказавшийся в распоряжении, был вовсе не JaCarta PKI Nano, как ожидалось, а устройство работающее в режиме SafeNet Authentication Client eToken PRO.

UPD 16.04.2021: Некогда Банку требовалось устройство, которое могло бы работать в той же инфраструктуре, что и eToken PRO (Java). В качестве такого устройства компания “ЗАО Аладдин Р.Д.” предложила токен JaCarta PRO, который был выбран банком.

UPD 16.04.2021: Благодарю компанию Аладдин Р.Д., за то что помогли разобраться и установить истину.

В этой ошибке нет никаких политических и скрытых смыслов, а только техническая ошибка сотрудника при подготовке документов. Токен JaCarta PRO является продуктом компании ЗАО “Аладдин Р.Д.”. Апплет, выполняющий функциональную часть, разработан компанией “ЗАО Аладдин Р.Д”.

Этот eToken PRO относился к партии, выпущенной до 1 декабря 2021 года. После этой даты компания «Аладдин Р.Д.» прекратила продажу устройств eToken PRO (Java).

Забегая немного вперед, нужно сказать, что работа с ним настраивалась через соответствующие драйверы — SafenetAuthenticationClient-10.0.32-0.x86_64, которые можно получить только в поддержке Аладдин Р.Д. по отдельной online заявке.

В КриптоПро CSP для работы с этим токеном требовалось установить пакет cprocsp-rdr-emv-64 | EMV/Gemalto support module.

Данный токен определялся и откликался. При помощи утилиты SACTools из пакета SafenetAuthenticationClient можно было выполнить его инициализацию. Но при работе с СКЗИ он вел себя крайне странно и непредсказуемо.

Проявлялось это следующим образом, на команду:

csptest -keyset -cont '\.Aladdin R.D. JaCarta [SCR Interface] (205D325E5842) 00 00alfa_shark' -check 

Выдавался ответ, что все хорошо:

[ErrorCode: 0x00000000]


Но сразу после попытки зачитать ключи программно эта же проверка начинала выдавать ошибку:

[ErrorCode: 0x8009001a]

Согласно перечню кодов ошибок объектной модели компонентов Microsoft

NTE_KEYSET_ENTRY_BAD
0x8009001A
Keyset as registered is invalid.

«Невалидный набор ключей» — причина такого сообщения, возможно, кроется либо в старом чипе, прошивке и апплете Gemalto, либо в их драйверах для ОС, которые не поддерживают новые стандарты формирования ЭП и функции хэширования ГОСТ Р 34.10-2021 и ГОСТ Р 34.11-2021.

В таком состоянии токен блокировался. СКЗИ начинало показывать неактуальное состояние считывателя и ключевого контейнера. Перезапуск службы криптографического провайдера cprocsp, службы работы с токенами и смарт-картами pcscd и всей операционной системы не помогали, только повторная инициализация.

Справедливости ради требуется отметить, что SafeNet eToken PRO корректно работал с ключами ГОСТ Р 34.10-2001 в ОС Windows 7 и 10.

Можно было бы попробовать установить СКЗИ КриптоПро CSP 4.0 ФКН (Gemalto), но целевая задача — защитить наши ключи ЭП и шифрования с помощью сертифицированных ФСБ и ФСТЭК изделий семейства JaCarta, в которых поддерживаются новые стандарты.

Проблему удалось решить, взяв настоящий токен JaCarta PKI в (XL) обычном корпусе.

Но на попытки заставить работать Safenet eToken PRO времени было потрачено немало. Хотелось обратить на это внимание и, возможно, кого-то оградить от подобного.

Работа с механизмами, на примере зашифрования сообщения

Механизмы в PKCS#11 задаются через структур CK_MECHANISM. Объекты типа CK_MECHANISM в дальнейшем передаются PKCS#11-функциям для указания нужного механизма. Сама структура CK_MECHANISM состоит из трех элементов:

  1. Идентификатор механизма (mechanism);

  2. Указатель на параметры механизма (pParameter);

  3. Длина в байтах параметров механизма (ulParameterLen).

Самый простой пример параметра механизма — вектор инициализации для алгоритмов шифрования. Попробуем на примере показать, как можно зашифровать сообщение через механизм CKM_GOST28147 с указанным вектором инициализации. Пример реализован внутри функции encrypt_data:

int encrypt_data(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE secretKey)
{
    /* Имитовставка */
    CK_BYTE iv[] = { 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f };
    /*  Механизм программного шифрования/расшифрования по алгоритму ГОСТ 28147-89 */
    CK_MECHANISM gost28147EncDecMech = {CKM_GOST28147, iv, sizeof(iv)};

    /*************************************************************************
    * Данные для шифрования                                                  *
    *************************************************************************/
    CK_BYTE data[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                   0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                   0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
                   0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00 };

    CK_BYTE_PTR encrypted;                            // Указатель на временный буфер для зашифрованных данных
    CK_ULONG encryptedSize;                           // Размер временного буфера в байтах

    CK_RV rv;
    int errorCode = 1;

    /*************************************************************************
    * Инициализировать операцию шифрования                                   *
    *************************************************************************/
    rv = functionList->C_EncryptInit(session, &gost28147EncDecMech, secretKey);
    CHECK_AND_LOG(" C_EncryptInit", rv == CKR_OK, rvToStr(rv), exit);

    /*************************************************************************
    * Зашифровать данные (при шифровании с использованием механизма          *
    * CKM_GOST28147_ECB размер данных должен быть кратен 8)                  *
    *************************************************************************/
    encryptedSize = sizeof(data);

    encrypted = (CK_BYTE_PTR)malloc(encryptedSize * sizeof(CK_BYTE));
    CHECK("  Memory allocation for encrypted data", encrypted != NULL_PTR, exit);

    rv = functionList->C_Encrypt(session, data, sizeof(data), encrypted, &encryptedSize);
    CHECK_AND_LOG(" C_Encrypt", rv == CKR_OK, rvToStr(rv), free_encrypted);

    /*************************************************************************
    * Распечатать буфер, содержащий зашифрованные данные                     *
    *************************************************************************/
    printf(" Encrypted buffer is:n");
    printHex(encrypted, encryptedSize);

    printf("Encryption has been completed successfully.n");

    errorCode = 0;
free_encrypted:
    free(encrypted);
exit:
    return errorCode;
}

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

Создание контейнера[править]

Примечание: Для того, чтобы сертификат из контейнера можно было использовать через модуль pkcs11 (из пакета lsb-cprocsp-pkcs11) в браузере firefox-gost, необходимо создать его с -provtype 75 (поддержка ГОСТ-2001).

Внимание! C 1 января 2021 г. по указанию ФСБ РФ и Минкомсвязи всем аккредитованным УЦ запрещен выпуск сертификатов ЭП по ГОСТ 2001.
Ключи и запрос на сертификат необходимо формировать ГОСТ 2021.

Создадим контейнер с именем «test» в локальном считывателе HDIMAGE.

При установленном пакете cprocsp-rdr-gui-gtk будет показано графическое окно, где будет предложено перемещать указатель мыши или нажимать клавиши:

Примечание: Если такой пакет не установлен, будет предложено ввести любые символы с клавиатуры.

После этого будет предложено указать пароль на контейнер (можно указать пустой, тогда пароль запрашиваться не будет):

После указания пароля снова будет предложено перемещать указатель мыши.

Вывод команды:

CSP (Type:75) v4.0.9006 KC1 Release Ver:4.0.9708 OS:Linux CPU:AMD64 FastCode:READY:AVX.
AcquireContext: OK. HCRYPTPROV: 6679219
GetProvParam(PP_NAME): Crypto-Pro GOST R 34.10-2001 KC1 CSP
Container name: "test"
Signature key is not available.
Attempting to create a signature key...
a signature key created.
Exchange key is not available.
Attempting to create an exchange key...
an exchange key created.
Keys in container:
  signature key
  exchange key
Extensions:
  OID: 1.2.643.2.2.37.3.9

  OID: 1.2.643.2.2.37.3.10
Total: SYS: 0,030 sec USR: 0,160 sec UTC: 22,910 sec
[ErrorCode: 0x00000000]

Локальный контейнер создан.

В КриптоПро 5 появилась возможность интерактивно выбирать носитель и тип создаваемого контейнера. Теперь можно создавать неизвлекаемые контейнеры. Для этого необходимо выполнить команду, где testinside_2021 — имя контейнера:

Откроется окно выбора носителя и способа создания контейнера. Для некоторых носителей нет возможности выбрать способ создания контейнера (Рутокен S, JaCarta PKI):

Для некоторых носителей можно выбрать способ создания контейнера (Рутокен ЭЦП, JaCarta-2 PKI/ГОСТ).
Создание неизвлекаемого контейнера:
Создание неизвлекаемого контейнера
Создание обычного контейнера:
Создание обычного контейнера

Создание объектов — на примере генерации ключевых пар

В первую очередь, напишем функцию, которая будет генерировать ключевую пару ГОСТ Р 34.10-2021 256 бит на указанном слоте:

int gen_gost_key_pair(CK_SESSION_HANDLE session)
{
    CK_KEY_TYPE keyTypeGostR3410_2021_256 = CKK_GOSTR3410;
    CK_BYTE keyPairIdGost2021_256[] = { "GOST R 34.10-2021 (256 bits) sample key pair ID (Aktiv Co.)" };
    CK_BYTE parametersGostR3410_2021_256[] = { 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01 };
    CK_BYTE parametersGostR3411_2021_256[] = { 0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02 };
    CK_BBOOL attributeTrue = CK_TRUE;
    CK_BBOOL attributeFalse = CK_FALSE;

    CK_OBJECT_CLASS publicKeyObject = CKO_PUBLIC_KEY;

    CK_ATTRIBUTE publicKeyTemplate[] =
    {
        { CKA_CLASS, &publicKeyObject, sizeof(publicKeyObject)},                                        // Класс - открытый ключ
        { CKA_ID, &keyPairIdGost2021_256, sizeof(keyPairIdGost2021_256) - 1 },                          // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)
        { CKA_KEY_TYPE, &keyTypeGostR3410_2021_256, sizeof(keyTypeGostR3410_2021_256) },                // Тип ключа - ГОСТ Р 34.10-2021(256)
        { CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                                            // Ключ является объектом токена
        { CKA_PRIVATE, &attributeFalse, sizeof(attributeFalse)},                                        // Ключ доступен без аутентификации на токене
        { CKA_GOSTR3410_PARAMS, parametersGostR3410_2021_256, sizeof(parametersGostR3410_2021_256) },   // Параметры алгоритма ГОСТ Р 34.10-2021(256)
        { CKA_GOSTR3411_PARAMS, parametersGostR3411_2021_256, sizeof(parametersGostR3411_2021_256) }    // Параметры алгоритма ГОСТ Р 34.11-2021(256)
    };

    CK_OBJECT_CLASS privateKeyObject = CKO_PRIVATE_KEY;

    CK_ATTRIBUTE privateKeyTemplate[] =
    {
        { CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject)},                                      // Класс - закрытый ключ
        { CKA_ID, &keyPairIdGost2021_256, sizeof(keyPairIdGost2021_256) - 1 },                          // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)
        { CKA_KEY_TYPE, &keyTypeGostR3410_2021_256, sizeof(keyTypeGostR3410_2021_256) },                // Тип ключа - ГОСТ Р 34.10-2021(256)
        { CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                                            // Ключ является объектом токена
        { CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},                                          // Ключ доступен только после аутентификации на токене
        { CKA_GOSTR3410_PARAMS, parametersGostR3410_2021_256, sizeof(parametersGostR3410_2021_256) },   // Параметры алгоритма ГОСТ Р 34.10-2021(256)
        { CKA_GOSTR3411_PARAMS, parametersGostR3411_2021_256, sizeof(parametersGostR3411_2021_256) }    // Параметры алгоритма ГОСТ Р 34.11-2021(256)
    };

    CK_OBJECT_HANDLE privateKey;                      // Хэндл закрытого ключа ГОСТ (ключевая пара для подписи и шифрования)    
    CK_OBJECT_HANDLE publicKey;                       // Хэндл открытого ключа ГОСТ (ключевая пара для подписи и шифрования)    

    CK_MECHANISM gostR3410_2021_256KeyPairGenMech = { CKM_GOSTR3410_KEY_PAIR_GEN, NULL_PTR, 0 };

    CK_RV rv;   
    int errorCode = 1;

    /*************************************************************************
    * Генерация ключевой пары на токене                                      *
    *************************************************************************/
    rv = functionList->C_GenerateKeyPair(session, &gostR3410_2021_256KeyPairGenMech, 
    publicKeyTemplate, arraysize(publicKeyTemplate),
    privateKeyTemplate, arraysize(privateKeyTemplate),
    &publicKey, &privateKey);
    CHECK_AND_LOG(" C_GenerateKeyPair", rv == CKR_OK, rvToStr(rv), exit);

    errorCode = 0;
    printf("Gost key pair generated successfullyn");

exit:
    return errorCode;
}

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

Читайте также:  Выбираем токен для налоговой. Какие бывают и чем отличаются? | Пикабу

Теперь перейдём к объектам. Внутри функции gen_gost_key_pair происходит создание двух объектов на токене: открытого и закрытого ключей. Вот, что стандарт PKCS#11 говорит про объекты:

Cryptoki recognizes a number of classes of objects, as defined in the CK_OBJECT_CLASS data type. An object consists of a set of attributes, each of which has a given value. Each attribute that an object possesses has precisely one value.

То есть стандарт не даёт явное определение объекта, но из того, что там написано, мы знаем:

Также в стандарте представлена классификация объектов:

Иерархия PKCS#11 объектов
Иерархия PKCS#11 объектов

Заголовок диаграммы определяет класс объекта, а то что ниже — некоторые из его атрибутов. Видно, что объектом может являться некоторый механизм (о механизмах мы поговорим позже), встроенные функции токена (Hardware feature), некоторые данные на токене (Storage). В нашем случае мы выполнили действие с данными.

Название всех атрибутов начинается с префикса “CKA_”. Одним из самых важных атрибутов является CKA_ID. Он задаёт идентификатор объекта и используется для связи ключевых пар и сертификатов. Атрибут CKA_TOKEN является булевым и показывает, является ли объект — объектом токена.

Атрибут CKA_PRIVATE тоже является булевым и определяет нужна ли предварительная аутентификация для получения доступа к объекту. Атрибут CKA_ID — задаёт шестнадцатеричный идентификатор объекта. Также есть булевые атрибуты CKA_MODIFIABLE, CKA_COPYABLE, CKA_DESTROYABLE для более тонкой настройки доступа к объекту.

Объекты данных могут быть самыми разнообразными: асимметричные ключи, симметричные ключи, сертификаты, просто какая-либо информация на токене. В нашем примере мы создали два объекта, но сделали это неявно с помощью механизма генерации ключей. C_GenerateKeyPair приняла на вход механизм генерации ключевой пары, шаблоны открытого и закрытого ключа и с помощью механизма сгенерировала объекты ключевой пары (publicKey и privateKey).

Установка пакетов криптопро csp

При установке КриптоПро CSP по умолчанию нужные пакеты для работы с токенами и смарт-картами отсутствуют.

zypper search cprocsp

Выполняем установку в CSP компонента поддержки JaCarta components for CryptoPro CSP

zypper install cprocsp-rdr-jacarta-64-3.6.408.683-4.x86_64.rpm

Некоторые компоненты имеют зависимости. Так, например, если попытаться выполнить установку пакета поддержки SafeNet eToken PRO cprocsp-rdr-emv-64-4.0.9944-5.x86_64.rpm — EMV/Gemalto support module, то получим сообщение о необходимости сначала установить базовый компонент CSP поддержки считывателей cprocsp-rdr-pcsc-64-4.0.9944-5.x86_64.rpm — PC/SC components for CryptoPro CSP readers:

zypper install cprocsp-rdr-emv-64-4.0.9944-5.x86_64.rpm
Loading repository data...
Reading installed packages...
Resolving package dependencies...

Problem: nothing provides cprocsp-rdr-pcsc-64 >= 4.0 needed by cprocsp-rdr-emv-64-4.0.9944-5.x86_64
 Solution 1: do not install cprocsp-rdr-emv-64-4.0.9944-5.x86_64
 Solution 2: break cprocsp-rdr-emv-64-4.0.9944-5.x86_64 by ignoring some of its dependencies

Choose from above solutions by number or cancel [1/2/c] (c): c

Устанавливаем базовые пакеты поддержки считывателей и ключевых носителей:

zypper install cprocsp-rdr-pcsc-64-4.0.9944-5.x86_64.rpm

zypper install lsb-cprocsp-pkcs11-64-4.0.9944-5.x86_64.rpm

Теперь можно установить модули для работы с остальными видами носителей и компонент GUI:

zypper install cprocsp-rdr-emv-64-4.0.9944-5.x86_64.rpm

zypper install cprocsp-rdr-novacard-64-4.0.9944-5.x86_64.rpm
zypper install cprocsp-rdr-mskey-64-4.0.9944-5.x86_64.rpm
zypper install cprocsp-rdr-gui-gtk-64-4.0.9944-5.x86_64.rpm

Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр

Проверяем итоговую конфигурацию КриптоПро CSP:

zypper search cprocsp
Loading repository data...
Reading installed packages...

S | Name | Summary | Type— —————————– —————————————————- ——–i | cprocsp-curl-64 | CryptoPro Curl shared library and binaris. Build 9944. | packagei | cprocsp-rdr-emv-64 | EMV/Gemalto support module | packagei | cprocsp-rdr-gui-gtk-64 | GUI components for CryptoPro CSP readers. Build 9944. | packagei | cprocsp-rdr-jacarta-64 | JaCarta components for CryptoPro CSP. Build 683. | packagei | cprocsp-rdr-mskey-64 | Mskey support module | packagei | cprocsp-rdr-novacard-64 | Novacard support module | packagei | cprocsp-rdr-pcsc-64 | PC/SC components for CryptoPro CSP readers. Build 9944.| packagei | lsb-cprocsp-base | CryptoPro CSP directories and scripts. Build 9944. | packagei | lsb-cprocsp-ca-certs | CA certificates. Build 9944. | packagei | lsb-cprocsp-capilite-64 | CryptoAPI lite. Build 9944. | packagei | lsb-cprocsp-kc2-64 | CryptoPro CSP KC2. Build 9944. | packagei | lsb-cprocsp-pkcs11-64 | CryptoPro PKCS11. Build 9944. | packagei | lsb-cprocsp-rdr-64 | CryptoPro CSP readers. Build 9944. | package

Чтобы применить изменения, выполняем перезапуск службы криптографического провайдера и проверяем ее статус:

/etc/init.d/cprocsp restart
/etc/init.d/cprocsp status

Как использовать PAM-модули для локальной аутентификации в Linux по ключам ГОСТ-2012 на Рутокене / Хабр

Формирование cms-подписи

Данная возможность является расширением библиотеки Рутокен и может работать только с ГОСТ-ключами. Для создания подписи в формате CMS требуется наличие закрытого ключа и сертификата (неявно содержащего в себе открытый ключ). Создание CMS-подписи реализовано в функции sign_cms:

int sign_cms(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE certificate, CK_OBJECT_HANDLE privateKey)
{
    /*************************************************************************
    * Данные для подписи                                                     *
    *************************************************************************/
    CK_BYTE data[] =
    {
        0x01, 0x00, 0x02, 0x35, 0x35,
        0x02, 0x00, 0x01, 0x01,
        0x81, 0x00, 0x09, 0x34, 0x30, 0x34, 0x34, 0x34, 0x35, 0x39, 0x39, 0x38,
        0x82, 0x00, 0x0A, 0x37, 0x37, 0x38, 0x31, 0x35, 0x36, 0x34, 0x36, 0x31, 0x31,
        0x83, 0x00, 0x13, 0x41, 0x6B, 0x74, 0x69, 0x76, 0x20, 0x52, 0x75, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x20, 0x42, 0x61, 0x6E, 0x6B, 0x2E,
        0x84, 0x00, 0x14, 0x34, 0x37, 0x37, 0x37, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x31, 0x31, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x37, 0x36,
        0x85, 0x00, 0x0A, 0x33, 0x32, 0x32, 0x38, 0x37, 0x33, 0x36, 0x37, 0x36, 0x35,
        0x86, 0x00, 0x03, 0x52, 0x55, 0x42,
        0xFF, 0x00, 0x0D, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
    };

    CK_BYTE_PTR signature;                             // Указатель на буфер, содержащий подпись исходных данных
    CK_ULONG signatureSize;                            // Размер буфера, содержащего подпись исходных данных, в байтах
    char* signaturePem;                                // Строка с CMS в формате PEM

    CK_RV rv;
    int errorCode = 1;                                 // Флаг ошибки

    /*************************************************************************
    * Подписать данные                                                       *
    *************************************************************************/
    rv = functionListEx->C_EX_PKCS7Sign(session, data, sizeof(data), certificate,
        &signature, &signatureSize, privateKey, NULL_PTR, 0, USE_HARDWARE_HASH);
    CHECK_AND_LOG(" C_EX_PKCS7Sign", rv == CKR_OK, rvToStr(rv), exit);

        /*************************************************************************
        * Сконвертировать и распечатать буфер в формате PEM                      *
        *************************************************************************/
        GetCMSAsPEM(signature, signatureSize, &signaturePem);
        CHECK(" Get CMS in PEM format", signaturePem != NULL, free_signature);

        printf("nSignature is:n");
        printf("%sn", signaturePem);


    errorCode = 0;
    printf("Data has been signed successfully.n");

free_signature_pem:
    free(signaturePem);

    /*************************************************************************
    * Освободить память, выделенную в библиотеке                             *
    *************************************************************************/
free_signature:
    rv = functionListEx->C_EX_FreeBuffer(signature);
    CHECK_RELEASE_AND_LOG(" C_EX_FreeBuffer", rv == CKR_OK, rvToStr(rv), errorCode);

exit:
    return errorCode;
}

Создание CMS-подписи произошло вызовом всего лишь одной функции расширения C_EX_PKCS7Sign. А объект сертификата нашелся так же просто, как и объект ключа с минимальными отличиями в коде. Все это показывает, как просто и лаконично (по меркам языка C) спроектирован стандарт PKCS#11 с идеей объектного подхода.

Оцените статью
ЭЦП Эксперт
Добавить комментарий