I. портирование кода модуля tclpkcs11 в модуль pyp11 для python
Портирование заключается в адаптации кода модуля tclpkcs11 к требованиям со стороны Python. Все изменения в проекте будут касаться только модуля tclpkcs11.c. Поэтому, первое, что мы сделаем, скопируем модуль tclpkcs11.c в файл pythonpkcs11.c и в дальнейшем будем работать именно с ним. Модуль для Python назовем
pyp11
. Использовать для его создания будем
. Почему-то этот способ многие (но не я) считают самым трудным, но зато он самый эффективный. Анализ C API для Tcl и C API для Python показал их значительное сходство, что и позволило очень быстро провести портирование. Отметим основные этапы портирования, которые вполне возможно кому-то помогут перенести те или иные модули (библиотеки) из Tcl в Python или наоборот.
Первое, в файле pythonpkcs11.c заменяем все объявления Tcl_Obj на PyObject, что вполне естественно: Tcl работает со своими объектами, а Python со своими.
Второе касается передачи параметров.
В общем виде объявление функции, реализующей ту или иную команду Tcl, в С-коде выглядит следующим образом (применительно к нашему коду):
name_proc_tcl (CliendData cd, Tcl_Interp *interp, int objc, Tcl_Obj[] *objv[] ){
. . .
};
В Python аналогичный заголовок функции будет выглядеть так:
name_proc_py (PyObject *self, PyObject *args){
. . .
};
В C-коде для tcl проверка количества входных параметров проводится с использованием переменной objc.
name_proc_tcl (CliendData cd, Tcl_Interp *interp, int objc, Tcl_Obj[] *objv[] ){
if (objc != 4) {
. . .
Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be "pki::pkcs11::login handle slot password"", -1));
return(TCL_ERROR);
}
. . .
};
В Python параметры передаются в виде кортежа. Поэтому число переданных параметров вычисляется функцией PyTuple_Size(args):
name_proc_py (PyObject *self, PyObject *args){
//Вводим переменную для числа параметров
int objc;
objc = PyTuple_Size(args);
. . .
if (objc != 3) {
PyErr_SetString(PyExc_TypeError, "pyp11_login args error (count args != 3)");
return NULL;
}
. . .
};
Отметим, что число параметров в коде для Tcl на единицу больше, т.к. в objv[0] хранится имя функции (аналогично функции main в C).
В приведенном коде наглядно видно как обрабатываются ошибки в Tcl и Python.
Вызов прерывания в случае ошибки для Tcl выполняется оператором
return (TCL_ERROR);
Текстовое сообщение об ошибке формируется оператором TclSetObjResult.
Для Python будут использоваться операторы return NULL и PyErr_SetString.
Теперь самое главное — разбор параметров.
В Tcl каждый параметр передается как отдельный Tcl-объект, а в Python — как кортеж параметров в виде Python-объектов. Поэтому, если мы хотим вносить минимальные изменения в код, целесообразно сначала распаковать кортеж по отдельным объектам, например (применительно к функции pyp11_login):
…
char *tcl_handle;
long slotid_long;
char *password;
//Массив PyObject-ов для входных параметров
PyObject *argspy[3];
//Растаскиваем входные параметры/объекты ("OOO" - три объекта) по своим ячейкам
PyArg_ParseTuple(args, "OOO", &argspy[0], &argspy[1], &argspy[2])
…
Полученные объекты распаковываем с их функциональным назначением:
…
//Получаем строку (s) с handle библиотеки PKCS11
PyArg_Parse(argspy[0], "s", &tcl_handle);
//Получаем номер слота (l), в котором находится токен
PyArg_Parse(argspy[1], "l", &slotid_long);
//Получаем строку (s) с PIN-кодом владельца
PyArg_Parse(argspy[2], "s", &password);
...
Сразу оговоримся, что в C API Python имеется функция, которая позволяет сразу разбирать кортеж параметров. В этом случае можно обойтись одним оператором:
PyArg_ParseTuple(args, «sls», &tcl_handle, &slotid_long, &password);
Как ни парадоксально, это практически все рекомендации.
Осталось последнее, — возвращаемые значения.
Результаты выполнения команд возвращаются либо в виде строки, либо в виде списка, либо в виде словаря.
Приведём некоторые соответствия. Так для создания списка в коде для Tcl используется функция Tcl_NewObj(), а в коде для Python используется функция PyListNew(0).
Для добавления элемента в список для Tcl используется функция TclListObjAppendElement, а для Python — функция PyList_Append. Все эти соответствия можно найти, сравнив
и код pyp11 (ССЫЛКА).
Также вместо используемых функций ckalloc и ckfree в tclpkcs11.c для Tcl, в модуле pythonpkcs11.c используются стандартные функции работы с памятью — malloc и free.
После проведенного анализа модификация кода вместе с тестированием заняла пару рабочих дней.
Iii. управление токенами pkcs#11
Для тестирования функций управления подойдет любой токен PKCS#11, даже токен без поддержки какой-либо криптографии, например RuTokenLite. Но поскольку мы ведём речь о российской криптографии, то целесообразно сразу иметь токен с поддержкой российской криптографии.
Установить программный токен или получить доступ к облачному токену можно, воспользовавшись утилитой cryptoarmpkcs.
После запуска утилиты необходимо зайти на вкладку «Создать токены»:
На вкладке можно найти инструкции для получения токенов.Итак, у нас токен и библиотека для работы с ним. После загрузки модуля pyp11 требуется загрузить библиотеку для работы с нашим токеном. В примерах будут использоваться библиотека librtpkcs11ecp-2.
0 для работы с аппаратным токеном, библиотека libls11sw2022 для работы с программным токеном и библиотека libls11cloud.so для работы с облачным токеном. Читатели могут использовать любые токены, даже те, на которых нет российской криптографии, на них тоже можно проверить функции управления.Итак, загружаем библиотеку командой loadmodule:
bash-4.4$ python3
Python 3.7.9 (default, Feb 1 2021, 16:55:33)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> import pyp11
>>> #Выбираем библиотеку pkcs11
>>> lib = "/usr/local/lib64/librtpkcs11ecp_2.0.so"
>>> #Обработка ошибки при загрузке библиотеки PKCS#11
>>> try:
... #Вызываем команду загрузки библиотеки и плохим числом параметров
... handlelib = pyp11.loadmodule(lib, 2)
... except:
... print ('Ошибка загрузки библиотеки: ')
... e = sys.exc_info()[1]
... e1 = e.args[0]
... print (e1)
...
Ошибка загрузки библиотеки:
pyp11_load_module args error (count args != 1)
>>> #Загружаем с правильным синтаксисом
>>> idlib = pyp11.loadmodule(lib)
>>> #Печатаем дескриптор библиотеки
>>> print (idlib)
pkcs0
>>>
Дескриптор загруженной библиотеки используется при её выгрузке:
>>> pyp11.unloadmodule(idlib)
Теперь, когда библиотека загружена, можно получить список поддерживаемых её слотов и узнать есть ли в каких слотах токены. Для получения списка слотов с полной информацией о них и содержащихся в них токенах используется команда:
>>> slots = pyp11.listslots(idlib)
>>>
Команда pyp11.listslots возвращает список, каждый элемент которого содержит информацию о слоте:
[<info slot1>, <info slot2>, ... , <info slotN>]
В свою очередь, каждый элемент этого списка также является списком, состоящим из четырех элементов:
[<номер слота>, <метка токена, находящегося в слоте>, <флаги слота и токена>, <информация о токене>]
Если слот не содержит токен, то элементы и содержат пустое значение.
Наличие токена в слоте определяется по наличию флага TOKEN_PRESENT в списке :
bash-4.4$ python3
Python 3.7.9 (default, Feb 1 2021, 16:55:33)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> import pyp11
>>> #Выбираем библиотеку
>>> #lib = '/usr/local/lib64/libls11sw2022.so'
>>> lib = '/usr/local/lib64/librtpkcs11ecp_2.0.so'
>>> #Загружаем библиотеку
>>> libid = pyp11.loadmodule(lib)
>>> #Дескриптор библиотеки
>>> print (libid)
pkcs0
>>> #Загружаем список слотов
>>> slots = pyp11.listslots(libid)
>>> tokpr = 0
>>> #Ищем первый подключенный токен
>>> while (tokpr == 0):
... #Перебираем слоты
... for v in slots:
... #Список флагов текущего слота
... flags = v[2]
... #Проверяем наличие в стоке токена
... if (flags.count('TOKEN_PRESENT') !=0):
... tokpr = 1
... #Избавляемся от лишних пробелов у метки слота
... lab = v[1].strip()
... infotok = v[3]
... slotid = v[0]
... break
... if (tokpr == 0):
... input ('Нет ни одного подключенного токена.nВставьте токен и нажмите ВВОД')
... slots = pyp11.listslots(libid)
... #Информация о подключенном токене
...
Нет ни одного подключенного токена.
Вставьте токен и нажмите ВВОД
''
>>> #Информация о подключенном токене
>>> print ('LAB="' lab '", SLOTID=' str(slotid))
LAB="Rutoken lite <no label>", SLOTID=0
>>> print ('FLAGS:', flags)
FLAGS: ['TOKEN_PRESENT', 'RNG', 'LOGIN_REQUIRED', 'SO_PIN_TO_BE_CHANGED', 'REMOVABLE_DEVICE', 'HW_SLOT']
>>>
Если взглянуть на флаги (FLAGS:) подключенного токена, то в них отсутствует флаг ‘TOKEN_INITIALIZED’. Отсутствие этого флага говорит о том, что токен не инициализирован и требуется его инициализация:
#Проверяем, что токен проинициализирован
>>> if (flags.count('TOKEN_INITIALIZED') == 0''):
... #Инициализируем токен
... dd = pyp11.inittoken (libid, 0, '87654321',"TESTPY2")
...
>>>
Как видим, для инициализации токена используется следующая команда:
pyp11.inittoken (<дискриптор библиоткети>, <номер слота>, <SO-PIN>, <метка токена>)
Естественно, токен можно переинициализировать независимо от наличия флага ‘TOKEN_INITIALIZED’, только надо иметь в виду, что переинициализация токена ведет к уничтожению на нем всех объектов (ключи, сертификаты и т.д).
Vi. работа с объектами токена
Основными объектами, с которыми приходится иметь дело, работая с токенами PKCS#11, являются сертификаты и ключи. И те и другие имеют атрибуты. Нас в первую очередь интересуют атрибуты CKA_LABEL или метка объекта и СКА_ID или идентификатор объекта. Именно атрибут CKA_ID используется для доступа и к сертификатам и ключам.
Уже имея в своем распоряжении рассмотренные выше команды модуля pyp11, можно создать ключевую пару и сформировать подписанный
. Отправить полученный запрос в
и получить там сертификат. Но получив сертификат, возникает вопрос как его поставить на токен и привязать к ключевой паре? Именно эту задачу решает команда pyp11.importcert:
Как работает команда? Первым делом она вычисляет по открытому ключу сертификата идентификатор CKA_ID. Именно этот идентификатор будет возвращен в hex-кодировке после успешного размещения сертификата на токене. После установки сертификата на токен в DER-формате, устанавливаются его атрибуты CKA_ID и CKA_LABEL.
Если вам необходимо связать тройку x x не только по CKA_ID, но и по метке CKA_LABEL, то необходимо установить метку у ключевой пары аналогичную метке сертификата. Для этого используется команда rename:
pyp11.rename(<идентификатор библиотеки>, <слот токена>, <тип объекта>, <ассоциированный список>)
В указывается, к каким типам объектов будет применяться команда: ‘cert’ | ‘key’ | ‘all’ (сертификаты, ключевая пара, к тому и другому).
Команда rename позволяет менять не только CKA_LABEL, но и CKA_ID. Конкретные объекты могут задаваться идентификаторами объектов CKA_ID (pkcs11_id), например:
#Импортируем сертификат и получаем его CKA_ID
labcert = 'LabelNEW'
ckaid = pyp11.importcert(aa, 0, cert_der_hex, labcert)
#Устанавливаем метку сертификата и для ключей
#Готовим словарь
ldict = dict(pkcs11_id=ckaid, pkcs11_label=labcert)
#Меняем метки у ключей
pyp11.rename(aa, 0, 'key', ldict)
Аналогичным образом меняется атрибут CKA_ID. В этом случае в словарь вместо метки указывается новый CKA_ID:
ldict = dict(pkcs11_id=ckaid, pkcs11_id_new=11111)
Аналогичным образом можно удалить объекты:
pyp11.delete(<идентификатор библиотеки>, <слот токена>, <тип объекта>, <ассоциированный список>)
При уничтожении в словарь попадает только один элемент, который будет указывать на удаляемые объекты. Это либо CKA_ID (ключ pkcs11_id) либо непосредственно handle-объекта (как правило, его можно получить по команде pyp11.listobjects, ключ pkcs11_handle):
ldict = dict(pkcs11_id=ckaid)
#Или с handle-объекта:
#ldict = dict(hobj=pkcs11_handle)
#Уничтожить личный сертификат с ключами
pyp11.login(aa. 0, '01234567')
pyp11.delete(aa, 0, 'all', ldict)
pyp11.logout(aa, 0)
Упомянем еще об одной очень редко используемой команде. Это команда закрытия сессий на токене:
pyp11.closesession(<идентификатор библиотеки>)
Эту команду следует вызывать, когда возникнет ошибка «PKCS11_ERROR SESSION_HANDLE_INVALID», а затем повторить команду, на которой возникла ошибка. Эта ошибка может возникнуть при кратковременном извлечении токена из компьютера при работе вашей программы.
И завершим мы рассмотрение командой pyp11.listcertsder:
Вот пример кода:
#!/usr/bin/python3
#-*- coding: utf-8 -*-
import sys
import time
import pyp11
print('Список сертификатов токена')
aa = pyp11.loadmodule('/usr/local/lib64/libls11sw2022.so')
lcerts = pyp11.listcerts(aa, 0)
if (len(lcerts) == 0):
print ('На токене нет сертификатов')
quit()
#Перебираем сертификаты
for cert in lcerts:
#Информация о сертификате
for key in cert:
print (key ': ' cert[key])
#Сравним с pyp11.listobjects
lm = pyp11.listobjects(aa, 0, 'cert', 'value')
print('Работа с listobjects:')
for obj in lm:
for key in obj:
print (key ': ' obj[key])
quit()
Команды pyp11.listobjects для сертификатов и команда pyp11.listcerts фактически дублируют друг друга, но так сложилось исторически.
Добавление/переопределение методов у объектов
В принципе этого материала достаточно, чтобы начать использовать ООП в Tcl. Но мы упомянули и то, что в TcllOO можно динамически конструировать не только сам класс, то и экземпляры класса, т.е. объекты. На одной из таких возможностей хотелось бы остановится.
Для этого добавим в класс certificate еще один метод для подписания этим сертификатом некоторого документа:
#Метод для Подписания документа
oo::define certificate {
method signDoc {doc} {
set sign "Здесь должна находиться подпись документа $doc"
#Счетчик подписанных документов
my variable signedDoc
#Количество подписанных документов
incr signedDoc
return [list $signedDoc $sign]
}
}
При вызове этого метода должно происходить подписание документа и увеличение счетчика подписанных документов на единицу. В качестве результата работы этого метода возвращается общее число подписанных на данный момент документов и сама подпись:
. . .
set doc "Подпись1"
puts "Подписание документа $doc"
foreach {count sign} [cert1 signDoc $doc] {
puts "tПодписано документов на данный момент=$count"
puts "tПодпись документа="$sign""
}
. . .
Результат будет выглядеть так:
. . .
Подписание документа Подпись1
Подписано документов на данный момент=1
Подпись документа="Здесь должна находиться подпись документа Подпись1"
. . .
Сам алгорит подписи здесь не рассматривается, но его можно найти в утилите cryptoarmpkcs:
А теперь представим, что владелец сертификата убывает в отпуск. Он знает, что в отпуске он будет отдыхать и не в коем случае не будет работать с документами и тем более что-либо подписывать. Он хочет отозвать сертификат, а когда вернется восстановить его действие. Для этих целей служит следующая функция:
#Процедура отзыва сертификата
proc revoke {cert_obj} {
oo::objdefine $cert_obj {
#Переопределяем метод подписи для конкретного объекта
method signDoc {args} {
#Переменная accessCert хранит число несанкционированных попыток подписания
my variable accessCert
set sign "Сертификат временно отозван. Не пытайтесь им подписывать!"
#Число попыток несанкционированного использования возрастает на 1
incr accessCert
return [list $accessCert $sign]
}
method unrevoke {} {
my variable accessCert
#Вызов метод unrevoke удалит метод подписи для конкретного объекта,
#восстанавливая тем самым действие метода signDoc из класса и
#удалит сам метод unrevoke
oo::objdefine [self] { deletemethod signDoc unrevoke }
if {![info exist accessCert]} {
return 0
}
return $accessCert
}
}
}
Вызов этой функции определяет новый функционал методв signDoc для конкретного объекта. Для остальных объектов, как существующих и так и новых, сохраняется действие метода, определенного для класса. Также определяется новый метод unrevoke, вызов которого сотрудником по возвращению из отпуска приведет к восстановлению метода signDoc из класса certificate, путем удаления метода signDoc для объекта, а также удалит и сам метод unrevoke.
Ниже приведен фрагмент выполнения примера example5.tcl:
. . .
Подписание документа Подпись1
Подписано документов на данный момент=1
Подпись документа="Здесь должна находиться подпись документа Подпись1"
Подписание документа Подпись2
Подписано документов на данный момент=2
Подпись документа="Здесь должна находиться подпись документа Подпись2"
Отзыв сертификата
Попытка подписать документ Подпись3
Попыток несанкционированного доступа=1
Подпись документа="Сертификат временно отозван. Не пытайтесь им подписывать!"
Попытка подписать документ подпись4
Попыток несанкционированного доступа=2
Подпись документа="Сертификат временно отозван. Не пытайтесь им подписывать!"
Действие сертификата восстанвлено
За время его отзыва было 2 попытки несанкционированного досьупа
Попытка подписать документ Подпись после восстановления
Подписано документов на данный момент=3
Подпись документа="Здесь должна находиться подпись документа Подпись после восстановления"
. . .
Упомянем еще один оператор. Это оператор клонирования объекта:
oo::copy <идентификатор исходного объекта> <идентификатор клона>
Говорить и писать об ООП на TclOO можно долго и долго.
Еще интересней его исследовать.
Использование механизмов криптографических токенов pkcs#11 в скриптовых языках

