В своих комментариях к статье «Англоязычная кроссплатформенная утилита для просмотра российских квалифицированных сертификатов 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 сохранены. Библиотека пакета написана на языке Си.
Итак, что нового появилось в пакете? Прежде всего добавлена команда, позволяющая получить список поддерживаемых подключенным токеном криптографических механизмов:
Как получить список слотов с подключенными токенами показано здесь (процедура — proc ::slots_with_token):
Возьмем простой скрипт:
Этот скрипт позволяет получить список механизмов для криптографии GOSTR3410, поддерживаемых на токенах семейства RuToken. Для начала возьмем, как писал в статье Pas, «любимый всякими ЭДО Рутокен Лайт»:
И естественно окажется, что он не поддерживает ни одного мезанизма ГОСТ, что и требовалось доказать. Берем другой токен Рутокен ЭЦП:
Да, этот токен поддерживает российскую криптографию, но только подпись ГОСТ Р 34.10-2001, которая практически вышла из употребления. Но если взять токен Рутокен ЭЦП-2.0, то все будет хорошо, он поддерживает ГОСТ Р 34.10-2012 с ключами длиной 256 и 512 бит:
Если мы заговорили о поддержке российской криптографии, включая алгоритмы шифрования кузнечик и магму, теми или иными токенами, то наиболее полно ее поддерживают программные и облачные токены и это естественно:
Переходим в следующей новой функции, добавленной в пакет:
Эта функция возвращает список сертификатов, хранящихся ни токене. Естественно возникает вопрос, а чем она отличается от уже имеющейся функции pki::pkcs11::listcerts?
Прежде всего новая функция не задействует пакет ::pki. Одним из возвращаемых элементов является элемент cert_der, содержащий полный сертификат. Это удобно, например, при экспорте сертификата, или получения его отпечатка (fingetprint). Ранее приходилось собирать полный сертификат из tbs-сертификата и его подписи. Полный перечень возвращаемых элементов для каждого сертификата наглядно видно при распечатке содержимого одного сертификата:
Элемент 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) у сертификата или ключей, мы выполняем команду вида:
Для удаления с токена сертификата или ключей выполняется команда вида:
Список ключевых элементов может формируется следующим образом:
и т.д.
Вызов функции в этом случае выглядит так (будем удалять сертификат и связанные с ним ключи):
Читатель уже, наверное, догадался, что этот список можно оформить как словарь dict:
Есть и другие способы, например, через массив (array).
Еще раз отметим, что в списке ключевых элементов всегда должны присутствовать элементы pkcs11_handle и pkcs11_slotid, однозназно определяющие подключенный токен. Остальной состав определяется конкретной функцией.
Для установки на токен сертификата используется следующая функция:
Функция возвращает значение CKA_ID в шестнадцатеричном виде. Список ключевых параметров определяет токен, на котором будет находится сертификат:
На очереди у нас вычисление хэша. В российской криптографии сегодня используется три вида хэш функции:
— ГОСТ Р 34.11-94
— ГОСТ Р 34 .11-2012 с длиной хэш значения 256 бит (stribog256)
— ГОСТ Р 34 .11-2012 с длиной хэш значения 512 бит (stribog512)
Для определения какой хэш поддерживает токен у нас есть функция pki::pkcs11::listmechs.
Функция вычисления хэша имеет следующий вид:
Отметим, что результат вычисления вычисления представляется в шестнадцатеричном виде:
Для проверки возьмем openssl с поддержкой российской криптографии:
Как видим, результат получен идентичный.
Для проверки электронной подписи будь то сертификат или список отозванных сертификатов или подписанный документ в формате нам теперь не хватает только функции проверки подписи:
Если подпись прошла проверку, то возвращается 1, в противном случае – 0. Для проверки электронной подписи требуется сама подпись документа, хэш документа, определяемый типом подписи, и открытый ключ, которым была создана подпись, со всеми параметрами (значение, тип и параметры). Вся информация о ключе в виде asn1-структуры publickeyinfo должна быть включена в список ключевых элементов:
Текст скрипта для проверки электронной подписи сертификатов из файла находится
Сохраните скрипт в файле и попробуйте его выполнить:
Можно удивиться, а как же сертификаты на токене? Первое, мы решали задачу использования криптографических машин PKCS#11. Мы их задействовали. А чтобы проветить сертификат с токена есть функция пакета pki::pkcs11::listcertsder, которая позволяет выбрать нужный сертификат и проверить его. Это можно рассматривать в качестве домашнего задания.
Появление новой вервии пакета TclPKCS11v.1.0.1 позволило доработать утилиту просмотра сертификатов, добавив в нее функции импорта сертификата на токен, удаление сертификатов и сопутствующих ключей с токена, смена меток сертификатов и ключей и т.д.:
Самая важная добавленная функция это проверка цифровой подписи сертификата:
Внимательный читатель правильно обратил внимание, что ничего не сказано о генерации ключевой пары. Эта функция также добавлена в пакет TclPKCS11:
Как используются функции из пакета TclPKCS11, естественно, можно найти в исходном коде утилиты.
Подробно функция генерации ключевой пары будет рассмотрена в следующей статье, когда будет представлена утилита создания запроса на квалифицированный сертификат с генерацией ключевой пары на токене PKCS#11, механизм получения сертификата в удостоверяющем центре (УЦ) и импорта его на токен:
В этой же статье будет рассмотрена и функция подписания документа. Это будет последняя статья из этой серии. Дальше планируется ряд статей о поддержки российской криптографии в модном сегодня скриптовом языке Ruby. До встречи!
Здесь же мы продолжим разговор о языке 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-2012 с ключами длиной 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) = LS11SW2016_LIN_64
0 {LS11SW2016_LIN_64 }
Список механизмов
CKM_GOSTR3410_KEY_PAIR_GEN
CKM_GOSTR3410_512_KEY_PAIR_GEN
CKM_GOSTR3410
CKM_GOSTR3410_512
CKM_GOSTR3410_WITH_GOSTR3411
CKM_GOSTR3410_WITH_GOSTR3411_12_256
CKM_GOSTR3410_WITH_GOSTR3411_12_512
CKM_GOSTR3410_DERIVE
CKM_GOSTR3410_12_DERIVE
CKM_GOSR3410_2012_VKO_256
CKM_GOSR3410_2012_VKO_512
CKM_KDF_4357
CKM_KDF_GOSTR3411_2012_256
CKM_KDF_TREE_GOSTR3411_2012_256
CKM_GOSTR3410_KEY_WRAP
CKM_GOSTR3410_PUBLIC_KEY_DERIVE
CKM_LISSI_GOSTR3410_PUBLIC_KEY_DERIVE
CKM_GOST_GENERIC_SECRET_KEY_GEN
CKM_GOST_CIPHER_KEY_GEN
CKM_GOST_CIPHER_ECB
CKM_GOST_CIPHER_CBC
CKM_GOST_CIPHER_CTR
CKM_GOST_CIPHER_OFB
CKM_GOST_CIPHER_CFB
CKM_GOST_CIPHER_OMAC
CKM_GOST_CIPHER_KEY_WRAP
CKM_GOST_CIPHER_ACPKM_CTR
CKM_GOST_CIPHER_ACPKM_OMAC
CKM_GOST28147_KEY_GEN
CKM_GOST28147
CKM_GOST28147_KEY_WRAP
CKM_GOST28147_PKCS8_KEY_WRAP
CKM_GOST_CIPHER_PKCS8_KEY_WRAP
CKM_GOST28147_ECB
CKM_GOST28147_CNT
CKM_GOST28147_MAC
CKM_KUZNYECHIK_KEY_GEN
CKM_KUZNYECHIK_ECB
CKM_KUZNYECHIK_CBC
CKM_KUZNYECHIK_CTR
CKM_KUZNYECHIK_OFB
CKM_KUZNYECHIK_CFB
CKM_KUZNYECHIK_OMAC
CKM_KUZNYECHIK_KEY_WRAP
CKM_KUZNYECHIK_ACPKM_CTR
CKM_KUZNYECHIK_ACPKM_OMAC
CKM_MAGMA_KEY_GEN
CKM_MAGMA_ECB
CKM_MAGMA_CBC
CKM_MAGMA_CTR
CKM_MAGMA_OFB
CKM_MAGMA_CFB
CKM_MAGMA_OMAC
CKM_MAGMA_KEY_WRAP
CKM_MAGMA_ACPKM_CTR
CKM_MAGMA_ACPKM_OMAC
CKM_GOSTR3411
CKM_GOSTR3411_12_256
CKM_GOSTR3411_12_512
CKM_GOSTR3411_HMAC
CKM_GOSTR3411_12_256_HMAC
CKM_GOSTR3411_12_512_HMAC
CKM_PKCS5_PBKD2
CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC
CKM_TLS_GOST_KEY_AND_MAC_DERIVE
CKM_TLS_GOST_PRE_MASTER_KEY_GEN
CKM_TLS_GOST_MASTER_KEY_DERIVE
CKM_TLS_GOST_PRF
CKM_TLS_GOST_PRF_2012_256
CKM_TLS_GOST_PRF_2012_512
CKM_TLS12_MASTER_KEY_DERIVE
CKM_TLS12_KEY_AND_MAC_DERIVE
CKM_TLS_MAC
CKM_TLS_KDF
CKM_TLS_TREE_GOSTR3411_2012_256
CKM_EXTRACT_KEY_FROM_KEY
CKM_SHA_1
CKM_MD5
CKM_GOSTR3410_512_KEY_PAIR_GEN
CKM_GOSTR3410
CKM_GOSTR3410_512
CKM_GOSTR3410_WITH_GOSTR3411
CKM_GOSTR3410_WITH_GOSTR3411_12_256
CKM_GOSTR3410_WITH_GOSTR3411_12_512
CKM_GOSTR3410_DERIVE
CKM_GOSTR3410_12_DERIVE
CKM_GOSR3410_2012_VKO_256
CKM_GOSR3410_2012_VKO_512
CKM_KDF_4357
CKM_KDF_GOSTR3411_2012_256
CKM_KDF_TREE_GOSTR3411_2012_256
CKM_GOSTR3410_KEY_WRAP
CKM_GOSTR3410_PUBLIC_KEY_DERIVE
CKM_LISSI_GOSTR3410_PUBLIC_KEY_DERIVE
CKM_GOST_GENERIC_SECRET_KEY_GEN
CKM_GOST_CIPHER_KEY_GEN
CKM_GOST_CIPHER_ECB
CKM_GOST_CIPHER_CBC
CKM_GOST_CIPHER_CTR
CKM_GOST_CIPHER_OFB
CKM_GOST_CIPHER_CFB
CKM_GOST_CIPHER_OMAC
CKM_GOST_CIPHER_KEY_WRAP
CKM_GOST_CIPHER_ACPKM_CTR
CKM_GOST_CIPHER_ACPKM_OMAC
CKM_GOST28147_KEY_GEN
CKM_GOST28147
CKM_GOST28147_KEY_WRAP
CKM_GOST28147_PKCS8_KEY_WRAP
CKM_GOST_CIPHER_PKCS8_KEY_WRAP
CKM_GOST28147_ECB
CKM_GOST28147_CNT
CKM_GOST28147_MAC
CKM_KUZNYECHIK_KEY_GEN
CKM_KUZNYECHIK_ECB
CKM_KUZNYECHIK_CBC
CKM_KUZNYECHIK_CTR
CKM_KUZNYECHIK_OFB
CKM_KUZNYECHIK_CFB
CKM_KUZNYECHIK_OMAC
CKM_KUZNYECHIK_KEY_WRAP
CKM_KUZNYECHIK_ACPKM_CTR
CKM_KUZNYECHIK_ACPKM_OMAC
CKM_MAGMA_KEY_GEN
CKM_MAGMA_ECB
CKM_MAGMA_CBC
CKM_MAGMA_CTR
CKM_MAGMA_OFB
CKM_MAGMA_CFB
CKM_MAGMA_OMAC
CKM_MAGMA_KEY_WRAP
CKM_MAGMA_ACPKM_CTR
CKM_MAGMA_ACPKM_OMAC
CKM_GOSTR3411
CKM_GOSTR3411_12_256
CKM_GOSTR3411_12_512
CKM_GOSTR3411_HMAC
CKM_GOSTR3411_12_256_HMAC
CKM_GOSTR3411_12_512_HMAC
CKM_PKCS5_PBKD2
CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC
CKM_TLS_GOST_KEY_AND_MAC_DERIVE
CKM_TLS_GOST_PRE_MASTER_KEY_GEN
CKM_TLS_GOST_MASTER_KEY_DERIVE
CKM_TLS_GOST_PRF
CKM_TLS_GOST_PRF_2012_256
CKM_TLS_GOST_PRF_2012_512
CKM_TLS12_MASTER_KEY_DERIVE
CKM_TLS12_KEY_AND_MAC_DERIVE
CKM_TLS_MAC
CKM_TLS_KDF
CKM_TLS_TREE_GOSTR3411_2012_256
CKM_EXTRACT_KEY_FROM_KEY
CKM_SHA_1
CKM_MD5
$
Переходим в следующей новой функции, добавленной в пакет:
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-2012 с длиной хэш значения 256 бит (stribog256)
— ГОСТ Р 34 .11-2012 с длиной хэш значения 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) = pkcsmod0ASN1-структура публичного ключа берется из сертификата подписанта:
lpkar(pkcs11_slotid) = 0
lpkar(pubkeyinfo) = 301f06082a85030701010101301306072a85030202240
006082a8503070101020203430004407d9306687af5a8e63af4b09443ed2e03794be
10eba6627bf5fb3da1bb474a3507d2ce2cd24b63c727a02521897d1dd6edbdc7084d
8886a39289c3f81bdf2e179
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)
}
Текст скрипта для проверки электронной подписи сертификатов из файла находится
здесь
#! /usr/bin/env tclsh
package require pki
lappend auto_path .
package require pki::pkcs11
#Задайте путь к вашей библиотеке PKCS#11
#set pkcs11_module "/usr/local/lib/libcackey.so"
#set pkcs11_module "/usr/local/lib64/librtpkcs11ecp_2.0.so"
set pkcs11_module "/usr/local/lib64/libls11sw2016.so"
puts "Connect the Token and press Enter"
gets stdin yes
set handle [pki::pkcs11::loadmodule $pkcs11_module]
set slots [pki::pkcs11::listslots $handle]
foreach slotinfo $slots {
set slotid [lindex $slotinfo 0]
set slotlabel [lindex $slotinfo 1]
set slotflags [lindex $slotinfo 2]
if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} {
set token_slotlabel $slotlabel
set token_slotid $slotid
#Найден слот с токеном
break
}
}
#Из PEM в DER
proc ::cert_to_der {data} {
if {[string first "-----BEGIN CERTIFICATE-----" $data] != -1} {
set data [string map {"\r\n" "\n"} $data]
}
array set parsed_cert [::pki::_parse_pem $data "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"]
if {[string range $parsed_cert(data) 0 0 ] == "0" } {
#Очень похоже на DER-кодировка "0" == 0x30
set asnblock $parsed_cert(data)
} else {
set asnblock ""
}
return $asnblock
}
proc usage {use error} {
puts "Copyright(C) Orlov Vladimir (http://soft.lissi.ru) 2019"
if {$use == 1} {
puts $error
puts "Usage:\nverify_cert_with_pkcs11 <file with certificate> \[<file with CA certificate>\]\n"
}
}
set countcert [llength $argv]
if { $countcert < 1 || $countcert > 2 } {
usage 1 "Bad usage!"
exit
}
set file [lindex $argv 0]
if {![file exists $file]} {
usage 1 "File $file not exist"
exit
}
#Проверяемый сертификат cert_user
puts "Loading user certificate: $file"
set fd [open $file]
chan configure $fd -translation binary
set cert_user [read $fd]
close $fd
if {$cert_user == "" } {
usage 1 "Bad file with certificate user: $file"
exit
}
set cert_user [cert_to_der $cert_user]
if {$cert_user == ""} {
puts "User certificate bad"
exit
}
catch {array set cert_parse [::pki::x509::parse_cert $cert_user]}
if {![info exists cert_parse]} {
puts "User certificate bad"
exit
}
#parray cert_parse
if {$countcert == 1} {
if {$cert_parse(issuer) != $cert_parse(subject)} {
puts "Bad usage: not self signed certificate"
} else {
set cert_CA $cert_user
}
} else {
set fileca [lindex $argv 1]
if {![file exists $fileca]} {
usage 1 "File $fileca not exist"
exit
}
#Сертификат издателя cert_CA
puts "Loading CA certificate: $fileca"
set fd [open $fileca]
chan configure $fd -translation binary
set cert_CA [read $fd]
close $fd
if {$cert_CA == "" } {
usage 1 "Bad file with certificate CA=$fileca"
exit
}
set cert_CA [cert_to_der $cert_CA]
if {$cert_CA == ""} {
puts "CA certificate bad"
exit
}
}
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 token_slotlabel $slotlabel
set token_slotid $slotid
}
}
#Ключ от корневого сертификата
#array set cert_parse_CA [::pki::x509::parse_cert $cert_CA]
catch {array set cert_parse_CA [::pki::x509::parse_cert $cert_CA]}
#array set cert_parse_CA [::pki::x509::parse_cert $cert_CA_256]
#array set cert_parse_CA [::pki::x509::parse_cert $CA_12_512]
if {![info exists cert_parse_CA]} {
puts "CA certificate bad"
exit
}
###############################
set aa [dict create pkcs11_handle $handle pkcs11_slotid $token_slotid]
set tbs_cert [binary format H* $cert_parse(cert)]
#puts "SIGN_ALGO1=$cert_parse(signature_algo)"
catch {set signature_algo_number [::pki::_oid_name_to_number $cert_parse(signature_algo)]}
if {![info exists signature_algo_number]} {
set signature_algo_number $cert_parse(signature_algo)
}
#puts "SIGN_ALGO=$signature_algo_number"
switch -- $signature_algo_number {
"1.2.643.2.2.3" - "1 2 643 2 2 3" {
# "GOST R 34.10-2001 with GOST R 34.11-94"
set digest_algo "gostr3411"
}
"1.2.643.7.1.1.3.2" - "1 2 643 7 1 1 3 2" {
# "GOST R 34.10-2012-256 with GOSTR 34.11-2012-256"
set digest_algo "stribog256"
}
"1.2.643.7.1.1.3.3" - "1 2 643 7 1 1 3 3" {
# "GOST R 34.10-2012-512 with GOSTR 34.11-2012-512"
set digest_algo "stribog512"
}
default {
puts "Неизвестная алгоритм подписи:$signature_algo_number"
exit
}
}
#Посчитать хэш от tbs-сертификата!!!!
set digest_hex [pki::pkcs11::digest $digest_algo $tbs_cert $aa]
puts "digest_hex=$digest_hex"
puts [string length $digest_hex]
#Получаем asn-структуру публичного ключа
#Создаем список ключевых элементов
binary scan $cert_CA H* cert_CA_hex
array set infopk [pki::pkcs11::pubkeyinfo $cert_CA_hex [list pkcs11_handle $handle pkcs11_slotid $token_slotid]]
parray infopk
set lpk [dict create pkcs11_handle $handle pkcs11_slotid $token_slotid]
#Добавляем pybkeyinfo в список ключевых элементов
lappend lpk "pubkeyinfo"
#lappend lpk $pubinfo
lappend lpk $infopk(pubkeyinfo)
array set lpkar $lpk
parray lpkar
puts "Enter PIN user for you token \"$token_slotlabel\":"
#set password "01234567"
gets stdin password
if { [pki::pkcs11::login $handle $token_slotid $password] == 0 } {
puts "Bad password"
exit
}
if {[catch {set verify [pki::pkcs11::verify $digest_hex $cert_parse(signature) $lpk]} res] } {
puts $res
exit
}
if {$verify != 1} {
puts "BAD SIGNATURE=$verify"
} else {
puts "SIGNATURE OK=$verify"
}
puts "Конец!"
exit
Сохраните скрипт в файле и попробуйте его выполнить:
$./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. До встречи!
kovserg
С нетерпением ждём сиатью с функцией подписания документа и добавлением метки времени.
saipr Автор
Утилита формирования запроса готова, а там тоже идет подписание документа (запроса). Так что проблем не вижу. Презентуем утилиту создания запроса, а затем, если есть востребованность, подготовим утилиту и для подписания (про время я запомнил) документов (PKCS#7).