Руководства
ESMART PKI Client — Руководство пользователя
Руководство пользователя для ESMART PKI Client, который предназначен для работы со смарт-картами или USB- ключами ESMART Token и ESMART Token ГОСТ. Программа позволяет выполнить все необходимые операции со смарт-картами или USB-ключами в удобном графическом интерфейсе
СКАЧАТЬ
ESMART PKI Client — Руководство администратора
Руководство администратора для ESMART PKI Client, который предназначен для работы со смарт-картами или USB- ключами ESMART Token и ESMART Token ГОСТ. Программа позволяет выполнить все необходимые операции со смарт-картами или USB-ключами в удобном графическом интерфейсе
СКАЧАТЬ
Общие вопросы
Я купил новый USB-токен или смарт-карту ESMART Token и не знаю PIN-код
По умолчанию на устройствах ESMART Token заданы следующие значения:
— PIN-код пользователя — 12345678
— SO-PIN (PIN-код администратора) — 12345678
Я забыл PIN-код
При наличии SO-PIN (PIN-кода администратора) Вы можете разблокировать PIN-код пользователя при помощи программного обеспечения ESMART PKI Client.
Если утерян SO-PIN (PIN-код администратора), но имеется PIN-код пользователя, Вы можете продолжить использование ESMART Token как пользователь (удаление, запись, просмотр).
При утере SO-PIN и PIN-кода пользователя, использование или возврат к заводским настройкам
невозможны!
На токене/смарт-карте имеются контейнеры, созданные при помощи СКЗИ «КриптоПРО CSP», они отображаются в ESMART PKI Client, но я ничего не могу подписать
СКЗИ «КриптоПРО CSP» использует для хранения информации на токенах и смарт-картах отдельные области и работает с ними через собственные библиотеки. В последних версиях ESMART PKI Client реализована возможность отображения контейнеров, созданных при помощи СКЗИ «КриптоПРО CSP» (в имени таких контейнеров стоит отметка [CRYPTO-PRO]), однако эта функция имеет лишь информационный характер. Произвести какие-либо операции с такими контейнерами с помощью ESMART PKI Client или приложений, работающих через PKCS#11, не представляется возможным.
Решения для Windows
Записанный в УЦ ФНС России контейнер с сертификатом не отображается в КриптоПро CSP
Данная особенность связана с переводом поддержки носителей с неизвлекаемыми ключами ESMART Token ГОСТ с интерфейса READER на PKCS#11. Для регистрации поддержки PKCS#11 необходимо переподключить модуль PKCS#11:
Панель управления — КриптоПро CSP — Запустить с правами администратора — Оборудование — Настроить считыватели — Считыватель смарт-карт PKCS#11 — Удалить — Добавить — Считыватель смарт-карт PKCS#11.
Рекомендуем отмечать галочками только те носители, которые Вы действительно планируете использовать на данной рабочей станции, иначе в системном журнале будут множество ошибок загрузки библиотек.
Краткая видеоинструкция по добавлению считывателя в КриптоПро CSP 5.0.
Более подробная инструкция приведена на сайте компании КриптоПро
При попытке записать контейнер КриптоПро на ESMART Token 64K в КриптПро CSP возникает ошибка: «Ошибка 0х80090023: Токен безопасности не имеет доступного места для хранения дополнительного контейнера.»
Данная ошибка может возникнуть при записи на токен контейнера, содержащего цепочку из трёх и более сертификатов. Сначала убедитесь, что на токене отсутствуют контейнеры КриптоПро, при необходимости инициализируйте токен. Запустите от имени Администратора (обязательно!) КриптоПро CSP. Во вкладке «Оборудование» нажмите «Настроить типы носителей…», найдите в списке «ESMART Token 64K». Нажмите «Свойства» и перейдите во вкладку «Свойства». Установите параметр «Максимальное число контейнеров» — 4. Данная настройка уменьшит максимальное количество контейнеров, хранимых на токене, но пропорционально увеличит их возможный размер.
При установке ESMART PKI Client возникает ошибка 26352
Для решения проблемы запустите оснастку «Сертификаты пользователя» (certmgr). Перейдите в «Доверенные издатели» -> «Сертификаты». Удалите все сертификаты «ISBC Ltd». Запустите setup.x64.exe (либо setup.exe в зависимости от вашей системы) от имени администратора. Программа установки автоматически добавит корректные сертификаты.
При запуске ESMART PKI Client появляется сообщение «The requested security protocol is not supported»
Данная ошибка может проявиться на ОС Windows Vista SP2 и Windows Server 2008 SP2. Для решения проблемы установите соответствующий патч из статьи на сайте Microsoft
При запуске ПО ESMART PKI Client появляется сообщение: «Ошибка при инициализации PKCS #11. Возможно, в системе не установлены соответствующие библиотеки». ESMART PKI Client запускается, но не определяются смарт-карты и ESMART Token
Переустановите ESMART PKI Client, запустив установщик
от имени администратора
или установите библиотеки вручную.
Также возможно возникновение данной ошибки при установке PKI Client посредством Windows Remote Desktop (RDP). Для установки PKI Client рекомендуем использовать иное ПО для удалённого доступа к компьютеру
На удаленном рабочем столе не отображаются ESMART Token и смарт-карты. КриптоПРО CSP не определяет сертификаты на ESMART Token. В запущенном ESMART PKI Client не отображаются подключенные ESMART Token
Особенность работы со смарт-картами в терминальной сессии заключается в том, что использовать можно только смарт-карты, подключенные к клиентской станции. Смарт-карты, подключенные непосредственно к терминальному серверу в терминальной сессии использовать нельзя. Это ограничение службы терминалов.
ESMART Token, подключенный к удаленной машине при работе через RDP, работать не будет. Необходимо или подключать его к машине, с которой заходит клиент, или воспользоваться иным ПО для удалённого доступа к компьютеру
При подключении ESMART Token в ПО КриптоПро CSP не отображаются установленные на этом ESMART Token сертификаты. При работе с ESMART Token отображается предупреждение: «Некорректный тип носителя или носитель не отформатирован.»
Необходимо зайти в КриптоПро CSP на вкладку «Оборудование» — строка «Типы ключевых носителей», нажать на кнопку «Настроить типы носителей». В ключевых носителях должны быть установлены следующие носители: ESMART Token 64K, ESMART Token GOST (3 шт.). При отсутствии данных об этих ESMART Tokenах работать с КриптоПро CSP будет невозможно.
Поддержка ESMART Token 64K в КриптоПро начинается с версии 3.6 R2, ESMART Token ГОСТ с версии 3.9. Если версия старше, то следует установить «Модуль поддержки КриптоПро CSP для Windows» для ESMART Token 64K и/или ESMART Token GOST. В ином случае следует переустановить КриптоПро CSP. Если переустановка не помогает, то следует установить «Модуль поддержки КриптоПро CSP для Windows»
Не удается записать контейнеры VipNet на ESMART Token при использовании профиля «По умолчанию»
Необходимо установить профиль VipNet, VipNet2 или VipNet3
На компьютерах с ОС Windows XP не удается авторизоваться при помощи смарт-карт
Необходимо установить Microsoft Base Smart Card Cryptographic Service Provider Package (KB909520). А также драйвера для устройств чтения смарт-карт.
Если неработающее устройство это ESMART Token, а не смарт-карта (то есть ридер и смарт-карта в одной сборке), то необходимо установить обновление с драйверами — Microsoft Usbccid (WUDF).
Так как поддержка Windows XP прекращена, необходимое для работы ESMART Token ПО не устанавливается в автоматическом режиме
При запуске *.exe файла, не находится файл*.msi, хотя оба помещены в одну папку
Убедиться, что архив с файлами распакован и запуск установки осуществляется не из этого архива.
Возможно, потребуется отключение SmartScreen для файлов в панели настроек.
При переключении режима работы Службы смарт-карт (на авто или ручной режим) возникает ошибка: «Невозможно создать файл, т.к. он уже существует»
Необходимо вручную изменить значения в реестре, выполняющие ту же функцию: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SCardSvr
для параметра Start поменять с 4 на 2
Примечание:
Automatic (Delayed Start) — 2
Manual — 3
Disabled — 4
При извлечении карты не блокируется АРМ
Необходимо включить политику блокировки при извлечении локально в gpedit.msc (Конфигурация компьютера — Конфигурация Windows — Параметры безопасности — Локальные политики — Параметры безопасности — Интерактивный вход в систему: поведение при извлечении смарт-карты — выставить опцию «Блокировка рабочей станции»). Также необходимо убедиться, что служба «Политика удаления смарт-карт» запущена, тип запуска установлен Автоматический
После установки ПО на Windows XP ESMART Token не определяется клиентом, а также в разделе «Оборудование» отображается как неверно установленный
При перенаправлении ESMART через RDP. Ошибка библиотек PKCS #11
Проблема заключается в одном из обновлений Windows.
Решение на сайте Microsoft
При работе через RDP из внешней сети возникает ошибка Kerberos
Особенность заключается в том, что аутентификация по смарт-картам является надстройкой (расширением) аутентификации Kerberos, но никак не заменяет его. Т.е. аутентификация по смарт-карте без Kerberos невозможна. Когда вбивается IP адрес (\\192.168.1.1\share) используется NTLM, а не Kerberos. Описание на сайте Microsoft.
Решение: использовать VPN-соединение.
При попытке обратиться к контейнеру КриптоПро через ACR3901U мигает окно, соединение сбрасывается
Проявляется с прошивкой v.110, необходимо обновить прошивку до v.111
Ошибка: «Данная реализация не является частью протестированных криптографических алгоритмов Windows Platform FIPS»
Необходимо отключить политику «Системная криптография: использовать FIPS совместимые алгоритмы для шифрования, хеширования и подписывания»
Ручная установка драйверов
Скачайте и распакуйте архив.
Для ридера драйвера не требуются — используются стандартные драйвера CCID в составе ОС. Драйвера смарт-карты находятся в директориях ESMART CSP и ESMART GOST CSP для ESMART Token 64K и ESMART Token ГОСТ соответственно, устанавливаются стандартным образом.
Добавление библиотек на x86 системе:
— скопировать библиотеки из SystemFolder в C:\Windows\System32
Добавление библиотек на x64 системе:
— скопировать библиотеки из System64Folder в C:\Windows\System32
— скопировать библиотеки из SystemFolder в C:\Windows\SysWow64
Внесите изменения в реестр.
Для этого скачайте и распакуйте архив. Запустите файлы изменения реестра из архива (с названием remove — для удаления).
Перезагрузите компьютер.
Решения для Linux / MacOS
ESMART PKI Client не запускается
Должна быть установлена среда Mono.
В Linux для запуска программы из командной строки необходимо набрать команду, указав действительный путь к исполняемому файлу:
> sudo mono ESMARTPkiClient.exe
В некоторых ОС, в т.ч. с установленным Mono, PKI Client может не запускаться, или не будут отображаться все графические элементы. Это связано с индивидуальными особенностями ОС Linux и работы в них программ. В таком случае следует сообщать разработчикам
При запуске ПО ESMART PKI Client появляется сообщение: «Ошибка при инициализации PKCS#11. Возможно в системе не установлены соответствующие библиотеки»
Проверить, установлены ли пакеты для работы со смарт-картами (pcsclite, usb-ccid), проверить, запущена ли служба pcscd.
Установить библиотеки. Это можно сделать с помощью .rpm пакета командой:
> rpm -Uvh isbc_pkcs11-****.rpm (или подобной).
Выяснить значение DISPLAY для суперпользователя (> sudo -i) командой:
> echo $DISPLAY
Если значение пусто, то задать его самостоятельно командой:
> export DISPLAY=»*»
Вместо «*» должно быть некое значение, выяснить которое можно у других пользователей системы
При запуске ПО ESMART PKI Client появляется окно с сообщением: «Unable to load library: libisbc_pkcs11_main.so»
Установить/обновить соответствующие пакеты: pcsc-lite, CCID драйвер, ISBC PKCS#11, mono, libgdiplus.
Дополнительно убедиться, что в нужных папках есть символьные ссылки на следующие библиотеки: libdl.so, libgdiplus.so. Как правило, после установки пакетов есть только библиотеки с номером версии, например: /usr/lib64/libgdiplus.so.0, но ссылки /usr/lib64/libgdiplus.so нет. Её необходимо создать командой
> sudo ln -s /usr/lib64/libgdiplus.so.0 /usr/lib64/libgdiplus.so
ПО ESMART PKI Client не запускается. В терминале отображается ошибка с текстом «System.DllNotFoundException: libgdiplus.so»
В папке /usr/lib64 или /usr/lib (в зависимости от разрядности ОС) нет файла libgdiplus.so
Установить пакет libgdiplus необходимой разрядности. Создать символьную ссылку libgdiplus.so в папке /usr/lib64 или /usr/lib (в зависимости от разрядности ОС).
Как правило, после установки пакетов есть только библиотеки с номером версии, например /usr/lib64/libgdiplus.so.0, но ссылки /usr/lib64/libgdiplus.so нет. Её необходимо создать командой
> sudo ln -s /usr/lib64/libgdiplus.so.0 /usr/lib64/libgdiplus.so
На удаленном рабочем столе не отображаются ESMART Token и смарт-карты. КриптоПРО CSP не определяет сертификаты на ESMART Token. В запущенном ESMART PKI Client не отображаются подключенные ESMART Token
Особенность работы со смарт-картами в терминальной сессии заключается в том, что использовать можно только смарт-карты, подключенные к клиентской станции. Смарт-карты, подключенные непосредственно к терминальному серверу в терминальной сессии использовать нельзя. Это ограничение службы терминалов.
ESMART Token, подключенный к удаленной машине при работе через RDP, работать не будет. Необходимо или подключать его к машине, с которой заходит клиент, или воспользоваться другим ПО для подключения к машине (TeamViewer и др.)
Не удается записать контейнеры VipNet на ESMART Token при использовании профиля «По умолчанию»
Необходимо установить профиль для ESMART Token VipNet, VipNet2 или VipNet3
В VirtualBox на MacOS не определяется ESMART Token, возникает ошибка: «Used by someone else»
Привязать ESMART Token в настройках виртуальной машины (если есть запись с этим устройством, удалить её и привязать заново). Извлечь ESMART Token, выключить виртуальную машину, включить заново, подключить ESMART Token
Если Вам не удалось найти описание и решение Вашей проблемы, Вы можете направить её подробное описание в адрес нашей технической поддержки:
E-mail: help@esmart.ru
Skarvon |
|
Статус: Участник Группы: Участники Сказал «Спасибо»: 3 раз |
Здравствуйте! Окно появляется всегда, независимо от наличия «ESMART PKI Client». Используется ПИН-код по умолчанию. Если авторизоваться в «ESMART PKI Client», то появляется такое-же окно, но авторизация проходит успешно. Параметры токена: Версия КриптоПро, которую мы используем: 4.0.9944 Подскажите пожалуйста, поддерживает ли КриптоПро CSP 4.0 указанные токены и если да, то что нужно сделать, чтобы исправить эту ситуацию? |
|
|
Skarvon |
|
Статус: Участник Группы: Участники Сказал «Спасибо»: 3 раз |
Вот решение: |
|
|
Агафьин Сергей |
|
Статус: Сотрудник Группы: Участники Сказал «Спасибо»: 5 раз |
Добрый день. |
С уважением, |
|
|
|
Пользователи, просматривающие эту тему |
Guest |
Быстрый переход
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.
Страницы Назад 1 … 6 7 8 9 10 … 21 Далее
Чтобы отправить ответ, нужно авторизоваться или зарегистрироваться
RSS: текущая тема
Сообщений с 106 по 120 из 311
#106 2022-02-02 11:59:22
- Nikolay11
- Посетитель
- Неактивен
Re: Проблемы с RSA PKCS11
Добрый день такая же проблема
#107 Ответ от Николай Киблицкий 2022-02-02 12:41:16 (2022-02-11 13:52:29 отредактировано Николай Киблицкий)
- Николай Киблицкий
- Техническая поддержка
- Неактивен
Re: Проблемы с RSA PKCS11
Здравствуйте, Nikolay11, могу ли я подключиться к вашему ПК удаленно?
Если это возможно, запустите программу AnyDeskи сообщите данные для подключения письмом на почту http://hotline@rutoken.ru/.
#108 Ответ от Nikolay11 2022-02-02 13:00:23
- Nikolay11
- Посетитель
- Неактивен
Re: Проблемы с RSA PKCS11
Николай Киблицкий пишет:
Здравствуйте, Nikolay11, могу ли я подключиться к вашему ПК удаленно?
Если это возможно, запустите программу AnyDeskAnyDesk и сообщите данные для подключения письмом на почту http://hotline@rutoken.ru/.
663980939
#109 Ответ от Nikolay11 2022-02-02 13:14:22
- Nikolay11
- Посетитель
- Неактивен
Re: Проблемы с RSA PKCS11
Nikolay11 пишет:
Николай Киблицкий пишет:
Здравствуйте, Nikolay11, могу ли я подключиться к вашему ПК удаленно?
Если это возможно, запустите программу AnyDeskAnyDesk и сообщите данные для подключения письмом на почту http://hotline@rutoken.ru/.663980939
WARN ru.centerinform.transport.util.a — RSA сертификаты не найдены
2022-02-02 13:01:52,734 ERROR ru.centerinform.transport.pki.key.KeyMaster — Ошибка при инициализации RSA
#110 Ответ от Nikolay11 2022-02-04 07:04:03 (2022-02-04 07:06:26 отредактировано Nikolay11)
- Nikolay11
- Посетитель
- Неактивен
Re: Проблемы с RSA PKCS11
Nikolay11 пишет:
Nikolay11 пишет:
Николай Киблицкий пишет:
Здравствуйте, Nikolay11, могу ли я подключиться к вашему ПК удаленно?
Если это возможно, запустите программу AnyDeskAnyDesk и сообщите данные для подключения письмом на почту http://hotline@rutoken.ru/.663980939
WARN ru.centerinform.transport.util.a — RSA сертификаты не найдены
2022-02-02 13:01:52,734 ERROR ru.centerinform.transport.pki.key.KeyMaster — Ошибка при инициализации RSA
Добрый день всем. Проблему решил самостоятельно. Решение простое-НИКОГДА НЕ ГЕНЕРЕРОВАТЬ КЛЮЧ RSA ЧЕРЕЗ УТМ МОДУЛЬ. Только через личный кабинет. УТМ модуль его видит, но корректно с ним не работает. Помогло удаление старых и нового ключей RSA, и многократной 5 раз переустановки УТМ. Еще один нюанс проверьте не отключается электропитание USB режиме экономии через настройки компьютера.
Отдельное спасибо техподдержке форума, а именно Николаю Киблицкому за желание помочь, но к сожалению, я ввиду своей мобильности, не могу ждать, когда мне придут на помощь.
РУТОКЕН драйвера работают корректно!!!!,
Некорректно работает УТМ модуль(что можно было ожидать от Росалкогольрегулирования и его специалистов-программистов УТМ Модуля)
#111 Ответ от lepsky 2022-02-11 13:47:01
- lepsky
- Посетитель
- Неактивен
Re: Проблемы с RSA PKCS11
Здравствуйте, та же проблема.
#112 Ответ от Николай Киблицкий 2022-02-11 13:52:00
- Николай Киблицкий
- Техническая поддержка
- Неактивен
Re: Проблемы с RSA PKCS11
Здравствуйте, lepsky, могу ли я подключиться к вашему ПК удаленно?
Если это возможно, запустите программу AnyDeskи сообщите данные для подключения письмом на почту http://hotline@rutoken.ru/.
#113 Ответ от lepsky 2022-02-11 13:58:05
- lepsky
- Посетитель
- Неактивен
Re: Проблемы с RSA PKCS11
2 минуты
#114 Ответ от Fedor 2022-02-14 11:40:34
- Fedor
- Посетитель
- Неактивен
Re: Проблемы с RSA PKCS11
Добрый день!
Возникал небольшая проблема. ИП получил подпись на рутокен в ФНС, пытаюсь войти в ЛК ЕГАИС, ввожу пин-код выходит сообщение
В чем может быть проблема?
3-мя минутами ранее сделал аналогично по другому ИП — все прошло без проблем.
Спасибо.
#115 Ответ от Николай Киблицкий 2022-02-14 11:49:56
- Николай Киблицкий
- Техническая поддержка
- Неактивен
Re: Проблемы с RSA PKCS11
Здравствуйте, Fedor.
Для решения воспользуйтесь решением описанным в инструкции.
#116 Ответ от Fedor 2022-02-14 12:01:28
- Fedor
- Посетитель
- Неактивен
Re: Проблемы с RSA PKCS11
Николай Киблицкий пишет:
Здравствуйте, Fedor.
Для решения воспользуйтесь решением описанным в инструкции.
Воспользовался обработкой, но ситуация не изменилась. Как можно решить данную проблему?
Спасибо.
#117 Ответ от Fedor 2022-02-14 12:04:11
- Fedor
- Посетитель
- Неактивен
Re: Проблемы с RSA PKCS11
Fedor пишет:
Николай Киблицкий пишет:
Здравствуйте, Fedor.
Для решения воспользуйтесь решением описанным в инструкции.Воспользовался обработкой, но ситуация не изменилась. Как можно решить данную проблему?
Спасибо.
Перезагрузил компьютер, все заработало.
СПАСИБО БОЛЬШОЕ!!!!!!
#118 Ответ от sveta36 2022-02-25 09:18:36
- sveta36
- Посетитель
- Неактивен
Re: Проблемы с RSA PKCS11
Добрый день! При записи сертификата PKI на рутокен 2.0 возникает ошибка. ошибка в методе createCertificateRequest() Error:CX509PrivateKey::Create: Файл существует. 0х80070050 (win32:80). Помогите пжл разобраться в данной ситуации!
#119 Ответ от Ксения Шаврова 2022-02-25 11:11:43
- Ксения Шаврова
- Администратор
- Неактивен
Re: Проблемы с RSA PKCS11
sveta36, приложите, пожалуйста скриншот ошибки и в каком сервисе возникает эта ошибка?
#120 Ответ от Ксения Шаврова 2022-02-25 12:19:01
- Ксения Шаврова
- Администратор
- Неактивен
Re: Проблемы с RSA PKCS11
>Ошибка возникает в личном кабинете фс рар егаис. При формировании сертификата рки
Тогда нам понадобятся скриншоты:
1) «Панель управления Рутокен» — вкладка «Администрирование» — кнопка «Информация»
2) «Панель управления Рутокен» — вкладка «Сертификаты»
Сообщений с 106 по 120 из 311
Страницы Назад 1 … 6 7 8 9 10 … 21 Далее
Чтобы отправить ответ, нужно авторизоваться или зарегистрироваться
Перед началом работы с электронной подписью на компьютере с OS Windows необходимо выполнить следующие настройки:
1. Установка КриптоПРО CSP
В случае, если у вас отсутствуют предустановленные криптопровайдеры, нажмите на ссылку «КриптоПРО 5.0» ниже для загрузки файла установки КриптоПРО на компьютер.
·КриптоПро CSP 5.0.12000 для Windows 7 / 8 / 10
После окончания загрузки, откройте zip-архив с помощью соответствующей программы-архиватора (например, Win—RAR). Внутри будет сам файл установки КриптоПРО. Запустите его и установите с параметрами по умолчанию. В процессе установки у Вас может появиться следующее окно:
Рис.1 – Установка КриптоПРО
Пропустите окно, нажав «Далее». Установка КриптоПРО завершена.
2. Установка драйвера для токена
Подписи можно хранить в реестре компьютера, на обычных флеш-накопителях или на специальных usb-токенах. Список токенов, пин-коды и ссылки на ПО представлены в таблице ниже (Таблица 1).
Таблица 1 – Драйверы для защищенных носителей
Тип USB-носителя | Внешний вид USB-носителя | Ссылка на загрузку драйверов | PIN-код |
ruToken | Драйверы Рутокен для Windows | 12345678 | |
eToken | Драйверы eToken для Windows | 1234567890 | |
JaCarta LT | Единый клиент JC | 1234567890 | |
MS-Key | MSKey Driver | 11111111 | |
Esmart* | ESMART PKI Client | 12345678 | |
JaCarta LT NanoJaCarta ГОСТJaCarta S/E | Единый клиент JC | 1234567890 |
Визуально определите ваш носитель.
Для работы с одним из этих носителей необходимо установить драйвер. Перейдите по соответствующей ссылке, скачайте драйвер и установите его на компьютер. Установку драйвера проводите с параметрами по умолчанию.
3. Установка корневых сертификатов УЦ
Для установки корневых сертификатов Вы можете воспользоваться автоматическим установщиком, который доступен для скачивания по данной ссылке, ЕГО НЕОБХОДИМО ЗАПУСТИТЬ ОТ ИМЕНИ АДМИНИСТРАТОРА
4. Установка плагинов для работы с КЭП
Для работы с электронной подписью в различных сервисах и ИС посредством браузера, необходимо установить дополнительное программное обеспечение, расширяющее возможности браузеров.
1. Крипто-Про ЭЦП Browser plugin 2.0 — стандартный плагин КриптоПро ЭЦП Browser plug-in.
Перейдите по ссылке для скачивания установочного файла, после завершения загрузки запустите файл. Во всех окнах подтверждения жмите «Да» и дождитесь завершения установки.
2. capicom2102.msi — стандартная библиотека CAPICOM от Microsoft.
Перейдите по ссылке для скачивания установочного файла, после завершения загрузки запустите файл. Примите лицензионное соглашение, во всех окнах подтверждения жмите «Далее» и дождитесь завершения установки.
Это важно!!! У некоторых площадок/порталов/сервисов есть собственные плагины, необходимые для работы с ЭП. Рекомендуется ознакомится с регламентом интересующей вас площадки перед началом работы. Например, для портала ГОСУСЛУГИ необходим этот плагин, а для ЭТП ГУП Татарстана криптографический плагин.
5. Настройка браузера
После установки Плагинов, их необходимо активировать в Вашем браузере. Ниже приведен пример активации плагина КриптоПро ЭЦП Browser plug-in в самых распространенных браузерах:
- Internet Explorer
- Google Chrome
- Яндекс.Браузер
- Microsoft Edge
- Mozilla Firefox
- Opera
- Спутник
Остались вопросы? Как мы можем помочь?
Как мы можем помочь?
Инструкция по выпуску квалифицированной электронной подписиНастройка компьютера для работы с КЭП (MacOS)
В этой статье мы познакомимся со стандартом PKCS#11, предназначенным для работы с различными криптографическими устройствами. Для демонстрации мы будем использовать токены и смарт-карты Рутокен ЭЦП 2.0.
Чтобы вам было проще понять эту информацию, перед её прочтением желательно:
-
ознакомиться с инфраструктурой PKI;
-
знать, что такое CMS-подпись;
-
знать, для чего нужен Рутокен и что он может.
Исходя из определения из Википедии:
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-формат описывает способ общения с различными устройствами с помощью байтовых последовательностей. На практике это происходит следующим образом:
-
Токену или смарт-карте посылается байтовая последовательность;
-
Операционная система устройства обрабатывает эту последовательность в команду и посылает в ответ код возврата и, при необходимости, дополнительную информацию.
Что же могут содержать внутри себя такие команды? Все, что угодно! Начиная с просьбы отправить информацию об устройстве или записать какие-то данные на него и заканчивая просьбой зашифровать указанное сообщение. Более того, эти команды могут быть разбиты на несколько, что позволяет оптимизировать работу с токеном и сделать ее конвейерной. К примеру, при шифровании сообщения устройству может передаваться небольшая часть сообщения, а он в ответ будет высылать результат шифрования только этой части. Эта особенность может быть использована при работе с данными больших размеров.
И вот вроде бы все хорошо: APDU даёт полную возможность для общения с токеном и смарт-картой, но зачем же тогда нужна PKCS#11-обертка? Причины достаточно очевидны:
-
Работать с шестнадцатеричными байтовыми последовательностями весьма неудобно, каждый раз надо заглядывать в спецификацию команд. Хотелось бы иметь их текстовый эквивалент, а лучше целую библиотеку для разработки.
-
Программирование через APDU-команды имеет “низкий КПД”. При выполнении даже самой простой операции надо написать множество команд. PKCS#11 устраняет эту проблему: при выполнении одной функции PKCS#11 посылается несколько APDU команд. Меньше пишешь — больше делаешь.
Теперь, когда мы знаем что лежит в основе PKCS#11-команд, перейдем к рассмотрению цикла работы с устройством.
Репозиторий с примерами
Чтобы улучшить понимание представленных ниже примеров рекомендуем:
-
Клонировать репозиторий с примерами.
-
Выполнить настройку системы согласно README.
-
Попробовать собрать примеры.
-
Отформатировать устройство семейства Рутокен ЭЦП 2.0 с помощью примера ex0_format_token.
Цикл работы с токеном
Придержим пока описание содержимого стандарта PKCS#11 и дадим поверхностное представление о том, как происходит работа с токеном в целом. Для этого рассмотрим листинг смены PIN-кода Пользователя на токене и смарт-карте:
#include
#include "utils.h"
extern CK_FUNCTION_LIST_PTR functionList; // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LIST
extern CK_FUNCTION_LIST_EXTENDED_PTR functionListEx; // Указатель на список функций расширения PKCS#11, хранящийся в структуре CK_FUNCTION_LIST_EXTENDED
int 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_LIST
CK_FUNCTION_LIST_EXTENDED_PTR functionListEx; // Указатель на список функций расширения PKCS#11, хранящийся в структуре CK_FUNCTION_LIST_EXTENDED
static HMODULE module;
int init_pkcs11()
{
CK_C_GetFunctionList getFunctionList; // Указатель на функцию C_GetFunctionList
CK_C_EX_GetFunctionListExtended getFunctionListEx; // Указатель на функцию C_EX_GetFunctionListExtended
/* Параметры для инициализации библиотеки: разрешаем использовать объекты синхронизации операционной системы */
CK_C_INITIALIZE_ARGS initArgs = { NULL_PTR, NULL_PTR, NULL_PTR, NULL_PTR, CKF_OS_LOCKING_OK, NULL_PTR };
CK_RV rv; // Код возврата PKCS#11 функций
int errorCode = 1; // Флаг ошибки
/*************************************************************************
* Выполнить действия для начала работы с библиотекой PKCS#11 *
*************************************************************************/
printf("Initialization...\n");
/*************************************************************************
* Загрузить библиотеку *
*************************************************************************/
module = LoadLibrary(PKCS11_LIBRARY_DIR "/" PKCS11ECP_LIBRARY_NAME);
CHECK(" LoadLibrary", module != NULL, exit);
/*************************************************************************
* Получить адрес функции запроса структуры с указателями на функции *
*************************************************************************/
getFunctionList = (CK_C_GetFunctionList)GetProcAddress(module, "C_GetFunctionList");
CHECK(" GetProcAddress (C_GetFunctionList)", getFunctionList != NULL, unload_pkcs11);
/*************************************************************************
* Получить адрес функции запроса структуры с указателями на функции *
* расширения стандарта PKCS#11 *
*************************************************************************/
getFunctionListEx = (CK_C_EX_GetFunctionListExtended)GetProcAddress(module, "C_EX_GetFunctionListExtended");
CHECK(" GetProcAddress (C_EX_GetFunctionListExtended)", getFunctionList != NULL, unload_pkcs11);
/*************************************************************************
* Получить структуру с указателями на функции *
*************************************************************************/
rv = getFunctionList(&functionList);
CHECK_AND_LOG(" Get function list", rv == CKR_OK, rvToStr(rv), unload_pkcs11);
/*************************************************************************
* Получить структуру с указателями на функции расширения стандарта *
*************************************************************************/
rv = getFunctionListEx(&functionListEx);
CHECK_AND_LOG(" Get function list extended", rv == CKR_OK, rvToStr(rv), unload_pkcs11);
/*************************************************************************
* Инициализировать библиотеку *
*************************************************************************/
rv = functionList->C_Initialize(&initArgs);
CHECK_AND_LOG(" C_Initialize", rv == CKR_OK, rvToStr(rv), unload_pkcs11);
errorCode = 0;
/*************************************************************************
* Выгрузить библиотеку из памяти *
*************************************************************************/
unload_pkcs11:
if (errorCode)
CHECK_RELEASE(" FreeLibrary", FreeLibrary(module), errorCode);
exit:
return errorCode;
}
Здесь происходит следующее:
-
В память процесса подгружается PKCS#11-библиотека, хранящаяся по пути PKCS11ECP_LIBRARY_NAME, с помощью функции LoadLibrary (стандартная функция для Windows-систем, для Linux-систем определена обертка).
-
Далее из библиотеки вытаскиваются указатели на функции C_GetFunctionList и C_EX_GetFunctionListExtended. Первая функция определена в стандарте PKCS#11 и позволяет получить структуру указателей на функции библиотеки. Вторая — является специфичной для библиотеки rtpkcs11ecp и позволяет получить схожую структуру указателей на функции расширения библиотеки. О функциях расширения мы поговорим позже.
-
Потом мы вызываем полученную функцию C_GetFunctionList и получаем уже саму структуру указателей на функции.
-
С помощью функции C_Initialize инициализируется загруженная библиотека. Функция C_Initialize в качестве аргумента принимает параметры инициализации библиотеки. Подробнее о них можно почитать здесь, для нас же важен флаг CKF_OS_LOCKING_OK. Его необходимо использовать, если мы хотим использовать библиотеку в нескольких потоках. В нашем примере мы могли бы опустить этот флаг.
Основной этап
Основной этап можно разделить на работу со слотами и работу внутри сессии
Этап работы со слотами
Слот — это дескриптор виртуального интерфейса, куда подключен токен. Конкретно в нашем примере весь этап работы со слотами уместился в определение одной функции 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_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;
}
Работу внутри сессий можно разделить на несколько этапов:
-
Открытие сессии в слоте с помощью функции C_OpenSession.
-
Аутентификация пользователя с помощью функции C_Login. Стандартно на Рутокене присутствуют два пользователя: Администратор (CKU_SO) и Пользователь (CKU_USER). Аутентификация на устройстве — не является обязательной операцией. Она нужна, когда мы хотим получить доступ к операциям и объектам, которые требуют наличия соответствующих прав доступа. В нашем случае, это операция смены PIN-кода Пользователя.
-
Выполнение различных функций по работе с сессиями. В нашем случае это функция C_SetPIN.
-
Далее по нисходящей могут идти операции сброса прав доступа (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;
}
Завершающий этап можно разделить на:
-
Деинициализацию библиотеки с помощью функции C_Finalize.
-
Выгрузку библиотеки из памяти процесса через функцию 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_EncryptFinal. Пример этого будет приведен ниже. Эта особенность связана с тем, что внутри библиотеки 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.
То есть стандарт не даёт явное определение объекта, но из того, что там написано, мы знаем:
-
объект относится к определенному классу;
-
объект состоит из множества атрибутов, имеющих заданное значение;
-
каждый атрибут имеет ровно одно значение.
Также в стандарте представлена классификация объектов:
Заголовок диаграммы определяет класс объекта, а то что ниже — некоторые из его атрибутов.
Видно, что объектом может являться некоторый механизм (о механизмах мы поговорим позже), встроенные функции токена (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 состоит из трех элементов:
-
Идентификатор механизма (mechanism);
-
Указатель на параметры механизма (pParameter);
-
Длина в байтах параметров механизма (ulParameterLen).
Самый простой пример параметра механизма — вектор инициализации для алгоритмов шифрования. Попробуем на примере показать, как можно зашифровать сообщение через механизм CKM_GOST28147 с указанным вектором инициализации. Пример реализован внутри функции encrypt_data:
int encrypt_data(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE secretKey)
{
/* Имитовставка */
CK_BYTE iv[] = { 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f };
/* Механизм программного шифрования/расшифрования по алгоритму ГОСТ 28147-89 */
CK_MECHANISM gost28147EncDecMech = {CKM_GOST28147, iv, sizeof(iv)};
/*************************************************************************
* Данные для шифрования *
*************************************************************************/
CK_BYTE data[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00 };
CK_BYTE_PTR encrypted; // Указатель на временный буфер для зашифрованных данных
CK_ULONG encryptedSize; // Размер временного буфера в байтах
CK_RV rv;
int errorCode = 1;
/*************************************************************************
* Инициализировать операцию шифрования *
*************************************************************************/
rv = functionList->C_EncryptInit(session, &gost28147EncDecMech, secretKey);
CHECK_AND_LOG(" C_EncryptInit", rv == CKR_OK, rvToStr(rv), exit);
/*************************************************************************
* Зашифровать данные (при шифровании с использованием механизма *
* CKM_GOST28147_ECB размер данных должен быть кратен 8) *
*************************************************************************/
encryptedSize = sizeof(data);
encrypted = (CK_BYTE_PTR)malloc(encryptedSize * sizeof(CK_BYTE));
CHECK(" Memory allocation for encrypted data", encrypted != NULL_PTR, exit);
rv = functionList->C_Encrypt(session, data, sizeof(data), encrypted, &encryptedSize);
CHECK_AND_LOG(" C_Encrypt", rv == CKR_OK, rvToStr(rv), free_encrypted);
/*************************************************************************
* Распечатать буфер, содержащий зашифрованные данные *
*************************************************************************/
printf(" Encrypted buffer is:\n");
printHex(encrypted, encryptedSize);
printf("Encryption has been completed successfully.\n");
errorCode = 0;
free_encrypted:
free(encrypted);
exit:
return errorCode;
}
В этом примере стоит обратить внимание на то, как передаётся вектор инициализации механизму шифрования. Стоит заметить, что после вызова функции C_Encrypt вызывать функцию C_EncryptFinal не нужно: для многих механизмов вызов C_Encrypt эквивалентен последовательному вызову функций C_EncryptUpdate и C_EncryptFinal.
Проверка поддержки механизмов
Ранее в примерах мы всегда надеялись на то, что токен поддерживает используемые механизмы, но, вообще говоря, — это может быть не так. Так, например, Рутокен 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 с объектами на смарт-картах/токенах можно работать через:
-
OpenSSL API (pkcs11 engine, rutoken engine);
-
C# для .NET Framework 4.5, .NET Standard 2.0, MonoAndroid 2.3, Xamarin.iOS 1.0 и Xamarin.Mac 2.0;
-
CryptoAPI;
-
Java c JDK 8 или новее через интерфейсы PKCS#11-wrapper и JCA;
-
C++ с использованием библиотеки pki-core;
-
JavaScript для большинства популярных браузеров с использованием Рутокен Плагин;
Если вы хотите узнать про какую-то реализацию подробнее, то напишите в комментариях и мы постараемся в ближайшее время выпустить новую статью с её разбором.