В своих комментариях к
статье
«Англоязычная кроссплатформенная утилита для просмотра российских квалифицированных сертификатов x509» пользователь
Pas
очень правильно заметил про токены PKCS#11, что они «сами все умеют считать». Да, токены фактически являются криптографическими компьютерами. И естественным является желанием использовать эти компьютеры в скриптовых языках будь то Python, Perl или Ruby. Мы уже так или иначе рассматривали
использование токенов PKCS#11
с поддержкой российской криптографии в Python для подписания и шифрования документов, для создания запроса на сертификат:
Здесь же мы продолжим разговор о языке Tcl. В предыдущей статье, когда мы рассматривали просмотр и валидацию сертификатов, хранящихся на токенах/смарткартах PKCS#11, мы использовали для доступа к ним (сертификатам) пакет TclPKCS11 версии 0.9.9. Как уже отмечалось, к сожалению, пакет разрабатывался под криптографию RSA и с учетом стандарта PKCS#11 v.2.20. Сегодня уже используется стандарт PKCS#11 v.2.40 и именно на него ориентируется технический комитет по криптографии ТК-26, выпуская рекомендации для отечественных производителей токенов/смарткарт, поддерживающих российскую криптографию. И вот с учетом всего сказанного появилась новый пакет TclPKCS11 версии 1.0.1 . Сразу огововоримся, все криптографические интерфейсы для RSA в новой версии пакета TclPKCS11 v.10.1 сохранены. Библиотека пакета написана на языке Си.
Итак, что нового появилось в пакете? Прежде всего добавлена команда, позволяющая получить список поддерживаемых подключенным токеном криптографических механизмов:
::pki::pkcs11::listmechs <handl> <slotid>
Как получить список слотов с подключенными токенами показано
здесь
(процедура — proc ::slots_with_token):
proc ::slots_with_token {handle} {
set slots [pki::pkcs11::listslots $handle]
# puts "Slots: $slots"
array set listtok []
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} {
set listtok($slotid) $slotlabel
}
}
#Список найденных токенов в слотах
parray listtok
return [array get listtok]
}
Возьмем простой скрипт:
#!/usr/bin/tclsh
lappend auto_path .
package require pki::pkcs11
#Библиотека для доступа к токенам семейства RuToken
set lib "/usr/local/lib64/librtpkcs11ecp_2.0.so"
<source lang="bash">set handle [pki::pkcs11::loadmodule $lib]
#Не забудьте вставить токен
#Получаем список слотов с метками подключенных токенов
set labslot [::slots_with_token $handle]
if {[llength $labslot] == 0} {
puts "Вы не подключили ни одного токена"
exit
}
set slotid 0
set lmech [pki::pkcs11::listmechs $handle $slotid]
set i 0
foreach mm $lmech {
#Ищем механизмы ГОСТ
if {[string first "GOSTR3410" $mm] != -1} {
puts -nonewline "[lindex $mm 0] "
if {$i == 2} {puts "";set i 0} else { incr i}
}
}
puts "n"
exit
Этот скрипт позволяет получить список механизмов для криптографии GOSTR3410, поддерживаемых на токенах семейства RuToken. Для начала возьмем, как писал в
статье Pas
, «любимый всякими ЭДО Рутокен Лайт»:
$ tclsh TEST_for_HABR.tcl
listtok(0) = ruToken Lite
0 {ruToken Lite }
$
И естественно окажется, что он не поддерживает ни одного мезанизма ГОСТ, что и требовалось доказать. Берем другой токен Рутокен ЭЦП:
$ tclsh TEST_for_HABR.tcl
listtok(0) = ruToken ECP }
0 {ruToken ECP }
CKM_GOSTR3410_KEY_PAIR_GEN CKM_GOSTR3410 CKM_GOSTR3410_DERIVE
CKM_GOSTR3410_WITH_GOSTR3411
$
Да, этот токен поддерживает российскую криптографию, но только подпись ГОСТ Р 34.10-2001, которая практически
вышла из употребления
. Но если взять токен Рутокен ЭЦП-2.0, то все будет хорошо, он поддерживает ГОСТ Р 34.10-2022 с ключами длиной 256 и 512 бит:
$ tclsh TEST_for_HABR.tcl
listtok(0) = RuTokenECP20
0 {RuTokenECP20 }
CKM_GOSTR3410_KEY_PAIR_GEN CKM_GOSTR3410 CKM_GOSTR3410_DERIVE
CKM_GOSTR3410_512_KEY_PAIR_GEN CKM_GOSTR3410_512 CKM_GOSTR3410_12_DERIVE
CKM_GOSTR3410_WITH_GOSTR3411 CKM_GOSTR3410_WITH_GOSTR3411_12_256 CKM_GOS
TR3410_WITH_GOSTR3411_12_512
$
Если мы заговорили о поддержке российской криптографии, включая алгоритмы шифрования кузнечик и магму, теми или иными токенами, то наиболее полно ее поддерживают программные и
облачные
токены и это естественно:
$ tclsh TEST_for_HABR.tcl
listtok(0) = LS11SW2022_LIN_64
0 {LS11SW2022_LIN_64 }
$
Переходим в следующей новой функции, добавленной в пакет:
set listcertsder [pki::pkcs11::listcertsder $handle $slotid]
Эта функция возвращает список сертификатов, хранящихся ни токене. Естественно возникает вопрос, а чем она отличается от уже имеющейся функции pki::pkcs11::listcerts?
Прежде всего новая функция не задействует пакет ::pki. Одним из возвращаемых элементов является элемент cert_der, содержащий полный сертификат. Это удобно, например, при экспорте сертификата, или получения его отпечатка (fingetprint). Ранее приходилось собирать полный сертификат из tbs-сертификата и его подписи. Полный перечень возвращаемых элементов для каждого сертификата наглядно видно при распечатке содержимого одного сертификата:
. . .
array set derc [[pki::pkcs11::listcertsder $handle $slotid] 0]
parray derc
derc(cert_der) = 3082064a …
derc(pkcs11_handle) = pkcsmod0
derc(pkcs11_id) = 5882d64386211cf3a8367d2f87659f9330e5605d
derc(pkcs11_label) = Thenderbird-60 от УЦ
derc(pkcs11_slotid) = 0
derc(type) = pkcs11
. . .
Элемент pkcs11_id хранит атрибут CKA_ID значение хэш SHA-1 от открытого ключа. Элемент cert_der это CKA_VALUE сертификата, pkcs11_label это CKA_LABEL.
Элемент pkcs11_id (CKA_ID в терминологии стандарта PKCS#11) является наравне с pkcs11_handle библиотеки и идентификатор слота с токеном pkcs11_slotid ключевым элементом для доступа к ключам и сертификатам, хранящимся на токенах.
Так, если мы хотим сменить метку (pkcs11_label) у сертификата или ключей, мы выполняем команду вида:
pki::pkcs11::rеname <cert|key|all> <список ключевых элементов>
Для удаления с токена сертификата или ключей выполняется команда вида:
pki::pkcs11::delete <cert|key|all> <список ключевых элементов>
Список ключевых элементов может формируется следующим образом:
set listparam {}
lappend listparam pkcs11_handle
lappend listparam $handle
lappend listparam pkcs11_slotid
lappend listparam $pkcs11_slotid
lappend listparam pkcs11_id
lappend listparam $pkcs11_id
и т.д.
Вызов функции в этом случае выглядит так (будем удалять сертификат и связанные с ним ключи):
pki::pkcs11::delete all $listparam
Читатель уже, наверное, догадался, что этот список можно оформить как словарь dict:
set listparam [dict create pkcs11_handle $pkcs11_handle]
dict set listparam pkcs11_slotid $pkcs11_slotid)
dict set listparam pkcs11_id $pkcs11_id
Есть и другие способы, например, через массив (array).
Еще раз отметим, что в списке ключевых элементов всегда должны присутствовать элементы pkcs11_handle и pkcs11_slotid, однозназно определяющие подключенный токен. Остальной состав определяется конкретной функцией.
Для установки на токен сертификата используется следующая функция:
set pkcs11_id_cert [::pki::pkcs11::importcert <cert_der_hex> <список ключевых параметров>
Функция возвращает значение CKA_ID в шестнадцатеричном виде. Список ключевых параметров определяет токен, на котором будет находится сертификат:
{pkcs11_handle <handle> pkcs11_slotid <slotid>}
На очереди у нас вычисление хэша. В российской криптографии сегодня используется три вида хэш функции:
— ГОСТ Р 34.11-94
— ГОСТ Р 34 .11-2022 с длиной хэш значения 256 бит (stribog256)
— ГОСТ Р 34 .11-2022 с длиной хэш значения 512 бит (stribog512)
Для определения какой хэш поддерживает токен у нас есть функция pki::pkcs11::listmechs.
Функция вычисления хэша имеет следующий вид:
set <результат> [pki::pkcs11::digest <gostr3411|stribog256|stribog512|sha1> <данные для хеширования> <список ключевых элементов>]
Отметим, что результат вычисления вычисления представляется в шестнадцатеричном виде:
. . .
set listparam [dict create pkcs11_handle $pkcs11_handle]
dict set listparam pkcs11_slotid $pkcs11_slotid
set res_hex [pki::pkcs11::digest stribog256 0123456789 $listparam]
puts $res_hex
086f2776f33aae96b9a616416b9d1fe9a049951d766709dbe00888852c9cc021
Для проверки возьмем
openssl с поддержкой российской криптографии
:
$ echo -n "0123456789"|/usr/local/lirssl_csp_64/bin/lirssl_s
tatic dgst -md_gost12_256
(stdin)= 086f2776f33aae96b9a616416b9d1fe9a0499 51d766709dbe00888852c9
cc021
$
Как видим, результат получен идентичный.
Для проверки электронной подписи будь то сертификат или список отозванных сертификатов или подписанный документ в формате нам теперь не хватает только функции проверки подписи:
set result [pki::pkcs11::verify <хэш документа> <подпись документа> <список ключевых элементов>]]
Если подпись прошла проверку, то возвращается 1, в противном случае – 0. Для проверки электронной подписи требуется сама подпись документа, хэш документа, определяемый типом подписи, и открытый ключ, которым была создана подпись, со всеми параметрами (значение, тип и параметры). Вся информация о ключе в виде asn1-структуры publickeyinfo должна быть включена в список ключевых элементов:
lpkar(pkcs11_handle) = pkcsmod0
lpkar(pkcs11_slotid) = 0
lpkar(pubkeyinfo) = 301f06082a85030701010101301306072a85030202240
006082a8503070101020203430004407d9306687af5a8e63af4b09443ed2e03794be
10eba6627bf5fb3da1bb474a3507d2ce2cd24b63c727a02521897d1dd6edbdc7084d
8886a39289c3f81bdf2e179
ASN1-структура публичного ключа берется из сертификата подписанта:
proc ::pki::x509::parse_cert_pubkeyinfo {cert_hex} {
array set ret [list]
set wholething [binary format H* $cert_hex]
::asn::asnGetSequence wholething cert
::asn::asnPeekByte cert peek_tag
if {$peek_tag != 0x02} {
# Version number is optional, if missing assumed to be value of 0
::asn::asnGetContext cert - asn_version
::asn::asnGetInteger asn_version ret(version)
}
::asn::asnGetBigInteger cert ret(serial_number)
::asn::asnGetSequence cert data_signature_algo_seq
::asn::asnGetObjectIdentifier data_signature_algo_seq ret(data_signature_algo)
::asn::asnGetSequence cert issuer
::asn::asnGetSequence cert validity
::asn::asnGetUTCTime validity ret(notBefore)
::asn::asnGetUTCTime validity ret(notAfter)
::asn::asnGetSequence cert subject
::asn::asnGetSequence cert pubkeyinfo
binary scan $pubkeyinfo H* ret(pubkeyinfo)
return $ret(pubkeyinfo)
}
Текст скрипта для проверки электронной подписи сертификатов из файла находится
Сохраните скрипт в файле и попробуйте его выполнить:
$./verify_cert_with_pkcs11.tcl
Copyright(C) Orlov Vladimir (http://museum.lissi-crypto.ru/)
Usage: verify_cert_with_pkcs11 <file with certificate> <file with CA certificate>
$
Можно удивиться, а как же сертификаты на токене? Первое, мы решали задачу использования криптографических машин PKCS#11. Мы их задействовали. А чтобы проветить сертификат с токена есть функция пакета pki::pkcs11::listcertsder, которая позволяет выбрать нужный сертификат и проверить его. Это можно рассматривать в качестве домашнего задания.
Появление новой вервии пакета TclPKCS11v.1.0.1 позволило доработать утилиту просмотра сертификатов, добавив в нее функции импорта сертификата на токен, удаление сертификатов и сопутствующих ключей с токена, смена меток сертификатов и ключей и т.д.:
Самая важная добавленная функция это проверка цифровой подписи сертификата:
Внимательный читатель правильно обратил внимание, что ничего не сказано о генерации ключевой пары. Эта функция также добавлена в пакет TclPKCS11:
array set genkey [pki::pkcs11::keypair <тип ключа> <параметр> <список ключевых элементов>]
Как используются функции из пакета TclPKCS11, естественно, можно найти в исходном коде утилиты.
Подробно функция генерации ключевой пары будет рассмотрена в следующей статье, когда будет представлена утилита создания запроса на квалифицированный сертификат с генерацией ключевой пары на токене PKCS#11, механизм получения сертификата в удостоверяющем центре (УЦ) и импорта его на токен:
В этой же статье будет рассмотрена и функция подписания документа. Это будет последняя статья из этой серии. Дальше планируется ряд статей о поддержки российской криптографии в модном сегодня скриптовом языке Ruby. До встречи!
Создание класса
Объявление класса в TclOO мало чем отличается от объявления класса в других языках. Класс в TclOO также содержит область данных, конструктор, область объектно-ориентированных методов и деструктор. При этом область данных, конструктор и деструктор могут опускаться.
Напомним, что конструктор вызывается при создание объекта (экземпляра объекта) заданного класса, а деструктор при его уничтожении. Конструктор (в отличии от деструктора), также как и методы, может иметь параметры. В нашем случае параметром для конструктора выступает сертификат в DER или PEM кодировке.
Области данных может предшествовать область наследуемых классов (superclass). Её будем рассматривать ниже. Но, для написания универсального класса certificate, эта область будет нами задействована.
В TclOO можно узнать какие классы в данный момент доступны в программе. Для этих целей служит команда следующего вида:
info class instances oo::class
В последующем, мы будем задействовать для наследования класс pubkey. Поэтому в нашем определении класса certificate присутствует проверка наличия класса pubkey и, если он присутствует, он объявляется как наследуемый (superclass pubkey).
Итак, ниже представлен класс для сертификата пока что с одним методом parse_cert, который возвращает список элементов сертификата:
В области данных командой variable определяются данные/переменные объекта через, которые доступны во всех методах класса.
Метод method определяется точно так же, как процедура proc Tcl. Методы могут иметь произвольное количество параметров. Внутри метода можно определять свои данные командой
my variable <идентификатор переменной>
. Методы могут быть публичными (экспортируемыми) и приватными.
Экспортируемые методы методы видимы за пределами класса. По умолчанию экспортируются методы начинаются со строчной буквы. По умолчанию методы, чьи имена начинаются с прописной буквы считаются неэкспортируемыми (приватными) методами. Область видимости независимо от первого символа можно задать явно. Для указания того, что метод является публичным служит следующая команда:
export <идентификатор метода>
Для запрета экспорта метода используется следующая команда:
unexport <идентификатор метода>
Для вызова одного метода из другого метода внутри класса используется команда my:
my <идентификатор метода>
Для этой же цели можно использовать внутреннюю команда класса self, которая возвращает идентификатор текущего объекта:
[self] <идентификатор метода>
Ниже мы увидим всё это.
Для дальнейшей работы соберем весь рассмотренный код в файле classparsecert.tcl.
После того как был определен класс можно создавать конкретный объект (экземпляр объекта). Для этого может быть использована одна из следующих команд:
или
set <переменная для идентификатора экземпляра класса > <имя класса> new [параметры для констуктура]
В первом случае программист сам назначает идентификатор для создаваемого экземпляра объекта. Этот идентификатор фактически будет командой, через которую осуществляется доступ к объекту и его методам:
Во втором случае идентификатор создаваемого объекта назначается интерпретатором и возвращается как результат выполнения команды new для указанного класса. В этом случае идентификатор объекта будет браться из этой переменной.
Интересно сравнить с созданием объекта в Python. И что мы видим? Несущественную синтаксическую разницу.
Напишем небольшой пример example1.tcl использования этого класса:
#Загружаем описание класса
source ./classparsecert.tcl
#Загружаем сертификат
set file [lindex $argv 0]
if {$argc != 1 || ![file exists $file]} {
puts "Usage: tclsh example1 <файл с сертификатом>"
exit
}
puts "Loading file: $file"
set fd [open $file]
chan configure $fd -translation binary
set data [read $fd]
close $fd
if {[catch {certificate create cert1 $data} er1]} {
puts "Файл не содержит СЕРТИФИКАТ"
exit
}
array set cert_parse [cert1 parse_cert]
#parray cert_parse
puts "Распарсенный сертификат"
foreach ind [array names cert_parse] {
puts "tcert_parse($ind)"
}
Выполним пример:
$tclsh ./example1.tcl
Loading file: minenergo.cer
Распарсенный сертификат
cert_parse(subject)
cert_parse(pubkeyinfo_hex)
cert_parse(extensions)
cert_parse(issuer)
cert_parse(data_signature_algo)
cert_parse(cert_full)
cert_parse(serial_number)
cert_parse(signature)
cert_parse(pubkey_algo)
cert_parse(notAfter)
cert_parse(signature_algo)
cert_parse(notBefore)
cert_parse(version)
cert_parse(tbsCert)
$