📜 ⬆️ ⬇️

PKCS # 11 cryptographic tokens: viewing and exporting certificates, checking their validity

image In the comments to the article “English-language cross-platform utility for viewing Russian x509 qualified certificates”, it was a wish from Pas user not only to “parse certificates”, but also to receive “root certificate chains and perform PKI validation, at least for certificates on tokens with non-recoverable keys ". The receipt of the certificate chain was described in a previous article . True, it was about the certificates stored in files, but we promised to add mechanisms for working with certificates stored on PKCS # 11 tokens. And that's what happened in the end.



The parsing and viewing utility is written in Tcl / Tk and in order to add to it the viewing of certificates on PKCS # 11 tokens / smartcards, as well as checking the validity of certificates, several tasks were required:
')

PKCS # 11 Token Access


To access the token and the certificates stored on it, use the TclPKCS11 package. The package is distributed in both binary and source. The source codes will come in handy later when we add support for tokens with Russian cryptography to the package. You can download the TclPKCS11 package in two ways, either using the tcl command like:

load < tclpkcs11> Tclpkcs11 

Or simply download as pki :: pkcs11 package, first put the tclpkcs11 library and the pkgIndex.tcl file in a convenient directory (in our case, the current directory pkcs11 directory) and add it to the auto_path path:

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

Since we are interested in tokens primarily with the support of Russian cryptography, then from the TclPKCS11 package we will use the following functions :
 ::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 
Immediately make a reservation that the functions login and logout will not be considered here. This is due to the fact that within the framework of this article we will deal only with certificates, and they are public objects of the token. To access public objects, there is no need to authenticate through the PIN code on the token.

The first function :: pki :: pkcs11 :: loadmodule is designed to load the PKCS # 11 library, which supports the token / smart card on which the certificates are located. The library can be obtained either by purchasing a token, or downloaded from the Internet, or it was preinstalled on a computer. In any case, you need to know which library supports your token. The loadmodule function returns a pointer to the loaded library:

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

Accordingly, there is a function to upload the loaded library:

 ::pki::pkcs11::unloadmodule $handle 

After the library has been loaded and we have its handle, you can get a list of slots supported by this library:

 ::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}} 

In this example, the list contains 15 (fifteen from 0 to 14) elements. This is the number of slots the library of tokens of the RuToken family can support. In turn, each element of the list itself is a list of three elements:

 {{ } { } {   }} 

The first item in the list is the slot number. The second item in the list is a label located in the token slot (32 bytes). If the slot is empty, then the second element contains 32 spaces. And the last, third element of the list contains flags. We will not consider all the many flags. We are interested in these flags only the presence of the flag TOKEN_PRESENT. This flag indicates that there is a token in the slot, and the certificates of interest may be on the token. Flags are a very useful thing, they describe the state of the token, the state of PIN-codes, etc. PKCS # 11 tokens are managed based on the flag value:



Now nothing prevents to write the procedure slots_with_token, which will return a list of slots with labels of tokens in them:

 #!/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" } 

If we execute this script, having previously saved it in the file slots_with_token.tcl, then as a result we get:

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

Of the 15 available slots for this library, only two are involved, zero and first.
Now nothing prevents to get a list of certificates that are on a particular token:

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

Each list item contains information about a single certificate. To get information from the certificate, use the function :: pki :: pkcs11 :: listcerts uses in turn the function :: pki :: x509 :: parse_cert from the pki package. But the function :: pki :: pkcs11 :: listcerts complements this list with the data inherent in the PKCS # 11 protocol, namely:


Recall that the remaining elements are mainly determined by the function pki :: parse_cert.
Below is the procedure for obtaining a list of labels (listCert) of certificates (CKA_LABEL, pkcs11_label) and an array of parsed identifiers (:: certs_p11). The key to access the element of the certificate array is the certificate label (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 } 

And now, when we have parser certificates, we calmly display in the combobox a list of their labels:



How to parse GOST-new public keys, we considered in the previous article .

