- Основной этап
- Rutokens и ubuntu 19.04 64bit : рб-софт
- Настройка и диагностика криптопро csp
- Подготовительный этап
- Поиск объектов и создание сырой подписи
- Причина 3
- Работа с механизмами, на примере зашифрования сообщения
- Создание контейнера[править]
- Создание объектов — на примере генерации ключевых пар
- Установка пакетов криптопро csp
- Формирование cms-подписи
Основной этап
Основной этап можно разделить на работу со слотами и работу внутри сессии
Этап работы со слотами
Слот — это дескриптор виртуального интерфейса, куда подключен токен. Конкретно в нашем примере весь этап работы со слотами уместился в определение одной функции 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;
}
Работа со слотами происходит примерно в такой последовательности:
Получение списка слотов с помощью функции C_GetSlotList или через функцию ожидания событий, связанных со слотами (пример будет рассмотрен ниже).
Выполнение различных функций работы со слотами. В нашей программе этот этап отсутствует, но его пример можно найти здесь для функции форматирования устройства C_EX_InitToken. Подробнее функции работы со слотами будут описаны ниже.
Этап работы внутри сессии
Сессия — это дескриптор контекста выполнения последовательности операций. Обычно во время сессий выполняются основные функции работы с токеном или смарт-картой: шифрование, подпись и т.п. В нашем случае выполняется смена PIN-кода:
Rutokens и ubuntu 19.04 64bit : рб-софт
Задача: с помощью RutokenS зайти на портал Госуслуг в Ubuntu 19.04
Итак, приступаем:
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
Если во время выполнения этой команды система предложить понизить версию установленного плагина:
то не вздумайте это делать!!! Если предложит обновить, то ставим самый свежий плагин!!!
После успешной установки заменяем дефолтный файл /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
примерный положительный вывод:
Если в контейнере с ключами хранятся связанные сертификаты например на 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
Примерный вывод:
При установке всех компонентов обращаем внимание на то, чтобы ВСЕ компоненты были самыми свежими!
Для проверки рутокена заходим на: https://www.ecpexpert.ru/sites/default/files/products/cades/demopage/simple.html#
Итого на дружбу рутокена и ubuntu ушло час делов с фотографированием процесса установки.
После всех манипуляций заходим на портал госуслуг без проблем с помощью рутокена.
Настройка и диагностика криптопро 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"
В результате выполнения в конфигурационный файл /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;
}
Здесь происходит следующее:
В память процесса подгружается PKCS#11-библиотека, хранящаяся по пути PKCS11ECP_LIBRARY_NAME, с помощью функции LoadLibrary (стандартная функция для Windows-систем, для Linux-систем определена обертка).
Далее из библиотеки вытаскиваются указатели на функции C_GetFunctionList и C_EX_GetFunctionListExtended. Первая функция определена в стандарте PKCS#11 и позволяет получить структуру указателей на функции библиотеки. Вторая — является специфичной для библиотеки rtpkcs11ecp и позволяет получить схожую структуру указателей на функции расширения библиотеки. О функциях расширения мы поговорим позже.
Потом мы вызываем полученную функцию C_GetFunctionList и получаем уже саму структуру указателей на функции.
С помощью функции 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 состоит из трех элементов:
Идентификатор механизма (mechanism);
Указатель на параметры механизма (pParameter);
Длина в байтах параметров механизма (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.
То есть стандарт не даёт явное определение объекта, но из того, что там написано, мы знаем:
Также в стандарте представлена классификация объектов:
Заголовок диаграммы определяет класс объекта, а то что ниже — некоторые из его атрибутов. Видно, что объектом может являться некоторый механизм (о механизмах мы поговорим позже), встроенные функции токена (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
Проверяем итоговую конфигурацию КриптоПро 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
Формирование 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 с идеей объектного подхода.