Русский
Русский
English
Статистика
Реклама

Pkcs#11

PKCS11 для самых маленьких

03.03.2021 08:18:42 | Автор: admin

В этой статье мы познакомимся со стандартом PKCS#11, предназначенным для работы с различными криптографическими устройствами. Для демонстрации мы будем использовать токены и смарт-карты Рутокен ЭЦП 2.0.

Чтобы вам было проще понять эту информацию, перед её прочтением желательно:

Исходя из определения из Википедии:

PKCS #11 один из стандартов семейства Public-Key Cryptography Standards (PKCS). Он определяет платформонезависимый программный интерфейс доступа к криптографическим устройствам смарт-картам, токенам, криптографическим ускорителям, серверам ключей и другим средствам криптографической защиты информации. Иногда именуется Cryptoki.

Проще говоря, PKCS#11 определяет платформонезависимый набор функций, структуры, константы и т.п. для работы с криптографическими устройствами. Эти функции могут быть реализованы внутри различных библиотек, например, opensc-pkcs11 или в нашей библиотеке по работе с устройствами Рутокен rtpkcs11ecp. Библиотеки могут отличаться не только реализацией, но и самим набором функций, типов и констант. Это возможно, так как стандарт PKCS#11 описывает различные способы расширения, что позволяет добавлять свои функции, например, для работы с CMS-подписью, флеш-памятью и т.п.

Но давайте обо всем по порядку. В первую очередь, определимся, что из себя представляют функции PKCS#11-библиотек. Функции PKCS#11 внутри это обертки для работы с токенами и смарт-картами через APDU команды

Про APDU

APDU язык ассемблера для устройств.

Более подробно об APDU-формате написано в этой статье, мы же опишем его проще. APDU-формат описывает способ общения с различными устройствами с помощью байтовых последовательностей. На практике это происходит следующим образом:

  1. Токену или смарт-карте посылается байтовая последовательность;

  2. Операционная система устройства обрабатывает эту последовательность в команду и посылает в ответ код возврата и, при необходимости, дополнительную информацию.

Что же могут содержать внутри себя такие команды? Все, что угодно! Начиная с просьбы отправить информацию об устройстве или записать какие-то данные на него и заканчивая просьбой зашифровать указанное сообщение. Более того, эти команды могут быть разбиты на несколько, что позволяет оптимизировать работу с токеном и сделать ее конвейерной. К примеру, при шифровании сообщения устройству может передаваться небольшая часть сообщения, а он в ответ будет высылать результат шифрования только этой части. Эта особенность может быть использована при работе с данными больших размеров.

И вот вроде бы все хорошо: APDU даёт полную возможность для общения с токеном и смарт-картой, но зачем же тогда нужна PKCS#11-обертка? Причины достаточно очевидны:

  • Работать с шестнадцатеричными байтовыми последовательностями весьма неудобно, каждый раз надо заглядывать в спецификацию команд. Хотелось бы иметь их текстовый эквивалент, а лучше целую библиотеку для разработки.

  • Программирование через APDU-команды имеет низкий КПД. При выполнении даже самой простой операции надо написать множество команд. PKCS#11 устраняет эту проблему: при выполнении одной функции PKCS#11 посылается несколько APDU команд. Меньше пишешь больше делаешь.

Теперь, когда мы знаем что лежит в основе PKCS#11-команд, перейдем к рассмотрению цикла работы с устройством.

Репозиторий с примерами

Чтобы улучшить понимание представленных ниже примеров рекомендуем:

  1. Клонировать репозиторий с примерами.

  2. Выполнить настройку системы согласно README.

  3. Попробовать собрать примеры.

  4. Отформатировать устройство семейства Рутокен ЭЦП 2.0 с помощью примера ex0_format_token.

Цикл работы с токеном

Придержим пока описание содержимого стандарта PKCS#11 и дадим поверхностное представление о том, как происходит работа с токеном в целом. Для этого рассмотрим листинг смены PIN-кода Пользователя на токене и смарт-карте:

#include #include "utils.h"extern CK_FUNCTION_LIST_PTR functionList;                 // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LISTextern CK_FUNCTION_LIST_EXTENDED_PTR functionListEx;      // Указатель на список функций расширения PKCS#11, хранящийся в структуре CK_FUNCTION_LIST_EXTENDEDint change_pin_code(CK_SLOT_ID slot, char* oldPin, char* newPin);int main(void){    CK_SLOT_ID_PTR slots;                              // Массив идентификаторов слотов    CK_ULONG slotCount;                                // Количество идентификаторов слотов в массиве    char* oldPin = "12345678";    char* newPin = "12345678";    CK_RV rv;                                          // Код возврата. Могут быть возвращены только ошибки, определенные в PKCS#11    int errorCode = 1;                                 // Флаг ошибки    // инициализируем библиотеку    if (init_pkcs11())         goto exit;    // получаем список слотов    if (get_slot_list(&slots, &slotCount))        goto free_pkcs11;    if (slotCount == 0) {        printf("No token found\n");        goto free_slots;    }    // изменяем PIN-код    if (change_pin_code(slots[0], oldPin, newPin))        goto free_slots;    errorCode = 0;    /*************************************************************************    * Очистить память, выделенную под слоты                                  *    *************************************************************************/free_slots:    free(slots);    /*************************************************************************    * Деинициализировать библиотеку                                          *    *************************************************************************/free_pkcs11:    free_pkcs11();exit:    if (errorCode) {        printf("\n\nSome error occurred. Sample failed.\n");    } else {        printf("\n\nSample has been completed successfully.\n");    }    return errorCode;}

Внутри заголовочного файла utils.h находится описание функций init_pkcs11, free_pkcs11, get_slot_list. Их определение мы дадим ниже.

Всю работу с токеном можно разделить на три этапа:

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

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

#include "utils.h"CK_FUNCTION_LIST_PTR functionList;                 // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LISTCK_FUNCTION_LIST_EXTENDED_PTR functionListEx;      // Указатель на список функций расширения PKCS#11, хранящийся в структуре CK_FUNCTION_LIST_EXTENDEDstatic 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. Его необходимо использовать, если мы хотим использовать библиотеку в нескольких потоках. В нашем примере мы могли бы опустить этот флаг.

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

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

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

Слот это дескриптор виртуального интерфейса, куда подключен токен. Конкретно в нашем примере весь этап работы со слотами уместился в определение одной функции 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: %d\n", (int)*slotCount);    /*************************************************************************    * Выставить признак успешного завершения программы                       *    *************************************************************************/    errorCode = 0;free_slots:    if (errorCode)    {        free(*slots_ptr);    }exit:    return errorCode;}

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

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

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

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

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

