PKCS#11加密令牌:查看和导出证书,检查其有效性

图片 在对“用于查看俄罗斯合格x509证书的英语跨平台实用程序”一文的评论中, Pas用户希望不仅具有“证书解析”功能,而且希望获得“根证书链并进行PKI验证,至少对于具有可提取密钥的令牌的证书而言” ”。 在先前的文章中,已经描述了获得证书链的过程。 没错,这是关于存储在文件中的证书的,但是我们承诺要增加使用存储在PKCS#11令牌上的证书的机制。 最终就是这样。



解析和查看实用程序是用Tcl / Tk编写的,为了向其中添加PKCS#11令牌/智能卡,查看证书以及检查证书的有效性,有必要解决以下几个问题:

  • 确定从令牌/智能卡获取证书的机制;
  • 根据已撤销的CRL证书列表检查证书;
  • 通过OCSP机制检查有效性证书。

访问PKCS#11令牌


要访问令牌和存储在令牌上的证书,我们将使用TclPKCS11软件包。 该软件包以二进制和源代码形式分发。 当我们在程序包中添加带有俄罗斯加密的令牌支持时,源代码将派上用场。 有两种下载TclPKCS11软件包的方法,或使用以下形式的tcl命令:

load < tclpkcs11> Tclpkcs11 

或将tclpkcs11库和pkgIndex.tcl文件放入您方便的目录(在我们的情况下,这是当前目录的pkcs11子目录)中并将其添加到auto_path路径中,然后以pki :: pkcs11软件包的形式下载。

 #lappend auto_path [file dirname [info scrypt]] lappend auto_path pkcs11 package require pki package require pki::pkcs11 

由于我们主要对支持俄罗斯加密的令牌感兴趣,因此从TclPKCS11软件包中,我们将使用以下功能
 ::pki::pkcs11::loadmodule <filename> -> handle ::pki::pkcs11::unloadmodule <handle> -> true/false ::pki::pkcs11::listslots <handle> -> list: slotId label flags ::pki::pkcs11::listcerts <handle> <slotId> -> list: keylist ::pki::pkcs11::login <handle> <slotId> <password> -> true/false ::pki::pkcs11::logout <handle> <slotId> -> true/false 
立即预订,此处将不考虑登录和注销功能。 这是由于以下事实:在本文中,我们将仅处理证书,它们是公共令牌对象。 要访问公共对象,无需通过令牌上的PIN码登录。

第一个功能:: pki :: pkcs11 :: loadmodule是用于加载PKCS#11库,该库支持证书所在的令牌/智能卡。 可以通过购买令牌来获得库,也可以从Internet下载该库,也可以将其预先安装在计算机上。 无论如何,您都需要知道哪个库支持您的令牌。 loadmodule函数返回已加载库的句柄:

 set filelib "/usr/local/lib64/librtpkcs11ecp_2.0.so" set handle [::pki::pkcs11::loadmodule $filelib] 

因此,有一个函数可以卸载已加载的库:

 ::pki::pkcs11::unloadmodule $handle 

在加载库并获得其句柄之后,您可以获得该库支持的插槽列表:

 ::pki::pkcs11::listslots $handle {0 {ruToken ECP } {TOKEN_PRESENT RNG LOGIN_REQUIRED USER_PIN_INITIALIZED TOKEN_INITIALIZED REMOVABLE_DEVICE HW_SLOT}} {1 { } {REMOVABLE_DEVICE HW_SLOT}} . . . {14 { } {REMOVABLE_D EVICE HW_SLOT}} 

在此示例中,列表包含15个(0到14中的15个)元素。 RuToken系列令牌可以支持多少个插槽。 反过来,列表本身的每个元素都是三个元素的列表:

 {{ } { } {   }} 

