
在对“查看俄罗斯合格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标准,它是由密码学技术委员会TK-26指导的,向支持俄罗斯密码学的令牌/智能卡的国内制造商发布建议。
综上所述,已经出现了新的
TclPKCS11软件包
1.0.1版本 。 我们将立即预订,以保存新版本的TclPKCS11 v.10.1软件包中RSA的所有加密接口。 软件包库是用C语言编写的。
那么,软件包中有什么新内容? 首先,添加了一个命令,该命令使您可以获取所连接令牌支持的加密机制的列表:
::pki::pkcs11::listmechs <handl> <slotid>
此处显示
了如何获取带有已连接令牌的插槽列表(过程-proc :: slot_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
该脚本允许您获取RuToken系列令牌支持的GOSTR3410加密机制的列表。 首先,让我们以
Pas在
文章 “各种EDO钟爱的Rutoken Light”中写道:
$ tclsh TEST_for_HABR.tcl listtok(0) = ruToken Lite 0 {ruToken Lite } $
自然而然地证明,他不支持任何GOST主义,这是有待证明的。 我们使用另一个令牌Rutoken EDS:
$ 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 $
是的,此令牌支持俄语加密,但仅支持GOST R 34.10-2001的签名,该签名几乎已
失效 。 但是,如果您使用Rutoken EDS-2.0令牌,那么一切都会好起来,它支持GOST R 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 $

如果我们谈论使用一个或另一个令牌支持俄罗斯加密,包括蚱grass和岩浆加密算法,那么软件和
云令牌最全面地支持它,这很自然:
$ 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
$
我们转到添加到软件包中的下一个新功能:
set listcertsder [pki::pkcs11::listcertsder $handle $slotid]
此函数返回没有令牌存储的证书列表。 这个问题自然而然地出现了,但是它与现有的函数pki :: pkcs11 :: listcerts有何不同?
首先,新功能不使用:: pki包。 返回的元素之一是cert_der元素,其中包含完整的证书。 例如,在导出证书或接收其指纹时,这很方便。
以前,我必须从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元素(PKCS#11标准术语中的CKA_ID)与pkcs11_handle库和带有pkcs11_slotid令牌的插槽标识符一起,是
用于访问存储在令牌上的密钥和证书的密钥元素。
因此,如果我们想更改证书或密钥的标签(pkcs11_label),我们将执行以下形式的命令:
pki::pkcs11::rname <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
还有其他方法,例如通过数组。
再次,我们注意到pkcs11_handle和pkcs11_slotid元素应始终出现在唯一标识所连接令牌的关键元素列表中。 组成的其余部分由特定功能确定。
以下功能用于在令牌上安装证书:
set pkcs11_id_cert [::pki::pkcs11::importcert <cert_der_hex> < >
该函数以十六进制返回值CKA_ID。 关键参数列表确定证书将位于的令牌:
{pkcs11_handle <handle> pkcs11_slotid <slotid>}
接下来是我们的哈希计算。 如今,在俄罗斯密码学中,使用了三种类型的哈希函数:
-GOST R 34.11-94
-GOST R 34 .11-2012,哈希值为256位(stribog256)
-GOST R 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。要验证电子签名,需要具有所有参数(值,类型和参数)的文档签名本身,由签名类型确定的文档哈希以及用于创建签名的公钥。 。 以publickeyinfo asn1结构形式出现的所有关键信息都应包括在关键元素列表中:
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) }
位于用于验证文件中证书电子签名的脚本文本
在这里 #! /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令牌上生成密钥对并创建请求合格证书的实用程序时,将介绍在认证中心(
CA )中获取证书并将其导入令牌的机制:

在同一篇文章中,将考虑对文档进行签名的功能。 这将是本系列的最后一篇文章。 接下来,计划了一系列文章,以支持现在流行的Ruby脚本语言的俄罗斯加密技术。 待会见!