📜 ⬆️ ⬇️

Using PKCS # 11 cryptographic token mechanisms in scripting languages

In his comments on the article “An English-language cross-platform utility for viewing Russian x509 qualified certificates,” user Pas very correctly noted about PKCS # 11 tokens that they “can count everything themselves”. Yes, tokens are actually cryptographic computers. And the natural desire is to use these computers in scripting languages, be it Python, Perl or Ruby. We have already somehow considered the use of PKCS # 11 tokens with the support of Russian cryptography in Python for signing and encrypting documents to create a certificate request:

image

Here we will continue the conversation about the Tcl language. In the previous article , when we reviewed and validated certificates stored on PKCS # 11 tokens / smartcards, we used the TclPKCS11 version 0.9.9 package to access them (certificates). As already noted, unfortunately, the package was developed for RSA cryptography and taking into account the PKCS # 11 v.2.20 standard. Today, the PKCS # 11 v.2.40 standard is being used and the technical committee on cryptography TK-26 is guided by it, issuing recommendations for domestic manufacturers of tokens / smartcards that support Russian cryptography. And with all this in mind, a new package TclPKCS11 version 1.0.1 has appeared . Immediately make a reservation, all the cryptographic interfaces for RSA in the new version of the TclPKCS11 v.10.1 package are saved. The library package is written in C language.

So what's new in the package? First of all, a command was added that allows you to get a list of cryptographic mechanisms supported by the connected token:
')
::pki::pkcs11::listmechs <handl> <slotid> 

How to get a list of slots with connected tokens is shown here (procedure - 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] } 

Take a simple script:

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

This script allows you to get a list of mechanisms for GOSTR3410 cryptography supported on tokens of the RuToken family. For a start, let's take, as Pas wrote in the article , “the beloved of all kinds of EDO Rutoken Light”

 $ tclsh TEST_for_HABR.tcl listtok(0) = ruToken Lite 0 {ruToken Lite } $ 

And it will naturally turn out that he does not support any of the mezanism of GOST, which was to be proved. Take another token 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 $ 

Yes, this token supports Russian cryptography, but only the signature of GOST R 34.10-2001, which is practically out of use . But if you take the token Rutoken EDS-2.0, then everything will be fine, it supports GOST R 34.10-2012 with the keys of length 256 and 512 bits:

 $ 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 $ 

If we started talking about the support of Russian cryptography, including grasshopper and magma encryption algorithms, with these or other tokens, then software and cloud tokens most fully support it, and this is natural:

 $ tclsh TEST_for_HABR.tcl listtok(0) = LS11SW2016_LIN_64 0 {LS11SW2016_LIN_64 } 

List of mechanisms
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

 $ 

Go to the next new feature added to the package:

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

This function returns a list of certificates stored in the token. The question naturally arises, how does it differ from the existing function pki :: pkcs11 :: listcerts?

First of all, the new feature does not use the package :: pki. One of the returned items is the cert_der element, which contains the full certificate. This is convenient, for example, when exporting a certificate, or obtaining its print (fingetprint). Previously, you had to collect a full certificate from a tbs certificate and its signature. A complete list of returned items for each certificate is clearly visible when listing the contents of one certificate:

 . . . 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 . . . 

The pkcs11_id element stores the CKA_ID attribute value SHA-1 hash of the public key. The cert_der element is the CKA_VALUE certificate, pkcs11_label is CKA_LABEL.

The pkcs11_id element (CKA_ID in the terminology of the PKCS # 11 standard) is along with the pkcs11_handle library and the slot identifier with the token pkcs11_slotid the key element for accessing keys and certificates stored on tokens.

So, if we want to change the label (pkcs11_label) of the certificate or keys, we execute a command like this:

 pki::pkcs11::rname <cert|key|all> <  > 

To remove a certificate or keys from a token, the following command is executed:

 pki::pkcs11::delete <cert|key|all> <  > 

The list of key elements can be formed as follows:

 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 

etc.
The function call in this case looks like this (we will delete the certificate and its associated keys):

 pki::pkcs11::delete all $listparam 

The reader has probably guessed that this list can be arranged as a dict dictionary:

 set listparam [dict create pkcs11_handle $pkcs11_handle] dict set listparam pkcs11_slotid $pkcs11_slotid) dict set listparam pkcs11_id $pkcs11_id 