列表的第一个元素是插槽号。 列表的第二个元素是位于令牌插槽中的标签(32个字节)。 如果插槽为空,则第二个元素包含32个空格。 列表的最后第三个元素包含标志。 我们不会考虑整个标志集。 我们对这些标志感兴趣的是TOKEN_PRESENT标志的存在。 正是这个标志指示令牌在插槽中,而我们感兴趣的证书可以在令牌上。 标志是非常有用的东西,它们描述令牌的状态,PIN码的状态等。 根据标志的值,管理PKCS#11令牌:



现在,没有什么可以阻止您编写slot_with_token过程,该过程将返回包含令牌标记的插槽列表:

 #!/usr/bin/tclsh lappend auto_path pkcs11 package require pki package require pki::pkcs11 #    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] } set filelib "/usr/local/lib64/librtpkcs11ecp_2.0.so" if {[catch {set handle [::pki::pkcs11::loadmodule $filelib]} res]} { puts "Cannot load library $filelib : $res" exit } #   set listslots {} set listslots [::slots_with_token $handle] #        while {[llength $listslots] == 0} { puts " " after 3000 set listslots [::slots_with_token $handle] } #        foreach {slotid labeltok} $listslots { puts "Number slot: $slotid" puts "Label token: $labeltok" } 

如果执行此脚本,将其保存在文件slot_with_token.tcl中,结果是:

 $ ./slots_with_token.tcl listtok(0) = ruToken ECP listtok(1) = RuTokenECP20 Number slot: 0 Label token: RuTokenECP20 Number slot: 1 Label token: ruToken ECP $ 

在该库的15个可用插槽中,仅涉及两个,零和第一个。
现在,什么都无法阻止获取位于特定令牌上的证书列表:

 set listcerts [::pki::pkcs11::listcerts $handle $slotid] 

每个列表项都包含有关一个证书的信息。 要从证书中获取信息,函数:: pki :: pkcs11 :: listcerts使用pki包中的函数:: pki :: x509 :: parse_cert。 但是功能:: pki :: pkcs11 :: listcerts用PKCS#11协议中固有的数据来补充此列表,即:

  • pkcs11_标签元素(在PKCS#11属性CKA_LABEL的术语中);
  • pkcs11_id元素(用PKCS#11属性CKA_ID表示);
  • pkcs11_handle元素,包含已加载的PKCS#11库的指示;
  • pkcs11_slotid元素,包含带有此证书所在的令牌的插槽号;
  • 一个类型元素,其中包含令牌上证书的pkcs11值。

回想一下,其余元素主要由pki :: parse_cert函数确定。
以下是获取证书标签列表(listCert)(CKA_LABEL,pkcs11_label)和已解析标识符数组(:: certs_p11)的过程。 访问证书数组元素的密钥是证书标签(CKA_LABEL,pkcs11_label):

 #  proc listcerttok {handle token_slotlabel token_slotid} { #     set listCer {} #   array set ::arrayCer [] set ::certs_p11 [pki::pkcs11::listcerts $handle $token_slotid] if {[llength $::certs_p11] == 0} { puts {Certificates are not on the token:$tokenslotlabel} return $listCer } foreach certinfo_list $::certs_p11 { unset -nocomplain certinfo array set certinfo $certinfo_list set certinfo(pubkeyinfo) [::pki::x509::parse_cert_pubkeyinfo $certinfo(cert)] set ::arrayCer($certinfo(pkcs11_label)) $certinfo(cert) lappend listCer $certinfo(pkcs11_label) } return $listCer } 

现在,我们已经解析了证书,现在可以在组合框内静默显示其标签列表:



如何解析我们在上一篇文章中讨论过的GOST公钥。

关于证书导出的两个词。 证书以PEM编码和DER编码(DER按钮,PEM格式)导出。 pki软件包具有方便的函数pki :: _ encode_pem,用于转换为PEM格式:

 set bufpem [::pki::_encode_pem <der-buffer> <Headline> <Lastline>] 

例如:

 set certpem [::pki::encode_pen $cert_der "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"] 

通过在组合框中选择化粪池证书标签,我们可以访问证书正文:

 #    set nick [.saveCert.labExp.listCert get] #        foreach certinfo_list $::certs_p11 { unset -nocomplain cert_parse array set cert_parse $certinfo_list if {$cert_parse(pkcs11_label) == $nick} { #   set cert_parse(pubkeyinfo) [::pki::x509::parse_cert_pubkeyinfo $cert_parse(cert)] break } } #   file|pkcs11 set ::tekcert "pkcs11" 

先前在这里讨论了解析证书并显示证书的另一种机制。

证书验证


解析证书时,变量:: notbefore和:: notafter存储用于加密操作(签名,加密等)的证书可以使用的日期以及证书的到期日期。 检查证书有效期的过程是:

 proc cert_valid_date {} { #       #    set startdate $::notbefore #    set enddate $::notafter #      set now [clock seconds] set isvalid 1 set reason "Certificate is valid" if {$startdate > $now} { set isvalid 0 #      set reason "Certificate is not yet valid" } elseif {$now > $enddate} { set isvalid 0 #    set reason "Certificate has expired" } return [list $isvalid $reason] } 

返回的列表包含两个项目。 第一个元素可以包含0(零)或1(一个)。 值“ 1”表示证书有效,0表示证书无效。 证书无效的原因在第二个元素中公开。 该元素可以包含以下三个值之一:

  • 证书有效(列表的第一个元素为1):
  • 证书尚未有效(证书尚未过期)
  • 证书已过期。

证书的有效性不仅取决于其有效期。 证书中心可以主动或应证书持有人的要求,例如在丢失带有私钥的介质的情况下,由证书中心暂停或终止证书。 在这种情况下,证书颁发机构会将证书包括在CA分发的已撤销COS / CRL证书列表中。 通常,证书中包括CRL分发点。 从已撤销的证书列表中检查证书的有效性。

通过SOS / CRL验证证书的有效性


第一步是获取SOS,然后解析它并检查证书。
证书扩展名为oid 2.5.29.31(id-ce-cRLDistributionPoints)的是COC / CRL颁发点的列表:

 array set extcert $cert_parse(extensions) set ::crlfile "" if {[info exists extcert(2.5.29.31)]} { set ::crlfile [crlpoints [lindex $extcert(2.5.29.31) 1]] } else { puts "cannot load CRL" } 

实际使用SOS / CRL加载文件如下:

 set filecrl "" set pointcrl "" foreach pointcrl $::crlfile { set filecrl [readca $pointcrl $dir] if {$filecrl != ""} { set f [file join $dir [file tail $pointcrl]] set fd [open $fw] chan configure $fd -translation binary puts -nonewline $fd $filecrl close $fd set filecrl $f break } # CRL  .     CRL } if {$filecrl == ""} { puts "Cannot load CRL" } 

实际上,readca过程用于加载COC / CRL:

 proc readca {url dir} { set cer "" #   if { "https://" == [string range $url 0 7]} { #    tls http::register https 443 ::tls::socket } #     if {[catch {set token [http::geturl $url -binary 1] #    set ere [http::status $token] if {$ere == "ok"} { #        set code [http::ncode $token] if {$code == 200} { #      set cer [http::data $token] } elseif {$code == 301 || $code == 302} { #    ,   set newURL [dict get [http::meta $token] Location] #     set cer [readca $newURL $dir] } else { #    set cer "" } } } error]} { #   ,     set cer "" } return $cer } 

dir变量存储将在其中保存COS / CRL的目录的路径,而url变量包含先前接收到的CRL分发点列表。

收到SOS / CRL时,我突然不得不面对这样一个事实,对于某些证书,该列表必须以匿名方式通过https(tls)协议接收。 老实说,这令人惊讶:CRL列表是公共文档,其完整性受电子签名的保护,我认为我可以通过匿名https访问它。 但是没有任何事要做,您必须连接tls软件包-软件包需要tls。

如果无法下载SOS / CRL,则如果未在证书中指定具有OCSP服务的访问点,则无法验证证书。 但这将在以下文章之一中讨论。

因此,这里有一个用于验证的证书,还有一个SOS / CRL列表,它仍然需要检查证书。 不幸的是,pki软件包中没有相应的功能。 因此,我必须编写一个程序来从已撤销的证书列表中检查证书的有效性(未撤销)。

validaty_cert_from_crl:
 proc validaty_cert_from_crl {crl sernum issuer} { array set ret [list] if { [string range $crl 0 9 ] == "-----BEGIN" } { array set parsed_crl [::pki::_parse_pem $crl "-----BEGIN X509 CRL-----" "-----END X509 CRL-----"] set crl $parsed_crl(data) } ::asn::asnGetSequence crl crl_seq ::asn::asnGetSequence crl_seq crl_base ::asn::asnPeekByte crl_base peek_tag if {$peek_tag == 0x02} { #   .CRL ::asn::asnGetInteger crl_base ret(version) incr ret(version) } else { set ret(version) 1 } ::asn::asnGetSequence crl_base crl_full ::asn::asnGetObjectIdentifier crl_full ret(signtype) ::::asn::asnGetSequence crl_base crl_issue set ret(issue) [::pki::x509::_dn_to_string $crl_issue] #     /CRL if {$ret(issue) != $issuer } { #/CRL    set ret(error) "Bad Issuer" return [array get ret] } binary scan $crl_issue H* ret(issue_hex) #  ::asn::asnGetUTCTime crl_base ret(publishDate) #   ::asn::asnGetUTCTime crl_base ret(nextDate) #   ::asn::asnPeekByte crl_base peek_tag if {$peek_tag != 0x30} { #    return [array get ret] } ::asn::asnGetSequence crl_base lcert # binary scan $lcert H* ret(lcert) while {$lcert != ""} { ::asn::asnGetSequence lcert lcerti #    ::asn::asnGetBigInteger lcerti ret(sernumrev) set ret(sernumrev) [::math::bignum::tostr $ret(sernumrev)] #      CRL if {$ret(sernumrev) != $sernum} { continue } # .    ::asn::asnGetUTCTime lcerti ret(revokeDate) if {$lcerti != ""} { #   ::asn::asnGetSequence lcerti lcertir ::asn::asnGetSequence lcertir reasone ::asn::asnGetObjectIdentifier reasone ret(reasone) ::asn::asnGetOctetString reasone reasone2 ::asn::asnGetEnumeration reasone2 ret(reasoneData) } break; } return [array get ret] } 

此功能的参数是证书吊销列表(crl),要验证的证书的序列号(序列号)及其发布者(发行者)。

证书吊销列表(crl)的加载方式如下:

 set f [open $filecrl r] chan configure $f -translation binary set crl [read $f] close $f 

已验证的证书(序列号)及其发布者(发行者)的序列号来自已解析的证书,并存储在变量:: sncert和:: issuercert中。

所有过程都可以在源代码中找到。 可在此处找到该实用程序的源代码及其Linux,OS X(macOS)和MS Windows发行版。


该实用程序还具有查看和验证文件中存储的证书的能力:



顺便说一句,从文件中查看的证书以及存储在令牌中的证书也可以导出。 这样可以轻松地将证书文件从DER格式转换为PEM,反之亦然。

现在,我们有了一个用于查看存储在文件和PKCS#11令牌/智能卡中的证书的单一查看器。

是的,我错过了要点:要检查证书的有效性,请单击“附加”按钮,然后选择菜单项“ CRL的有效性”,或者按鼠标右键,当光标位于主要信息上时,字段,然后选择菜单项“ CRL的有效期”:



此屏幕快照显示了云令牌中证书的浏览和验证。

总之,我们注意以下几点。 Pas用户在对文章的评论中非常正确地指出了PKCS#11令牌,他们“自己可以计算所有东西”。 是的,令牌实际上是加密计算机。 在以下文章中,我们将不仅讨论如何使用OCSP协议验证证书,还将讨论如何使用令牌/智能的加密机制(当然,我们所说的GOST加密)来计算哈希值(GOST R 34-10-) 94/2012),签名的形成和验证等。

Source: https://habr.com/ru/post/zh-CN443480/


All Articles