Two words about exporting a certificate. Certificates are exported both in PEM-encoding and DER-encoding (DER buttons, PEM-format). For conversion to PEM-format, the pki package has a convenient function pki :: _ encode_pem:

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

eg:

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

Selecting the septifikatnogo combobox, we get access to the body of the 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" 

A further mechanism for parsing a certificate and displaying it was previously discussed here .

Certificate validity check


When parsing a certificate, the variables :: notbefore and :: notafter store the date from which the certificate can be used in cryptographic operations (sign, encrypt, etc.) and the certificate’s expiration date. The procedure for checking the validity of the certificate is:

 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] } 

The returned list contains two items. The first element can contain either 0 (zero) or 1 (one). A value of “1” indicates that the certificate is valid, and 0 indicates that the certificate is not valid. The reason for which the certificate is not valid is disclosed in the second element. This element can contain one of three values:


The validity of the certificate is determined not only by the period of its validity. The certificate may be suspended or terminated by the certifying center, either on his initiative or at the request of the certificate holder, for example, if the carrier with the private key is lost. In this case, the certificate is included by the certification authority in the list of revoked COS / CRL certificates that are distributed by CAs. Typically, the CRL distribution point is included in the certificate. It is by the list of revoked certificates that the validity of the certificate is checked.

Checking the validity of the certificate for SOS / CRL


The first step is to get the SOS, then parse it and check the certificate for it.
The list of points of issue SOS / CRL is in the extension of the certificate with oid-ohm 2.5.29.31 (id-ce-cRLDistributionPoints):

 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" } 

Actually downloading a file from the COC / CRL is carried out as follows:

 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" } 

Actually, the readca procedure is used to load the COS / 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 } 

The dir variable stores the path to the directory in which the COC / CRL will be stored, and the url variable stores the previously obtained list of CRL distribution points.

Upon receipt of the COC / CRL, I suddenly had to face the fact that for some certificates this list has to be received via the https (tls) protocol in anonymous mode. Honestly, this is surprising: the CRL is a public document and its integrity is protected by an electronic signature and can be accessed by anonymous https in my opinion brute force. But there's nothing to do, you have to connect the tls package - package require tls.

If the COC / CRL failed to load, then the validity of the certificate cannot be verified unless the access point with the OCSP service is specified in the certificate. But this will be discussed in one of the following articles.

So, there is a certificate for verification, there is a COS / CRL list, it remains to check the certificate for it. Unfortunately, in the pki package there are no corresponding functions. Therefore, I had to write a procedure to check the validity of the certificate (its non-response) from the list of revoked certificates.

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] } 

The parameters of this function are the list of revoked certificates (crl), the serial number of the certificate being checked (sernum) and its publisher (issuer).

The list of revoked certificates (crl) is loaded as follows:

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

The serial number of the certificate to be verified (sernum) and its publisher (issuer) are taken from the parsed certificate and stored in the variables :: sncert and :: issuercert.

All procedures can be found in the source code. The source code of the utility and its distributions for Linux, OS X (macOS) and MS Windows platforms can be found here.


The utility also retains the ability to view and verify certificates stored in the file:



By the way, viewed certificates from files can also be exported, as well as those stored on a token. This makes it easy to convert files with certificates from DER-format to PEM and vice versa.

Now we have a single viewer for certificates stored both in files and on tokens / smartcards of PKCS # 11.

Yes, I missed the main point. To check the validity of the certificate, click the "Additionaly" button and select the "Validaty by CRL" menu item or right-click and, when the cursor is on the main information field and also select the menu item "Validity by SOS / CRL" ("Validaty by CRL"):



This screenshot shows the view and validation of certificates that are in a cloud token .

In conclusion, we note the following. In his comments on the article , Pas user very correctly noted about the PKCS # 11 tokens, that they “can count everything”. Yes, tokens are actually cryptographic computers. And in the following articles we will talk not only about how certificates are checked using the OCSP protocol, but also about how to use cryptographic mechanisms (this is, of course, GOST-cryptography) tokens / smart-cards for calculating the hash (GOST R 34-10- 94/2012), signature generation and verification, etc.

Source: https://habr.com/ru/post/443480/


All Articles