There are other ways, for example, through an array.

Once again, we note that the list of key elements must always contain the elements pkcs11_handle and pkcs11_slotid, which unambiguously determine the connected token. The rest of the composition is determined by a specific function.

To install on the certificate token, use the following function:

 set pkcs11_id_cert [::pki::pkcs11::importcert <cert_der_hex> <  > 

The function returns the value of CKA_ID in hexadecimal. The list of key parameters determines the token on which the certificate will be located:

 {pkcs11_handle <handle> pkcs11_slotid <slotid>} 

Next up is the hash calculation. In Russian cryptography today, three types of hash functions are used:
- GOST R 34.11-94
- GOST R 34 .11-2012 with a hash length of 256 bits (stribog256)
- GOST R 34 .11-2012 with a hash length of 512 bits (stribog512)
To determine which hash the token supports, we have the function pki :: pkcs11 :: listmechs.

The hash calculation function is as follows:

 set <> [pki::pkcs11::digest <gostr3411|stribog256|stribog512|sha1> <  > <  >] 

Note that the result of the calculation is represented in hexadecimal form:
 . . . 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 

For verification, take openssl with the support of Russian cryptography :

 $ echo -n "0123456789"|/usr/local/lirssl_csp_64/bin/lirssl_s tatic dgst -md_gost12_256 (stdin)= 086f2776f33aae96b9a616416b9d1fe9a0499 51d766709dbe00888852c9 cc021 $ 

As you can see, the result is identical.

To verify the electronic signature, whether it is a certificate or a list of revoked certificates or a signed document in the format, we now lack only the signature verification function:

 set result [pki::pkcs11::verify < > < > <  >]] 

If the signature has passed verification, then 1 is returned; otherwise, it returns 0. To verify the electronic signature, the document itself must be signed, the document hash determined by the signature type, and the public key that generated the signature with all parameters (value, type and parameters) . All key information in the form of an asn1 publickeyinfo structure should be included in the list of key elements:
lpkar (pkcs11_handle) = pkcsmod0
lpkar (pkcs11_slotid) = 0
lpkar (pubkeyinfo) = 301f06082a85030701010101301306072a85030202240
006082a8503070101020203430004407d9306687af5a8e63af4b09443ed2e03794be
10eba6627bf5fb3da1bb474a3507d2ce2cd24b63c727a02521897d1dd6edbdc7084d
8886a39289c3f81bdf2e179
The ASN1 public key structure is taken from the signer’s certificate:

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

The text of the script to verify the electronic signature of certificates from the file is
here
 #! /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 


Save the script in a file and try to execute it:

 $./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> $ 

It is surprising, but what about certificates on a token? First, we solved the problem of using PKCS # 11 cryptographic machines. We used them. And in order to get a certificate from a token, there is a function of the pki :: pkcs11 :: listcertsder package, which allows you to select the required certificate and check it. This can be considered a homework assignment.

The appearance of the new version of the TclPKCS11v.1.0.1 package allowed us to refine the certificate viewing utility , adding to it the functions of importing a certificate for a token, deleting certificates and associated keys from a token, changing the labels of certificates and keys, etc .:



The most important added feature is certificate digital signature verification:



The attentive reader correctly noted that nothing is said about the generation of the key pair. This feature is also added to the TclPKCS11 package:

 array set genkey [pki::pkcs11::keypair < > <> <  >] 

How the functions from the TclPKCS11 package are used can of course be found in the utility source code.

The function of generating a key pair will be discussed in detail in the next article, when the utility for creating a request for a qualified certificate will be presented with generating a key pair on the PKCS # 11 token, a mechanism for obtaining a certificate in the certification authority ( CA ) and importing it to the token:



In the same article will be considered and the function of signing the document. This will be the last article in this series. Further, a number of articles are planned on support of Russian cryptography in the Ruby scripting language that is in fashion today. See you!

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


All Articles