int change_pin_code(CK_SLOT_ID slot, char* oldPin, char* newPin){    CK_SESSION_HANDLE session;                         // Хэндл открытой сессии    CK_RV rv;                                          // Код возврата. Могут быть возвращены только ошибки, определенные в PKCS#11    int errorCode = 1;    /*************************************************************************    * Открыть RW сессию в первом доступном слоте                             *    *************************************************************************/    rv = functionList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &session);    CHECK_AND_LOG(" C_OpenSession", rv == CKR_OK, rvToStr(rv), exit);    /*************************************************************************    * Выполнить аутентификацию Пользователя                                *    *************************************************************************/    rv = functionList->C_Login(session, CKU_USER, oldPin, strlen(oldPin));    CHECK_AND_LOG(" C_Login (CKU_USER)", rv == CKR_OK, rvToStr(rv), close_session);    /*************************************************************************    * Установить PIN-код Пользователя по умолчанию                           *    *************************************************************************/    printf("\nChanging user PIN to default...\n");    rv = functionList->C_SetPIN(session, NULL_PTR, 0, newPin, strlen(newPin));    CHECK_AND_LOG(" C_SetPIN", rv == CKR_OK, rvToStr(rv), logout);    printf("User PIN has been changed to default successfully.\n");    errorCode = 0;    /*************************************************************************    * Сбросить права доступа                                                 *    *************************************************************************/logout:    rv = functionList->C_Logout(session);    CHECK_RELEASE_AND_LOG(" C_Logout", rv == CKR_OK, rvToStr(rv), errorCode);    /*************************************************************************    * Закрыть открытую сессию в слоте                                        *    *************************************************************************/close_session:    rv = functionList->C_CloseSession(session);    CHECK_RELEASE_AND_LOG(" C_CloseSession", rv == CKR_OK, rvToStr(rv), errorCode);exit:    return errorCode;}

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

  1. Открытие сессии в слоте с помощью функции C_OpenSession.

  2. Аутентификация пользователя с помощью функции C_Login. Стандартно на Рутокене присутствуют два пользователя: Администратор (CKU_SO) и Пользователь (CKU_USER). Аутентификация на устройстве не является обязательной операцией. Она нужна, когда мы хотим получить доступ к операциям и объектам, которые требуют наличия соответствующих прав доступа. В нашем случае, это операция смены PIN-кода Пользователя.

  3. Выполнение различных функций по работе с сессиями. В нашем случае это функция C_SetPIN.

  4. Далее по нисходящей могут идти операции сброса прав доступа (C_Logout) и завершения сессии (C_CloseSession).

Завершающий этап

Весь наш завершающий этап поместился внутри определения функции free_pkcs11:

int free_pkcs11(){        CK_RV rv;        int errorCode;        printf("\nFinalizing... \n");    rv = functionList->C_Finalize(NULL_PTR);        CHECK_RELEASE_AND_LOG(" C_Finalize", rv == CKR_OK, rvToStr(rv), errorCode);    CHECK_RELEASE(" FreeLibrary", FreeLibrary(module), errorCode);        return errorCode;}

Завершающий этап можно разделить на:

  1. Деинициализацию библиотеки с помощью функции C_Finalize.

  2. Выгрузку библиотеки из памяти процесса через функцию FreeLibrary (для Linux-систем имеется обертка).

Классификация функций PKCS#11

Стандартные функции и функции расширения

В общем виде все функции внутри PKCS#11-библиотек можно разделить на:

  • Стандартные те, что явно описаны в стандарте. Список указателей на эти функции можно получить с помощью функции C_GetFunctionList, хранящейся в этой же библиотеке. Сами же стандартные функции имеют префикс "C_".

  • Расширения те, что были добавлены разработчиками библиотеки и не описаны в стандарте. Стандарт явно не определяет функцию для получения списка расширенных функций. Но в библиотеке rtpkcs11ecp этой функцией является C_EX_GetFunctionListExtended. Сами же функции-расширения обычно имеют префикс "C_EX_".

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

Разделение по предназначению

С другой стороны, функции PKCS#11 можно классифицировать по их предназначению. Логично выделить следующие виды функций:

  • Функции общего назначения. Например, для получения списка функций или получения информации о библиотеке.

  • Функции работы со слотами. Они не зависят от контекста работы с токеном. Например, C_GetSlotInfo, C_GetTokenInfo и т.п.

  • Функции для работы с сессиями. Как уже говорилось ранее, обычно во время выполнения этих функций осуществляется основное взаимодействие с токеном. При выполнении функции внутри сессии контекст работы с токеном не теряется. Самый простой пример этих функций: функция аутентификации (C_Login), функции работы с закрытым ключом на токене (C_Sign, C_Encrypt) и т.п.

Атомарные и составные операции

Некоторые операции в PKCS#11 работают привычным для нас образом: вызвал функцию получил результат. Но есть и операции, реализация которых выстроена через многократный вызов функций. Ярким примером такой операции является блочное шифрование: чтобы зашифровать данные нужно вызвать функции: C_EncryptInit, C_EncryptUpdate, C_EncryptFinish. Пример этого будет приведен ниже. Эта особенность связана с тем, что внутри библиотеки PKCS#11 скрыт вызов APDU-команд, который как раз предусматривает разбиение некоторых команд на несколько итераций. Как уже говорилось ранее, это способствует эффективному использованию PKCS#11 с потоками данных.

Работа со слотами

Слоты это виртуальные устройства библиотеки для подключения токенов и смарт-карт. PKCS#11 предоставляет функции для получения списка слотов и ожидания изменения состояний слотов. Также с помощью специальных функций можно получать информацию о состоянии слота, например, наличие токена в нем, информация о подключенном токене и т.п. Давайте подробнее рассмотрим функции по работе со слотами.

Получение списка слотов

Одна из самых важных операций, которую мы будем использовать в 99 процентах случаев это получение списка активных слотов. Для этого в PKCS#11 есть функция C_GetSlotList. Примером ее использования является функция 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: %d\n", (int)*slotCount);    /*************************************************************************    * Выставить признак успешного завершения программы                       *    *************************************************************************/    errorCode = 0;free_slots:    if (errorCode)    {        free(*slots_ptr);    }exit:    return errorCode;}

Первый вызов функции C_GetSlotList позволяет получить количество доступных слотов. Это позволяет в дальнейшем выделять память под необходимое количество слотов. Второй вызов позволяет получить список слотов.

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

Мониторинг событий слотов и получение информации о слоте

Тем не менее, хоть функция C_GetSlotList и является самой часто используемой, она неудобна при написании приложений, которые хотят динамически следить за состоянием слотов. Особенно это может быть критично при написании многопоточных приложений. На помощь может прийти функция мониторинга событий слотов C_WaitForSlotEvent. Она прерывает свою работу при любом изменении состояния какого-либо слота и возвращает идентификатор измененного слота. Работа с этой функцией выглядит примерно следующим образом:

int monitor_slot_event(){    int errorCode = 0;    while (1) {        CK_SLOT_ID slot ;            CK_RV rv = functionList->C_WaitForSlotEvent(0, &slot, NULL_PTR);        if (CKR_CRYPTOKI_NOT_INITIALIZED == rv) break; // Индикатор того, что PKCS#11 деинициализирована из памяти.        CHECK_RELEASE_AND_LOG(" C_WaitForSlotEvent", rv == CKR_OK, rvToStr(rv), errorCode);        if (errorCode)            break;        CK_SLOT_INFO slotInfo;        rv = functionList->C_GetSlotInfo(slot, &slotInfo); // получение информации о слоте        if (CKR_CRYPTOKI_NOT_INITIALIZED == rv) break; // Индикатор того, что PKCS#11 деинициализирована из памяти.        CHECK_RELEASE_AND_LOG(" C_GetSlotInfo", rv == CKR_OK, rvToStr(rv), errorCode);        if (errorCode)            break;        if (CKF_TOKEN_PRESENT & slotInfo.flags) {                     token_inserted(slot);        }    }}

Как можно заметить, функция отлавливает помимо событий подключения и извлечения токена и смарт-карты, еще и событие деинициализации библиотеки PKCS#11 (код возврата CKR_CRYPTOKI_NOT_INITIALIZED). Данная особенность позволяет использовать эту функцию внутри многопоточных приложений без дополнительной возни с обработкой событий при завершении работы приложения.

Первым аргументом в функцию C_WaitForSlotEvent передается флаг блокировки (CKF_DONT_BLOCK). Если он установлен, то функция не является блокирующей. В таком случае, если никакой слот не был изменен, то возвращается код CKR_NO_EVENT.

Также стоит обратить внимание на использование функции получения информации о слоте C_GetSlotInfo. Мы использовали ее для определения наличия токена по флагу CKF_TOKEN_PRESENT. Подробнее с этой структурой можно ознакомиться здесь.

В этом файле представлен пример получения информации о подключенных токенах или смарт-картах с помощью функции C_GetTokenInfo. С помощью этой функции можно получить серийный номер, метку и другие характеристики подключенного устройства.

Объекты

Всё есть объект это почти про PKCS#11.

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

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

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

int gen_gost_key_pair(CK_SESSION_HANDLE session){    CK_KEY_TYPE keyTypeGostR3410_2012_256 = CKK_GOSTR3410;    CK_BYTE keyPairIdGost2012_256[] = { "GOST R 34.10-2012 (256 bits) sample key pair ID (Aktiv Co.)" };    CK_BYTE parametersGostR3410_2012_256[] = { 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01 };    CK_BYTE parametersGostR3411_2012_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, &keyPairIdGost2012_256, sizeof(keyPairIdGost2012_256) - 1 },                          // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)        { CKA_KEY_TYPE, &keyTypeGostR3410_2012_256, sizeof(keyTypeGostR3410_2012_256) },                // Тип ключа - ГОСТ Р 34.10-2012(256)        { CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                                            // Ключ является объектом токена        { CKA_PRIVATE, &attributeFalse, sizeof(attributeFalse)},                                        // Ключ доступен без аутентификации на токене        { CKA_GOSTR3410_PARAMS, parametersGostR3410_2012_256, sizeof(parametersGostR3410_2012_256) },   // Параметры алгоритма ГОСТ Р 34.10-2012(256)        { CKA_GOSTR3411_PARAMS, parametersGostR3411_2012_256, sizeof(parametersGostR3411_2012_256) }    // Параметры алгоритма ГОСТ Р 34.11-2012(256)    };    CK_OBJECT_CLASS privateKeyObject = CKO_PRIVATE_KEY;    CK_ATTRIBUTE privateKeyTemplate[] =    {        { CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject)},                                      // Класс - закрытый ключ        { CKA_ID, &keyPairIdGost2012_256, sizeof(keyPairIdGost2012_256) - 1 },                          // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)        { CKA_KEY_TYPE, &keyTypeGostR3410_2012_256, sizeof(keyTypeGostR3410_2012_256) },                // Тип ключа - ГОСТ Р 34.10-2012(256)        { CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                                            // Ключ является объектом токена        { CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},                                          // Ключ доступен только после аутентификации на токене        { CKA_GOSTR3410_PARAMS, parametersGostR3410_2012_256, sizeof(parametersGostR3410_2012_256) },   // Параметры алгоритма ГОСТ Р 34.10-2012(256)        { CKA_GOSTR3411_PARAMS, parametersGostR3411_2012_256, sizeof(parametersGostR3411_2012_256) }    // Параметры алгоритма ГОСТ Р 34.11-2012(256)    };    CK_OBJECT_HANDLE privateKey;                      // Хэндл закрытого ключа ГОСТ (ключевая пара для подписи и шифрования)        CK_OBJECT_HANDLE publicKey;                       // Хэндл открытого ключа ГОСТ (ключевая пара для подписи и шифрования)        CK_MECHANISM gostR3410_2012_256KeyPairGenMech = { CKM_GOSTR3410_KEY_PAIR_GEN, NULL_PTR, 0 };    CK_RV rv;       int errorCode = 1;    /*************************************************************************    * Генерация ключевой пары на токене                                      *    *************************************************************************/    rv = functionList->C_GenerateKeyPair(session, &gostR3410_2012_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 successfully\n");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). Мы пока ещё не описывали механизмы, но, говоря простым языком, механизм это идентификатор операции, которая выполняет какую-то криптографическую функцию. В нашем случае это функция генерации объекта.

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

В прошлом разделе мы сгенерировали ключевую пару. На этот раз будем считать, что у нас нет хендлов на сгенерированные ключи, но мы знаем их идентификатор 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 keyPairIdGost2012_256[] = { "GOST R 34.10-2012 (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, &keyPairIdGost2012_256, sizeof(keyPairIdGost2012_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 found\n", 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-2012(256)                     */    CK_BYTE parametersGostR3411_256[] = {0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02};    /* Механизм подписи/проверки подписи по алгоритму ГОСТ Р 34.10-2012(256) и хешированием по алгоритму ГОСТ Р 34.11-2012(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.

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

Работа с функциями расширения на примере создания запроса на сертификат

Пришло время показать, как работать с функциями-расширениями. Сделаем это на примере библиотеки PKCS#11 от Рутокен и создадим запрос на сертификат для нашей ключевой пары. Генерация запроса на сертификат не описана в стандарте, поэтому сделаем это через функцию-расширение C_EX_CreateCSR.

int create_csr(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE publicKey, CK_OBJECT_HANDLE privateKey){    /*************************************************************************    * Запрос на получение сертификата                                        *    *************************************************************************/    /*************************************************************************    * Список полей DN (Distinguished Name)                                   *    *************************************************************************/    CK_CHAR_PTR dn[] = { (CK_CHAR_PTR)"CN",                   // Тип поля CN (Common Name)                     (CK_CHAR_PTR)"UTF8String:Иванов",        // Значение                     (CK_CHAR_PTR)"C",                        // C (Country)                     (CK_CHAR_PTR)"RU",                     (CK_CHAR_PTR)"2.5.4.5",                  // SN (Serial Number)                     (CK_CHAR_PTR)"12312312312",                     (CK_CHAR_PTR)"1.2.840.113549.1.9.1",     // E (E-mail)                     (CK_CHAR_PTR)"ivanov@mail.ru",                     (CK_CHAR_PTR)"ST",                       // ST (State or province)                     (CK_CHAR_PTR)"UTF8String:Москва",                     (CK_CHAR_PTR)"O",                        // O (Organization)                     (CK_CHAR_PTR)"CompanyName",                     (CK_CHAR_PTR)"OU",                       // OU (Organizational Unit)                     (CK_CHAR_PTR)"Devel",                     (CK_CHAR_PTR)"L",                        // L (Locality)                     (CK_CHAR_PTR)"Moscow", };    /*************************************************************************    * Список дополнительных полей                                            *    *************************************************************************/    CK_CHAR_PTR exts[] = {(CK_CHAR_PTR)"keyUsage",                                                        // Использование ключа                      (CK_CHAR_PTR)"digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment",                      (CK_CHAR_PTR)"extendedKeyUsage",                                                    // Дополнительное использование                      (CK_CHAR_PTR)"1.2.643.2.2.34.6,1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.4",                      (CK_CHAR_PTR)"2.5.29.17",                                                           // Дополнительное имя (пример с кодированием в виде DER)                      (CK_CHAR_PTR)"DER:30:0F:81:0D:65:78:61:6d:70:6c:65:40:79:61:2E:72:75",                      (CK_CHAR_PTR)"2.5.29.32",                                                           // Политики сертификата (кодирование в виде DER с пометкой "critical")                      (CK_CHAR_PTR)"critical,DER:30:0A:30:08:06:06:2A:85:03:64:71:01",                      (CK_CHAR_PTR)"1.2.643.100.111",                                                     // Средства электронной подписи владельца                      (CK_CHAR_PTR)"ASN1:UTF8String:СКЗИ \\\"Рутокен ЭЦП 2.0\\\"", };    CK_BYTE_PTR csr;                                   // Указатель на буфер, содержащий подписанный запрос на сертификат    CK_ULONG csrSize;                                  // Размер запроса на сертификат в байтах    char* csrPem;                                      // Строка с CSR в формате PEM    CK_RV rv;    int errorCode = 1;    /*************************************************************************    * Создать запрос на сертификат                                           *    *************************************************************************/    printf("\nCreating CSR...\n");    /*************************************************************************    * Создание запроса на сертификат                                         *    *************************************************************************/    rv = functionListEx->C_EX_CreateCSR(session, publicKey, dn, arraysize(dn), &csr, &csrSize, privateKey, NULL_PTR, 0, exts, arraysize(exts));    CHECK_AND_LOG(" C_EX_CreateCSR", rv == CKR_OK, rvToStr(rv), exit);    /*************************************************************************    * Сконвертировать и распечатать буфер в формате PEM                      *    *************************************************************************/    GetCSRAsPEM(csr, csrSize, &csrPem);    CHECK(" Get CSR in PEM format", csrPem != NULL, free_csr);    printf("\nCertificate request is:\n");    printf("%s\n", csrPem);    errorCode = 0;    printf("Creating CSR has been completed successfully.\n");free_csr_pem:    free(csrPem);free_csr:    rv = functionListEx->C_EX_FreeBuffer(csr);    CHECK_RELEASE_AND_LOG(" C_EX_FreeBuffer", rv == CKR_OK, rvToStr(rv), errorCode);exit:    return errorCode;}

Можно заметить, что работа с функциями расширения очень похожа на работу со стандартными функциями. Основное отличие лишь в том, что мы обращаемся к другому списку функций CK_FUNCTION_LIST_EXTENDED_PTR. Создание запроса на сертификат происходит в одну строчку функцией C_EX_CreateCSR и возвращает запрос в DER-формате. Также стоит обратить внимание, что память, выделенную внутри библиотеки, следует высвобождать с помощью функции C_EX_FreeBuffer.

По полученному запросу можно получить сертификат в Удостоверяющем центре. Например, воспользуемся тестовым УЦ КриптоПро для получения сертификата. Полученный сертификат необходимо скачать в DER-формате, сохранить в файле с именем "cert_2012-256.cer" и положить в директорию, из которой вы запускаете примеры. Полученный сертификат можно импортировать на токен.

Импорт сертификата на токен. Создание объекта вручную

Сертификаты на токене так же как и ключи являются объектами. Сертификат можно импортировать на токен, создав объект с нужными атрибутами. Для этого мы реализовали функцию: import_cert:

int import_cert(CK_SESSION_HANDLE session){    CK_OBJECT_CLASS certificateObject = CKO_CERTIFICATE;    CK_BYTE keyPairIdGost2012_256[] = { "GOST R 34.10-2012 (256 bits) sample key pair ID (Aktiv Co.)" };    CK_BBOOL attributeTrue = CK_TRUE;    CK_BBOOL attributeFalse = CK_FALSE;    CK_CERTIFICATE_TYPE certificateType = CKC_X_509;    CK_ULONG tokenUserCertificate = 1;    /*************************************************************************    * Шаблон для импорта сертификата                                         *    *************************************************************************/    CK_ATTRIBUTE certificateTemplate[] =    {        { CKA_VALUE, 0, 0 },                                                               // Значение сертификата (заполняется в процессе работы)        { CKA_CLASS, &certificateObject, sizeof(certificateObject) },                      // Класс - сертификат        { CKA_ID, &keyPairIdGost2012_256, sizeof(keyPairIdGost2012_256) - 1 },             // Идентификатор сертификата (совпадает с идентификатором соответствующего ключа)        { CKA_TOKEN, &attributeTrue, sizeof(attributeTrue) },                              // Сертификат является объектом токена        { CKA_PRIVATE, &attributeFalse, sizeof(attributeFalse) },                          // Сертификат доступен без аутентификации        { CKA_CERTIFICATE_TYPE, &certificateType, sizeof(certificateType) },               // Тип сертификата - X.509        { CKA_CERTIFICATE_CATEGORY, &tokenUserCertificate, sizeof(tokenUserCertificate) }, // Категория сертификата - пользовательский    };    FILE* certFile;                                   // Поток ввода сертификата    CK_BYTE_PTR certDer;                              // Массив с сертификатом в DER формате    CK_ULONG certSize;                                // Размер массива сертификата    CK_OBJECT_HANDLE certificate;                     // Хэндл сертификата    CK_RV rv;    int r;    int errorCode = 1;                                // Флаг ошибки    /*************************************************************************    * Открыть поточный ввод сертификата из файла                             *    *************************************************************************/    certFile = fopen("cert_2012-256.cer", "rb");    CHECK_AND_LOG(" fopen", certFile != NULL, "\"cert_2012-256.cer\" doesn't exist", exit);    /*************************************************************************    * Определить размер файла, содержащего сертификат                        *    *************************************************************************/    r = fseek(certFile, 0, SEEK_END);    CHECK(" fseek", r == 0, close_certFile);    certSize = ftell(certFile);    CHECK(" ftell", certSize > 0, close_certFile);    r = fseek(certFile, 0, SEEK_SET);    CHECK(" fseek", r == 0, close_certFile);    /*************************************************************************    * Выделить память для сертификата                                        *    *************************************************************************/    certDer = (CK_BYTE_PTR)malloc(certSize);    CHECK(" malloc", certDer != NULL, close_certFile);    /*************************************************************************    * Прочитать сертификат                                                   *    *************************************************************************/    r = (int)fread(certDer, 1, (int)certSize, certFile);    CHECK(" fread", r == (int)certSize, free_certificate);    /*************************************************************************    * Задать шаблон сертификата для импорта                                  *    *************************************************************************/    certificateTemplate[0].pValue = certDer;    certificateTemplate[0].ulValueLen = certSize;    /*************************************************************************    * Создать сертификат на токене                                         *    *************************************************************************/    rv = functionList->C_CreateObject(session, certificateTemplate, arraysize(certificateTemplate), &certificate);    CHECK_AND_LOG(" C_CreateObject", rv == CKR_OK, rvToStr(rv), free_certificate);    errorCode = 0;    printf("Certificate has been created successfully\n");    /*************************************************************************    * Очистить память из-под строки с сертификатом                           *    *************************************************************************/free_certificate:    free(certDer);    /*************************************************************************    * Закрыть поток ввода сертификата                                        *    *************************************************************************/close_certFile:    r = fclose(certFile);    CHECK_RELEASE(" fclose", r == 0, errorCode);exit:    return errorCode;}

На этот раз мы создали объект напрямую с помощью функции C_CreateObject. Для создания объекта сертификата мы аналогично передали шаблон с атрибутами объекта: тело сертификата, идентификатор, тип доступа, тип сертификата и т.п.

Функцию C_CreateObject можно использовать не только для создания сертификата на токене, но и для создания других объектов, например, публичных ключей (CKO_PUBLIC_KEY), закрытых ключей (CKO_PRIVATE_KEY), симметричных ключей (CKO_SECRET_KEY), обычных данных (CKO_DATA). Их значение будет также содержаться внутри атрибута CKA_VALUE.

Теперь, когда у нас на токене имеется и ключевая пара и сертификат, для закрепления материала рассмотрим операцию создания CMS-подписи.

Формирование 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("%s\n", 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 с идеей объектного подхода.

Получение и установка атрибутов публичных объектов

В завершение главы про объекты хотелось бы отметить, что атрибуты публичных объектов можно получать и изменять. Делается это с помощью функций C_SetAttributeValue и C_GetAttributeValue.

Логика работы с функцией C_SetAttributeValue очень похожа на логику создания объектов создаётся шаблон, атрибуты заполняются указанными значениями. Все это передаётся на вход функции C_SetAttributeValue. Изменять атрибуты можно, если у объекта атрибут CKA_MODIFIABLE равен CK_TRUE.

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

Для демонстрации работы функций C_GetAttributeValue и C_SetAttributeValue рассмотрим пример получения тела сертификата и изменения текстовой метки сертификата:

int get_cert(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE cert){    CK_BYTE_PTR body = NULL_PTR;    CK_ATTRIBUTE template[] = {        {CKA_VALUE, NULL_PTR, 0}    };    char* certPem;    CK_RV rv;    int errorCode=1;        /*************************************************************************        * Получение размера тела сертификата                                     *        *************************************************************************/    rv = functionList->C_GetAttributeValue(session, cert, template, arraysize(template));    CHECK_AND_LOG(" C_GetAttributeValue", rv == CKR_OK, rvToStr(rv), exit);    body = (CK_BYTE_PTR) malloc(template[0].ulValueLen);    template[0].pValue = body;        /*************************************************************************        * Получение тела сертификата в формате DER                               *        *************************************************************************/    rv = functionList->C_GetAttributeValue(session, cert, template, arraysize(template));    CHECK_AND_LOG(" C_GetAttributeValue", rv == CKR_OK, rvToStr(rv), free);        /*************************************************************************        * Сконвертировать и распечатать буфер в формате PEM                      *        *************************************************************************/        GetCertAsPem(body, template[0].ulValueLen, &certPem);        CHECK(" Get cert in PEM format", certPem != NULL, free);        printf("\nCertificate request is:\n");        printf("%s\n", certPem);    errorCode = 0;        printf("Getting cert body has been completed successfully.\n");free_cert_pem:        free(certPem);free:    free(body);exit:    return errorCode;}int set_cert_label(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE cert){    CK_UTF8CHAR label[] = {"GOST certificate"};    CK_ATTRIBUTE template[] = {        CKA_LABEL, label, sizeof(label)-1    };    CK_RV rv;    int errorCode = 1;        /*************************************************************************        * Установка метки сертификата                                            *        *************************************************************************/    rv = functionList->C_SetAttributeValue(session, cert, template, arraysize(template));    CHECK_AND_LOG(" C_SetAttributeValue", rv == CKR_OK, rvToStr(rv), exit);    errorCode = 0;exit:    return errorCode;}

Про механизмы

Мы уже ранее встречались с механизмами в примерах и дали краткое описание. Давайте теперь опишем их подробнее. Описание механизмов в PKCS#11 было вынесено в отдельный документ, с которым можно ознакомиться здесь. В этом документе написано:

A mechanism specifies precisely how a certain cryptographic process is to be performed. PKCS #11 implementations MAY use one of more mechanisms defined in this docuоment.

Отсюда следует, что механизмы:

  • Определяют некоторое криптографическое преобразование.

  • PCKS#11 может использовать механизмы, определенные в этом документе.

Более того, некоторые PKCS#11-библиотеки могут использовать и другие механизмы.

Согласно документации механизмы можно разделить на:

  • Механизмы шифрования и расшифрования (Encrypt & Decrypt);

  • Механизмы подписи и проверки подписи (Sign & Verify);

  • Механизм формирования хеша (Digest);

  • Механизм восстановления подписи по публичному ключу (Sign Recover & Verify Recovery);

  • Механизм генерации симметричных и асимметричных ключей (Gen. Key/Key Pair);

  • Экспорт и импорт ключей (Wrap & Unwrap);

  • Выработка общего ключа на основе асимметричных ключей (Derive).

Каждый механизм идентифицирует одну или несколько из этих функций. Так, например, ранее рассмотренный механизм CKM_GOSTR3410_KEY_PAIR_GEN предназначен для генерации ключей, а механизм CKM_AES_ECB может использоваться как для зашифрования/расшифрования, так и для свертки/развертки ключей.

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

Механизмы в 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_EncryptFinish не нужно, т.к. C_Encrypt внутри себя вызывает функцию C_EncryptUpdate, а после неё уже завершает процесс шифрования с помощью функции C_EncryptFinish.

Проверка поддержки механизмов

Ранее в примерах мы всегда надеялись на то, что токен поддерживает используемые механизмы, но, вообще говоря, это может быть не так. Так, например, Рутокен Lite не поддерживает криптографические механизмы. Поэтому перед началом работы с каким-либо механизмом желательно убедиться в том, что он поддерживается устройством. Это можно сделать с помощью функции C_GetMechanismList, которая возвращает список поддерживаемых механизмов токена. Напишем удобную обертку для проверки поддержки определенного механизма:

int mech_supports(CK_SLOT_ID slot, CK_MECHANISM_TYPE mech, int* mechIsSupported){    CK_MECHANISM_TYPE_PTR mechanisms;                 // Массив поддерживаемых механизмов    CK_ULONG mechanismCount;                          // Количество поддерживаемых механизмов    CK_RV rv;    int errorCode = 1;    /*************************************************************************    * Получить список поддерживаемых токеном механизмов                      *    *************************************************************************/    rv = functionList->C_GetMechanismList(slot, NULL_PTR, &mechanismCount);    CHECK_AND_LOG(" C_GetMechanismList (number of mechanisms)", rv == CKR_OK, rvToStr(rv), exit);    CHECK_AND_LOG(" Checking mechanisms available", mechanismCount > 0, " No mechanisms available", exit);    mechanisms = (CK_MECHANISM_TYPE_PTR)malloc(mechanismCount * sizeof(CK_MECHANISM_TYPE));    CHECK(" Memory allocation for mechanisms", mechanisms != NULL_PTR, exit);    rv = functionList->C_GetMechanismList(slot, mechanisms, &mechanismCount);    CHECK_AND_LOG(" C_GetMechanismList", rv == CKR_OK, rvToStr(rv), free_mechanisms);    /*************************************************************************    * Определение поддерживаемых токеном механизмов                          *    *************************************************************************/    for (size_t i = 0; i < mechanismCount; ++i) {        if (mechanisms[i] == mech) {            *mechIsSupported = 1;            break;        }    }    errorCode = 0;    if (*mechIsSupported)        printf("Mechanism is supported\n");    else        printf("Mechanism is not supported\n");free_mechanisms:    free(mechanisms);exit:}

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

Утилита pkcs11-tool

Часто бывает необходимо просто и быстро выполнить какое-либо обращение к PKCS#11-библиотеке, не прибегая к написанию кода. На помощь может прийти утилита pkcs11-tool, которая распространяется в составе пакета (opensc)[https://github.com/OpenSC/OpenSC].

Утилита pkcs11-tool может гибко выполнять огромное количество стандартных PKCS#11-операций. Например:

  • Генерация ключевых пар:
    pkcs11-tool --module /usr/lib/librtpkcs11ecp.so --keypairgen --key-type GOSTR3410-2012-256:B --id 45 -l

  • Создание сырой подписи:
    pkcs11-tool --module /usr/lib/librtpkcs11ecp.so --sign --id 45 -l -i file.txt --mechanism GOSTR3410-WITH-GOSTR3411-12-256

  • Генерация псевдослучайных последовательностей:
    pkcs11-tool --module /usr/lib/librtpkcs11ecp.so --generate-random 16

  • Получение списка объектов на токене:
    pkcs11-tool --module /usr/lib/librtpkcs11ecp.so -O

  • Получение механизмов, поддерживаемых токеном:
    pkcs11-tool --module /usr/lib/librtpkcs11ecp.so -M

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

Дополнительный материал

Если вы захотите узнать, как работать с более специфичными функциями, то большое количество примеров по работе с PKCS#11 можно найти в нашем SDK. Все примеры по работе с PKCS#11-библиотекой находятся в директории sdk\pkcs11\samples.

Выводы

Зачастую библиотека PKCS#11 является основным кирпичиком при написании приложений или других библиотек для работы со смарт-картами на других языках программирования. Поэтому знание основ работы с PKCS#11 на языке C может помочь разобраться с тем, как работать с обертками или как лучше реализовать архитектуру новой обертки.

Кроме PKCS#11 с объектами на смарт-картах/токенах можно работать через:

Если вы хотите узнать про какую-то реализацию подробнее, то напишите в комментариях и мы постараемся в ближайшее время выпустить новую статью с её разбором.

Подробнее..

Поддержка токенов PKCS11 с ГОСТ-криптографией в Python. Часть II Объекты класса Token

18.03.2021 18:13:59 | Автор: admin
imageВ предыдущей статье был представлен модуль pyp11, написанный на языке Си и обеспечивающий поддержку токенов PKCS#11 с российской криптографией. В этой статье будет рассмотрен класс Token, который позволит упростить использование функционала модуля pyp11 в скриптах, написанных на Python-е. Отметим, что в качестве прототипа этого класса был взят класс token, написанный на TclOO и который используется в утилите cryptoarmpkcs:


Прототип класса Token
oo::class create token {  variable libp11  variable handle  variable infotok  variable pintok  variable nodet#Конструктор  constructor {handlelp11 labtoken slottoken} {    global pass    global yespas    set handle $handlelp11    set slots [pki::pkcs11::listslots $handle]    array set infotok []    foreach slotinfo $slots {      set slotflags [lindex $slotinfo 2]      if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} {        if {[string first $labtoken [lindex $slotinfo 1]] != -1} {          set infotok(slotlabel) [lindex $slotinfo 1]          set infotok(slotid) [lindex $slotinfo 0]          set infotok(slotflags) [lindex $slotinfo 2]          set infotok(token) [lindex $slotinfo 3]          #Берется наш токен          break        }      }    }    #Список найденных токенов в слотах    if {[llength [array names infotok]] == 0 } {      error "Constructor: Token not present for library   : $handle"    }    #Объект какого токена    set nodet [dict create pkcs11_handle $handle]    dict set nodet pkcs11_slotid $infotok(slotid)    set tit "Введите PIN-код для токене $infotok(slotlabel)"    set xa [my read_password $tit]    if {$xa == "no"} {      error "Вы передумали вводить PIN для токена $infotok(slotlabel)"    }    set pintok $pass    set pass ""    set rr [my login ]    if { $rr == 0 } {      unset pintok      error "Проверьте PIN-код токена $infotok(slotlabel)."    } elseif {$rr == -1} {      unset pintok      error "Отсутствует токен."    }    my logout  }#Методы класса  method infoslot {} {    return [array get infotok]  }  method listcerts {} {    array set lcerts []    set certsder [pki::pkcs11::listcertsder $handle $infotok(slotid)]    #Перебираем сертификаты    foreach lc $certsder {      array set derc $lc      set lcerts($derc(pkcs11_label)) [list $derc(cert_der) $derc(pkcs11_id)]      #parray derc    }    return [array get lcerts]  }  method read_password {tit} {    global yespas    global pass    set tit_orig "$::labpas"    if {$tit != ""} {      set ::labpas "$tit"    }    tk busy hold ".st.fr1"    tk busy hold ".st.fr3"    #place .topPinPw -in .st.fr1.fr2_certs.labCert  -relx 1.0 -rely 3.0 -relwidth 3.5    place .topPinPw -in .st.labMain  -relx 0.35 -rely 5.0 -relwidth 0.30    set yespas ""    focus .topPinPw.labFrPw.entryPw    vwait yespas    catch {tk busy forget ".st.fr1"}    catch {tk busy forget ".st.fr3"}    if {$tit != ""} {      set ::labpas "$tit_orig"    }    place forget .topPinPw    return $yespas  }  unexport read_password  method rename {type ckaid newlab} {    if {$type != "cert" && $type != "key" && $type != "all"} {      error "Bad type for rename "    }    set uu $nodet    lappend uu "pkcs11_id"    lappend uu $ckaid    lappend uu "pkcs11_label"    lappend uu $newlab    if { [my login ] == 0 } {      unset uu      return 0    }    pki::pkcs11::rename $type $uu    my logout    return 1  }  method changeid {type ckaid newid} {    if {$type != "cert" && $type != "key" && $type != "all"} {      error "Bad type for changeid "    }    set uu $nodet    lappend uu "pkcs11_id"    lappend uu $ckaid    lappend uu "pkcs11_id_new"    lappend uu $newid    if { [my login ] == 0 } {      unset uu      return 0    }    pki::pkcs11::rename $type $uu    my logout    return 1  }  method delete {type ckaid} {    if {$type != "cert" && $type != "key" && $type != "all" && $type != "obj"} {      error "Bad type for delete"    }    set uu $nodet    lappend uu "pkcs11_id"    lappend uu $ckaid    my login    ::pki::pkcs11::delete $type $uu    my logout    return 1  }  method deleteobj {hobj} {    set uu $nodet    lappend uu "hobj"    lappend uu $hobj#tk_messageBox -title "class deleteobj" -icon info -message "hobj: $hobj\n" -detail "$uu"    return [::pki::pkcs11::delete obj $uu ]  }  method listmechs {} {    set llmech [pki::pkcs11::listmechs $handle $infotok(slotid)]    return $llmech  }  method pubkeyinfo {cert_der_hex} {    array set linfopk [pki::pkcs11::pubkeyinfo $cert_der_hex $nodet]    return [array get linfopk]  }  method listobjects {type} {    if {$type != "cert" && $type != "pubkey" && $type != "privkey" && $type != "all" && $type != "data"} {      error "Bad type for listobjects "    }    set allobjs [::pki::pkcs11::listobjects $handle $infotok(slotid) $type]    return $allobjs  }  method importcert {cert_der_hex cka_label} {    set uu $nodet    dict set uu pkcs11_label $cka_label    if {[catch {set pkcs11id [pki::pkcs11::importcert $cert_der_hex $uu]} res] } {      error "Cannot import this certificate:$res"      #          return 0    }    return $pkcs11id  }  method login {} {    set wh 1    set rl -1    while {$wh == 1} {      if {[catch {set rl [pki::pkcs11::login $handle $infotok(slotid) $pintok]} res]} {        if {[string first "SESSION_HANDLE_INVALID" $res] != -1} {          pki::pkcs11::closesession $handle          continue        } elseif {[string first "TOKEN_NOT_PRESENT" $res] != -1} {          set wh 0          continue        }      }      break    }    if {$wh == 0} {      return -1    }    return $rl  }  method logout {} {    return [pki::pkcs11::logout $handle $infotok(slotid)]  }  method keypair {typegost parkey} {    my login    set skey [pki::pkcs11::keypair $typegost $parkey $nodet]    my logout    return $skey  }  method digest {typehash source} {    return [pki::pkcs11::digest $typehash $source $nodet]  }  method signkey {ckm digest hobj_priv} {    set uu $nodet    dict set uu hobj_privkey $hobj_priv    my login    set ss [pki::pkcs11::sign $ckm $digest $uu]    my logout    return $ss  }  method signcert {ckm digest pkcs11_id} {    set uu $nodet    dict set uu pkcs11_id $pkcs11_id    my login    set ss  [pki::pkcs11::sign $ckm $digest $uu]    my logout    return $ss  }  method verify {digest signature asn1pubkey} {    set uu $nodet    dict set uu pubkeyinfo $asn1pubkey    return [pki::pkcs11::verify $digest $signature $uu]  }  method tokenpresent {} {    set slots [pki::pkcs11::listslots $handle]    foreach slotinfo $slots {      set slotid [lindex $slotinfo 0]      set slotlabel [lindex $slotinfo 1]      set slotflags [lindex $slotinfo 2]      if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} {        if {infotok(slotlabel) == $slotlabel && $slotid == $infotok(slotid)} {          return 1        }      }    }    return 0  }  method setpin {type tpin newpin} {    if {$type != "user" && $type != "so"} {      return 0    }    if {$type == "user"} {      if {$tpin != $pintok} {        return 0      }    }    set ret [::pki::pkcs11::setpin  $handle $infotok(slotid) $type $tpin $newpin]    if {$type == "user"} {      if {$ret} {        set pitok $newpin      }    }    return $ret  }  method inituserpin {sopin upin} {    set ret [::pki::pkcs11::inituserpin $handle $infotok(slotid) $sopin $upin]    return $ret  }  method importkey {uukey} {    set uu $nodet    append uu " $uukey"    my login    if {[catch {set impkey [pki::pkcs11::importkey $uu ]} res] } {        set impkey 0    }    my logout    return $impkey  }#Деструктор  destructor {    variable handle    if {[info exists pintok]} {      my login    }    #    ::pki::pkcs11::unloadmodule  $handle  }}

Класс Token, как и любой класс в Python, включает конструктор, методы и деструктор. Конструктор и деструктор это те же методы, только с предопределёнными именами. Конструктор имеет имя __init__, а деструктор имя __del__. Объявление конструктора и деструктора можно опускать. И в классе Token мы опустим объявление деструктора, а вот конструктор будет необходим. Конструктор будет создавать экземпляр класса Token для конкретного токена с конкретными атрибутами.

I. Конструктор класса Token


Итак, конструктор класса Token выглядит следующим образом:
import sysimport pyp11class Token:  def __init__ (self, handlelp11, slottoken, serialnum):    flags = ''    self.pyver = sys.version[0]    if (self.pyver == '2'):        print ('Только для python3')        quit()#Сохраняем handle библиотеки PKCS#11    self.handle = handlelp11#Сохраняем номер слота с токеном    self.slotid = slottoken#Сохраняем серийный номер токена    self.sn = serialnum#Проверяем наличие в указанном слоте токена с заданным серийным номером    ret, stat = self.tokinfo()#Проверяем код возврата    if (stat != ''):#Возвращаем информацию об ошибке        self.returncode = stat        return#Экземпляр класса (объект) успешно создан


Параметрами конструктора (метода __init__) являются (помимо обязательного self) handle библиотеки токена (handlelp11), номер слота (slottoken), в котором должен находиться токен, и серийный номер токена (serialnum).
Для получения handle библиотеки pkcs#11, номеров слотов и информации о находящихся в них токенах можно использовать следующий скрипт:
#!/usr/bin/python3import sysimport pyp11from Token import Tokendef listslots (handle):    slots = pyp11.listslots(aa)    i = 0    lslots = []    for v in slots:        for f in v[2]:        if (f == 'TOKEN_PRESENT'):                i = 1                lslots.append(v)                break    i += 1    return (lslots)#Библиотеки для Linux#Программный токенlib = '/usr/local/lib64/libls11sw2016.so'#Облачный токен#lib = '/usr/local/lib64/libls11cloud.so'#Аппаратный токен#lib = '/usr/local/lib64/librtpkcs11ecp_2.0.so'#Библиотеки для Windows#lib='C:\Temp\ls11sw2016.dll'try:#Вызываем команду загрузки библиотеки и получаем её handle (дескриптор библиотеки)    aa = pyp11.loadmodule(lib)    print('Handle библиотеки ' + lib + ': ' + aa)except:    print('Except load lib: ')    e = sys.exc_info()[1]    e1 = e.args[0]#Печать ошибки    print (e1)    quit()#Список слотовslots = listslots(aa)i = 0for v in slots:    for f in v[2]:        if (f == 'TOKEN_PRESENT'):        if (i == 0):            print ('\nИнформация о токенах в слотах\n')        it = v[3]        print ('slotid=' + str(v[0]))        print ('\tFlags=' + str(v[2]))        print ('\tLabel="' + it[0].strip() + '"')        print ('\tManufacturer="' + it[1].strip() + '"')        print ('\tModel="' + it[2].strip() + '"')        print ('\tSerialNumber="' + it[3].strip() + '"')        i = 1        break    i += 1pyp11.unloadmodule(aa)if (i == 0):    print ('Нет ни одного подключенного токена. Вставьте токен и повторите операцию')quit()

Если с библиотекой и слотом всё ясно, то с серийным номером токена может возникнуть вопрос а зачем этот параметр нужен и почему именно он, а, например, не метка токена. Сразу оговоримся, что это принципиально для извлекаемых токенов, когда злоумышленником (или случайно) один токен в слоте будет заменён другим токеном. Более того, различные экземпляры токена могут иметь одинаковые метки. И наконец, токен может быть еще не проинициализирован или владелец будет его переинициализировать, в частности, сменит метку токена. Теоретически даже серийный номер не гарантирует его идентичность, оптимально учитывать всю информацию о токене (серийный номер, модель, производитель). В задачи конструктора и входит сохранение в переменных создаваемого экземпляра класса аргументов объекта токен:
...#Сохраняем handle библиотеки PKCS#11    self.handle = handlelp11#Сохраняем номер слота с токеном    self.slotid = slottoken#Сохраняем серийный номер токена    self.sn = serialnum...

Проверкой наличия указанного токена в указанном слоте занимается метод tokinfo(), определенный в данном классе.
Метод tokinfo возвращает два значения (см. выше в конструкторе):
#Проверяем наличие в указанном слоте токена с заданным серийным номером    ret, stat = self.tokinfo()

В первой переменной (ret) содержится результат выполнения метода, а во второй (stat) информация о том, как завершилось выполнение метода. Если вторая переменная пуста, то метод tokinfo успешно выполнился. Если вторая переменная не пуста, то выполнение метода завершилось с ошибкой. Информация об ошибке будет находиться в этой переменной. При обнаружении ошибки выполнения метода self.tokinfo конструктор записывает её в переменную returncode:
#Проверяем наличие в указанном слоте токена с заданным серийным номером    ret, stat = self.tokinfo()#Проверяем код возврата    if (stat != ''):#Возвращаем информацию об ошибке в переменной returncode        self.returncode = stat        return

После создания объекта (экземпляра класса) необходимо проверить значение переменной returncode, чтобы быть уверенным в том, что объект для указанного токена создан:
#!/usr/bin/python3import sysimport pyp11from Token import Token#Выбираем библиотеку#Аппаратный токенlib = '/usr/local/lib64/librtpkcs11ecp_2.0.so'try:    aa = pyp11.loadmodule(lib)except:    e = sys.exc_info()[1]    e1 = e.args[0]    print (e1)    quit()#Серийный номер токенаsn = '9999999999999999'slot = 110#Создаем объект токенаt1 = Token(aa, slot, sn)#Проверка переменной returncodeif (t1.returncode != ''):#Объект создан с ошибкой    print (t1.returncode)#Уничтожение объекта    del t1#Завершение скрипта    quit()#объект успешно создан. . .

Если обнаружена ошибка при создании объекта, то целесообразно этот объект уничтожить:
del <идентификатор объекта>

II. Архитектура методов в классе Token


Главным принципом при написании методов было то, чтобы обработка исключений была внутри методов, а информация об исключениях (ошибках) возвращалась в текстовом виде. Исходя из этого все методы возвращают два значения: собственно результат выполнения и информацию об ошибке. Если ошибок нет, то второе значение пустое. Мы это уже видели на примере использования метода tokinfo в конструкторе. А вот и сам код метода tokinfo:
  def tokinfo(self):    status = ''#Получаем список слотов    try:        slots = pyp11.listslots(self.handle)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        dd = ''        status = e1        return (dd, status)    status = ''#Ищем заданный слот с указанным  токеном#Перебираем слоты    for v in slots:#Ищем заданный слот            if (v[0] != self.slotid):                status = "Ошибочный слот"                continue            self.returncode = ''#Список флагов текущего слота            self.flags = v[2]#Проверяем наличие в стоке токена            if (self.flags.count('TOKEN_PRESENT') !=0):#Проверяем серийный номер токена                tokinf = v[3]                sn = tokinf[3].strip()                if (self.sn != sn):                    status = 'Серийный номер токена=\"' + sn + '\" не совпадает с заданным \"' + self.sn + '\"'                    dd = ''                    return (dd, status)                status = ''                break            else:                dd = ''                status = "В слоте нет токена"                return (dd, status)    tt = tokinf    dd = dict(Label=tt[0].strip())    dd.update(Manufacturer=tt[1].strip())    dd.update(Model=tt[2].strip())    dd.update(SerialNumber=tt[3].strip())    self.infotok = dd#Возвращаемые значения    return (dd, status)

Полное описание класса Token находится здесь.
import sysimport pyp11class Token:  def __init__ (self, handlelp11, slottoken, serialnum):    flags = ''    self.pyver = sys.version[0]    if (self.pyver == '2'):        print ('Только для python3')        quit()#Сохраняем handle библиотеки PKCS#11    self.handle = handlelp11#Сохраняем номер слота с токеном    self.slotid = slottoken#Сохраняем серийный номер токена    self.sn = serialnum#Проверяем наличие в указанном слоте с токена с заданным серийным номером    ret, stat = self.tokinfo()#Проверяем код возврата    if (stat != ''):#Возвращаем информацию об ошибке        self.returncode = stat        return#Экземпляр класса (объект) успешно создан  def tokinfo(self):    status = ''#Получаем список слотов    try:        slots = pyp11.listslots(self.handle)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        dd = ''        status = e1        return (dd, status)    status = ''#Ищем заданный слот с указанным  токеном#Перебираем слоты    for v in slots:#Ищем заданный слот            if (v[0] != self.slotid):                status = "Ошибочный слот"                continue            self.returncode = ''#Список флагов текущего слота            self.flags = v[2]#Проверяем наличие в стоке токена            if (self.flags.count('TOKEN_PRESENT') !=0):#Проверяем серийный номер токена                tokinf = v[3]                sn = tokinf[3].strip()                if (self.sn != sn):                    status = 'Серийный номер токена=\"' + sn + '\" не совпадает с заданным \"' + self.sn + '\"'                    dd = ''                    return (dd, status)                status = ''                break            else:                dd = ''                status = "В слоте нет токена"                return (dd, status)    tt = tokinf    dd = dict(Label=tt[0].strip())    dd.update(Manufacturer=tt[1].strip())    dd.update(Model=tt[2].strip())    dd.update(SerialNumber=tt[3].strip())    self.infotok = dd    return (dd, status)  def listcerts(self):    try:        status = ''        lcerts = pyp11.listcerts(self.handle, self.slotid)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        lcerts = ''        status = e1    return (lcerts, status)  def listobjects(self, type1, value = '' ):    try:        status = ''        if (value == ''):        lobjs = pyp11.listobjects(self.handle, self.slotid, type1)        else:        lobjs = pyp11.listobjects(self.handle, self.slotid, type1, value)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        lobjs = ''        status = e1    return (lobjs, status)  def rename(self, type, pkcs11id, label):    try:        status = ''        dd = dict(pkcs11_id=pkcs11id, pkcs11_label=label)        ret = pyp11.rename(self.handle, self.slotid, type, dd)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        ret = ''        status = e1    return (ret, status)  def changeckaid(self, type, pkcs11id, pkcs11idnew):    try:        status = ''        dd = dict(pkcs11_id=pkcs11id, pkcs11_id_new=pkcs11idnew)        ret = pyp11.rename(self.handle, self.slotid, type, dd)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        ret = ''        status = e1    return (ret, status)  def login(self, userpin):    try:        status = ''        bb = pyp11.login (self.handle, self.slotid, userpin)    except:        e = sys.exc_info()[1]        e1 = e.args[0]        bb = 0        status = e1    return (bb, status)  def logout(self):    try:        status = ''        bb = pyp11.logout (self.handle, self.slotid)    except:        e = sys.exc_info()[1]        e1 = e.args[0]        bb = 0        status = e1    return (bb, status)  def keypair(self, typek, paramk, labkey):#Параметры для ключей    gost2012_512 = ['1.2.643.7.1.2.1.2.1', '1.2.643.7.1.2.1.2.2', '1.2.643.7.1.2.1.2.3']    gost2012_256 = ['1.2.643.2.2.35.1', '1.2.643.2.2.35.2',  '1.2.643.2.2.35.3',  '1.2.643.2.2.36.0', '1.2.643.2.2.36.1', '1.2.643.7.1.2.1.1.1', '1.2.643.7.1.2.1.1.2', '1.2.643.7.1.2.1.1.3', '1.2.643.7.1.2.1.1.4']    gost2001 = ['1.2.643.2.2.35.1', '1.2.643.2.2.35.2',  '1.2.643.2.2.35.3',  '1.2.643.2.2.36.0', '1.2.643.2.2.36.1']#Тип ключа    typekey = ['g12_256', 'g12_512', 'gost2001']    genkey = ''    if (typek == typekey[0]):    gost = gost2012_256    elif (typek == typekey[1]):    gost = gost2012_512    elif (typek == typekey[2]):    gost = gost2001    else:    status = 'Неподдерживаемый тип ключа'    return (genkey, status)    if (gost.count(paramk) == 0) :    status = 'Неподдерживаемые параметры ключа'    return (genkey, status)    try:#Ошибок нет, есть ключевая пара    status = ''    genkey = pyp11.keypair(self.handle, self.slotid, typek, paramk, labkey)    except:#Не удалось создать ключевую пару    e = sys.exc_info()[1]    e1 = e.args[0]    print (e1)#Возвращаеи текст ошибки в словаре    status = e1    return (genkey, status)     def digest(self, typehash, source):#Считаем хэш    try:        status = ''        digest_hex = pyp11.digest (self.handle, self.slotid, typehash, source)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в словаре    status = e1    digest_hex = ''    return (digest_hex, status)#Формирование подписи  def sign(self, ckmpair, digest_hex, idorhandle):#Для подписи можно использовать CKA_ID или handle закрытого ключа    try:        status = ''        sign_hex = pyp11.sign(self.handle, self.slotid, ckmpair, digest_hex, idorhandle)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в словаре    status = e1    sign_hex = ''    return (sign_hex, status)#Проверка подписи  def verify(self, digest_hex, sign_hex, pubkeyinfo):#Для подписи можно использовать CKA_ID или handle закрытого ключа    try:        status = ''        verify = pyp11.verify(self.handle, self.slotid, digest_hex, sign_hex, pubkeyinfo)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    verify = 0    status = e1    return (verify, status)#Инициализировать токен  def inittoken(self, sopin, labtoken):    try:        status = ''        dd = pyp11.inittoken (self.handle, self.slotid, sopin, labtoken)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = 0    status = e1    return (dd, status)#Инициализировать пользовательский PIN-код  def inituserpin(self, sopin, userpin):    try:        status = ''        dd = pyp11.inituserpin (self.handle, self.slotid, sopin, userpin)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = 0    status = e1    return (dd, status)#Сменить пользовательский PIN-код  def changeuserpin(self, oldpin, newpin):    try:        status = ''        dd = pyp11.setpin (self.handle, self.slotid, 'user', oldpin, newpin)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = 0    status = e1    self.closesession ()    return (dd, status)  def closesession(self):    try:        status = ''        dd = pyp11.closesession (self.handle)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = 0    status = e1    return (dd, status)  def parsecert(self, cert_der_hex):    try:        status = ''        dd = pyp11.parsecert (self.handle, self.slotid, cert_der_hex)    except:#Не удалось разобрать сертификат    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = ''    status = e1    return (dd, status)  def importcert(self, cert_der_hex, labcert):    try:        status = ''        dd = pyp11.importcert (self.handle, self.slotid, cert_der_hex, labcert)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = ''    status = e1    return (dd, status)  def delobject(self, hobject):    try:        status = ''        hobjc = dict(hobj=hobject)        dd = pyp11.delete(self.handle, self.slotid, 'obj', hobjc)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = ''    status = e1    return (dd, status)  def delete(self, type, pkcs11id):    if (type == 'obj'):        dd = ''        status = 'delete for type obj use nethod delobject'        return (dd, status)    try:        status = ''        idobj = dict(pkcs11_id=pkcs11id)        dd = pyp11.delete(self.handle, self.slotid, type, idobj)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = ''    status = e1    return (dd, status)  def listmechs(self):    try:        status = ''        dd = pyp11.listmechs (self.handle, self.slotid)    except:#Не удалось получить список механизмов токена    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = ''    status = e1    return (dd, status)


Рассмотрим импользование функционала модуля pyp11 и аналогичных операторов с использованием класса Token.
В последнем случае необходимо будет создать и объект токена:
<дескриптор объекта> = Token(<дескриптор библиоткети>, <номер слота>, <серийный номер>)if (<дескриптор объекта>.returncode != ''):   print('Ошибка при создании объекта:')#Печать ошибки   print(<дескриптор объекта>.returncode)#Уничтожение объекта   del <дескриптор объекта>   quit()

Начнем с инициализации токена:
try:    ret = pyp11.inittoken (<дескриптор библиоткети>, <номер слота>, <SO-PIN>, <метка токена>)except:#Не удалось проинициализировать токен    e = sys.exc_info()[1]    e1 = e.args[0]    print (e1)    quit()

Аналогичный код при использовании класса Token выглядит так (идентификатор объекта t1):
ret, stat = t1.inittoken(<SO-PIN>, <метка токена>)#Проверка корретности инициализацииif (stat != ''):   print('Ошибка при инициализации токена:')#Печать ошибки   print(stat)   quit()  

Далее мы просто дадим соответствие основных операторов модуля pyp11 и методов класса Token без обработки исключений и ошибок:
<handle> := <дескриптор библиотеки pkcs11><slot> := <дескриптор слота с токеном><error> := <переменная с текстом ошибки><ret> := <результат выполнения оператора><cert_der_hex> := <сертификат в DER-формате в HEX-кодировке>=================================================#Инициализация пользовательского PIN-кода<ret> = pyp11.inituserpin (<handle>, <slot>, <SO-PIN>, <USER-PIN>)<ret>, <error> = <идентификатор объекта>.inituserpin (<SO-PIN>, <USER-PIN>)#Смена USER-PIN кода<ret> = pyp11.setpin (<handle>, <slot>, 'user', <USER-PIN старый>, <USER-PIN новый>)<ret>, <error> = t1.changeuserpin (<USER-PIN старый>, <USER-PIN новый>)#Смена SO-PIN кода<ret> = pyp11.setpin (<handle>, <slot>, 'so', <SO-PIN старый>, <SO-PIN новый>)<ret>, <error> = t1.changesopin (<SO-PIN старый>, <SO-PIN новый>)#Login<ret> = pyp11.login (<handle>, <slot>, <USER-PIN>)<ret>, <error> = t1.login (<USER-PIN>)#Logout<ret> = pyp11.logout (<handle>, <slot>)<ret>, <error> = t1.logout ()#Закрытие сессии<ret> = pyp11.closesession (<handle>)<ret>, <error> = t1.closesession ()#Список сертификатов на токене<ret> = pyp11.listcerts (<handle>, <slot>)<ret>, <error> = t1.listcerts ()#Список объектов на токене<ret> = pyp11.listobjects (<handle>, <slot>, <'cert' | 'pubkey' | 'privkey' | 'data' | 'all'> [, 'value'])<ret>, <error> = t1.listobjects (<'cert' | 'pubkey' | 'privkey' | 'data' | 'all'> [, 'value'])#Разбор сертификата<ret> = pyp11.parsecert (<handle>, <slot>, <cert_der_hex>)<ret>, <error> = t1.parsecert(<cert_der_hex>)#Импорт сертификата<ret> = pyp11.importcert (<handle>, <slot>, <cert_der_hex>, <Метка сертификата>)<ret>, <error> = t1.importcert(<cert_der_hex>, <Метка сертификата>)#Вычисление хэша<ret> = pyp11.digest (<handle>, <slot>, <тип алгоритма>, <контент>)<ret>, <error> = t1.digest(<тип алгоритма>, <контент>)#Вычисление электронной подписи<ret> = pyp11.digest (<handle>, <slot>, <механизм подписи>, <хэш от контента>, <CKA_ID | handle закрытого ключа>)<ret>, <error> = t1.digest(<механизм подписи>, <хэш от контента>, <CKA_ID | handle закрытого ключа>)#Проверка электронной подписи<ret> = pyp11.verify (<handle>, <slot>, <хэш от контента>, <подпись>, <asn1-структура subjectpublickeyinfo в hex>)<ret>, <error> = t1.verify(<хэш от контента>, <подпись>, <asn1-структура subjectpublickeyinfo в hex>)#Генерация ключевой пары<ret> = pyp11.keypair (<handle>, <slot>, <тип ключа>, <OID криптопараметра>, <CKA_LABEL>)<ret>, <error> = t1.keypair(<тип ключа>, <OID криптопараметра>, <CKA_LABEL>)

III. Сборка и установка модуля pyp11 с классом Token


Сборка и установка модуля pyp11 с классом Token ничем не отличается от описанной в первой части.
Итак, скачиваем архив и распаковываем его. Заходим в папку PythonPKCS11 и выполняем команду установки:
python3 setup.py install

После установки модуля переходим в папку tests и запускаем тесты для модуля pyp11.
Для тестирования класса Token переходим в папку test/classtoken.
Для подключения модуля pyp11 и класса Token в скрипты достаточно добавить следующие операторы:
import pyp11from Token import Token


IV. Заключение


В ближайшее время должна появиться и третья часть статьи, в которой будет рассказано, как добавить поддержку российской криптографии в проект PyKCS11.

P.S. Хочу сказать спасибо svyatikov за то, что помог протестировать проект на платформе Windows.
Подробнее..

Поддержка токенов PKCS11 с ГОСТ-криптографией в Python. Часть II Обёртка PyKCS11

26.03.2021 18:19:00 | Автор: admin
image Подошло время рассказать как была добавлена поддержка поддержка российской криптографии в проект PyKCS11. Всё началось с того, что мне на глаза попалась переписка разработчика проекта PyKCS11 с потенциальными потребителями по поводу возможной поддержки алгоритмов ГОСТ Р 34.10-2012 в нём. В этой переписке автор PkCS11 сказал, что не собирается включать поддержку российских криптоалгоритмов до тех пор, пока они не будут стандартизованы.
Ту же самую мысль он выразил и мне, когда я предложил ему это сделать. И не просто сделать, а выслал соответствующий программный код:

image

После этого я посчитал возможным форкнуть код в свой репозиторий и внести в него соответствующие правки. Проект PyKCS11 с поддержкой российской криптографии находится здесь.

I. Добавляем поддержку российских криптоалгоритмов


Итак, что же было сделано. Я фактически последовал одному из советов автора проекта PyKCS11:
What I can propose you is to create a PyKCS11_GOST.py file with the constant names and functions you want in order to extend PyKCS11 with GOST support.
(Я могу предложить вам создать файл PyKCS11_GOST.py с именами констант и функциями, которыми вы хотите расширить PyKCS11 для поддержки ГОСТ.)

Все константы, утвержденные ТК-26 для PKCS#11, были сведены в один файл pkcs11t_gost.h, помещенный в папку src:
//ТК-26#define NSSCK_VENDOR_PKCS11_RU_TEAM 0xd4321000 #define NSSCK_VENDOR_PKSC11_RU_TEAM NSSCK_VENDOR_PKCS11_RU_TEAM#define CK_VENDOR_PKCS11_RU_TEAM_TC26 NSSCK_VENDOR_PKCS11_RU_TEAM#define CKK_GOSTR3410_512 0xd4321003UL#define CKK_KUZNYECHIK 0xd4321004UL#define CKK_MAGMA 0xd4321005UL#define CKK_GOSTR3410_256 0xd4321006UL#define CKP_PKCS5_PBKD2_HMAC_GOSTR3411_TC26_V1 0xd4321801UL#define CKP_PKCS5_PBKD2_HMAC_GOSTR3411_2012_256 0xd4321002UL#define CKP_PKCS5_PBKD2_HMAC_GOSTR3411_2012_512 0xd4321003UL#define CKM_GOSTR3410_512_KEY_PAIR_GEN0xd4321005UL#define CKM_GOSTR3410_5120xd4321006UL#define CKM_GOSTR3410_WITH_GOSTR34110x00001202#define CKM_GOSTR3410_WITH_GOSTR3411_12_2560xd4321008UL#define CKM_GOSTR3410_WITH_GOSTR3411_12_5120xd4321009UL#define CKM_GOSTR3410_12_DERIVE0xd4321007UL#define CKM_GOSR3410_2012_VKO_2560xd4321045UL#define CKM_GOSR3410_2012_VKO_5120xd4321046UL#define CKM_KDF_43570xd4321025UL#define CKM_KDF_GOSTR3411_2012_2560xd4321026UL#define CKM_KDF_TREE_GOSTR3411_2012_2560xd4321044UL#define CKM_GOSTR3410_PUBLIC_KEY_DERIVE0xd432100AUL#define CKM_LISSI_GOSTR3410_PUBLIC_KEY_DERIVE0xd4321037UL#define CKM_GOST_GENERIC_SECRET_KEY_GEN0xd4321049UL#define CKM_GOST_CIPHER_KEY_GEN0xd4321048UL#define CKM_GOST_CIPHER_ECB0xd4321050UL#define CKM_GOST_CIPHER_CBC0xd4321051UL#define CKM_GOST_CIPHER_CTR0xd4321052UL#define CKM_GOST_CIPHER_OFB0xd4321053UL#define CKM_GOST_CIPHER_CFB0xd4321054UL#define CKM_GOST_CIPHER_OMAC0xd4321055UL#define CKM_GOST_CIPHER_KEY_WRAP0xd4321059UL#define CKM_GOST_CIPHER_ACPKM_CTR0xd4321057UL#define CKM_GOST_CIPHER_ACPKM_OMAC0xd4321058UL#define CKM_GOST28147_PKCS8_KEY_WRAP0xd4321036UL#define CKM_GOST_CIPHER_PKCS8_KEY_WRAP0xd432105AUL#define CKM_GOST28147_CNT0xd4321825UL#define CKM_KUZNYECHIK_KEY_GEN0xd4321019UL#define CKM_KUZNYECHIK_ECB0xd432101AUL#define CKM_KUZNYECHIK_CBC0xd432101EUL#define CKM_KUZNYECHIK_CTR0xd432101BUL#define CKM_KUZNYECHIK_OFB0xd432101DUL#define CKM_KUZNYECHIK_CFB0xd432101CUL#define CKM_KUZNYECHIK_OMAC0xd432101FUL#define CKM_KUZNYECHIK_KEY_WRAP0xd4321028UL#define CKM_KUZNYECHIK_ACPKM_CTR0xd4321042UL#define CKM_KUZNYECHIK_ACPKM_OMAC0xd4321043UL#define CKM_MAGMA_KEY_GEN0xd432102AUL#define CKM_MAGMA_ECB0xd4321018UL#define CKM_MAGMA_CBC0xd4321023UL#define CKM_MAGMA_CTR0xd4321020UL#define CKM_MAGMA_OFB0xd4321022UL#define CKM_MAGMA_CFB0xd4321021UL#define CKM_MAGMA_OMAC0xd4321024UL#define CKM_MAGMA_KEY_WRAP0xd4321029UL#define CKM_MAGMA_ACPKM_CTR0xd4321040UL#define CKM_MAGMA_ACPKM_OMAC0xd4321041UL#define CKM_GOSTR3411_12_2560xd4321012UL#define CKM_GOSTR3411_12_5120xd4321013UL#define CKM_GOSTR3411_12_256_HMAC0xd4321014UL#define CKM_GOSTR3411_12_512_HMAC0xd4321015UL#define CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC0xd4321035UL#define CKM_TLS_GOST_KEY_AND_MAC_DERIVE0xd4321033UL#define CKM_TLS_GOST_PRE_MASTER_KEY_GEN0xd4321031UL#define CKM_TLS_GOST_MASTER_KEY_DERIVE0xd4321032UL#define CKM_TLS_GOST_PRF0xd4321030UL#define CKM_TLS_GOST_PRF_2012_2560xd4321016UL#define CKM_TLS_GOST_PRF_2012_5120xd4321017UL#define CKM_TLS_TREE_GOSTR3411_2012_2560xd4321047UL

В этот перечень вошли механизмы как необходимые для формирования и проверки подписи по (ГОСТ Р 34.10-2012) ГОСТ Р 34.10-2012, так и шифрования (ГОСТ Р 34.12-2015 и ГОСТ Р 34.13-2015 алгоритмы шифрования Кузнечик и Магма). Естественно, здесь же присутствуют и алгоритмы хэширования ГОСТ Р 34.11-2012.
Для того, чтобы ГОСТ-овые константы попали в процесс сборки модуля, необходимо добавить в файл pkcs11.i (файл для SWIG) оператор включения файла pkcs11t_gost.h
%include "pkcs11t_gost.h"

перед оператором
%include "pkcs11lib.h"

Но это еще не всё. В методе getMechanismList (script PKCS11/__init__.py) заблокирован вывод механизмов чей код больше CKM_VENDOR_DEFINED (именно об этом и пишет автор проекта PyKCS11) (0x80000000L). Заметим, что ГОСТ-овые константы для новых алгоритмов попадают под это ограничение. Необходимо его снять хотя бы для ГОСТ-ов, заменим код метода getMechanismList на новый:
    def getMechanismList(self, slot):        """        C_GetMechanismList        :param slot: slot number returned by :func:`getSlotList`        :type slot: integer        :return: the list of available mechanisms for a slot        :rtype: list        """        mechanismList = PyKCS11.LowLevel.ckintlist()        rv = self.lib.C_GetMechanismList(slot, mechanismList)        if rv != CKR_OK:            raise PyKCS11Error(rv)        m = []#Правки для ГОСТ#define NSSCK_VENDOR_PKCS11_RU_TEAM 0xd4321000         for x in range(len(mechanismList)):            mechanism = mechanismList[x]            if mechanism >= CKM_VENDOR_DEFINED:                if mechanism >= CKM_VENDOR_DEFINED and mechanism < 0xd4321000:                    k = 'CKM_VENDOR_DEFINED_0x%X' % (mechanism - CKM_VENDOR_DEFINED)                    CKM[k] = mechanism                    CKM[mechanism] = k            m.append(CKM[mechanism])        return m#ORIGINAL#        for x in range(len(mechanismList)):#            mechanism = mechanismList[x]#            if mechanism >= CKM_VENDOR_DEFINED:#                k = 'CKM_VENDOR_DEFINED_0x%X' % (mechanism - CKM_VENDOR_DEFINED)#                CKM[k] = mechanism#                CKM[mechanism] = k#            m.append(CKM[mechanism])#        return m


Отметим также, что несмотря на то, что в модуль включены все механизмы, которые определены во включаемых файлах pkcs11t.h и pkcs11t_gost.h для pkcs11 v.2.40, все эти механизмы могут быть выполнены. Проблема состоит в том, что для некоторых из них требуется определенная структура параметров. Это, в частности, относится к механизму CKM_RSA_PKCS_OAEP, которому требуются параметры в виде структуры CK_RSA_PKCS_OAEP_PARAMS, и механизму CKM_PKCS5_PBKD2, который ждет параметров в виде структуры CK_PKCS5_PBKD2_PARAMS. Есть и другие механизмы. Но поскольку автор реализовал отдельные структуры для отдельных механизмов (для того же CKM_RSA_PKCS_OAEP), то не составит труда реализовать поддержку структур параметров и для других механизмов. Так, если кому потребуется работа с контейнером PKCS#12, то придется реализовать поддержку структуры CK_PKCS5_PBKD2_PARAMS.
Всё это относится к довольно сложным криптографическим механизмам.
А вот всё то, что касается хэширования, формирования проверки электронной подписи, наконец, шифрования, то всё работает замечательно. Но для начала надо собрать проект

II. Сборка обертки PyKCS11 с поддержкой ГОСТ-ов


Она ничем не отличается от сборки родной обёртки PkCS11 за исключением того, что исходный код необходимо получить здесь.
Далее следуем инструкции по сборке и установке пакета PyKCS11.
Для тестирования потребуется токен с поддержкой российской криптографии. Здесь мы имеем в виду ГОСТ Р 34.10-2012 и ГОСТ Р 34.11-2012. Это может быть как аппаратный токен, например RuTokenECP-2.0, так и программные или облачные токены.
Установить программный токен или получить доступ к облачному токену можно, воспользовавшись утилитой cryptoarmpkcs.
Скачать утилиту cryptoarmpkcs можно здесь.
Скачать утилиту cryptoarmpkcs можно здесь.

После запуска утилиты необходимо зайти на вкладку Создать токены:

image

На вкладке можно найти инструкции для получения и установки токенов.

II. Тестирование российских алгоритмов

Для тестирования можно использовать скрипты, которые лежат в папке testGost:
  • ckm_kuznyechik_cbc.py
  • ckm_gostr3411_12_256.py
  • ckm_gostr3410_with_gostr3411_12_256.py
  • ckm_gostr3410_512.py

Для тестирования исходные данные брались как из соответствующих ГОСТ-ов, так и из рекомендаций ТК-26.
В данных скриптах тестируются следующие механизмы:
1. Генерация ключевых пар:
  • CKM_GOSTR3410_512_KEY_PAIR_GEN (ГОСТ Р 34.10-2012 с длиной ключа 1024 бита)
  • CKM_GOSTR3410_KEY_PAIR_GEN (ГОСТ Р 34.10-2012 с длиной ключа 512 бит)

2. Формирование и проверка электронной подписи:
  • CKM_GOSTR3410
  • CKM_GOSTR3410_512
  • CKM_GOSTR3410_WITH_GOSTR3411_12_256

3. Хэширования:
  • CKM_GOSTR3411_12_256

4. Шифрование/расшифровка
  • CKM_KUZNYECHIK_CBC


Генерация ключевых пар позволяет владельцу токена получить закрытый ключ, которым он может подписать, например, запрос на сертификат. Запрос на сертификат может быть отправлен в удостоверяющий центр и там по нему могут выдать сертификат. Владелец сертификата может импортировать его на токен, где хранится закрытый ключ. Теперь у владельца токена есть личный сертификат с закрытым ключом, который он может использовать для подписи документы.
Ну а если ему требуется особый режим секретности, то он может зашифровать документ по одному из алгоритмов, а именно Магме или Кузнечику. Всё это конечно в том случае, если сам токен поддерживает эти механизмы, пакет PyKCS11 является всего лишь посредником.
На этом наше повествование, связанное с поддержкой в Python токенов с российской криптографией заканчивается.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru