Utilisation des mécanismes de jetons cryptographiques PKCS # 11 dans les langages de script

Dans ses commentaires sur l' article «Un utilitaire multiplateforme anglophone pour visualiser les certificats x509 qualifiés en russe», Pas a noté très correctement les jetons PKCS # 11 qu'ils «peuvent tous se compter eux-mêmes». Oui, les jetons sont en fait des ordinateurs cryptographiques. Et il est naturel de vouloir utiliser ces ordinateurs dans des langages de script, que ce soit Python, Perl ou Ruby. Nous avons déjà envisagé l' utilisation des jetons PKCS # 11 avec prise en charge de la cryptographie russe en Python pour la signature et le cryptage de documents, afin de créer une demande de certificat:

image

Ici, nous continuons la discussion sur le langage Tcl. Dans l' article précédent, lorsque nous avons examiné l'affichage et la validation des certificats stockés sur les jetons / cartes à puce PKCS # 11, nous avons utilisé le package TclPKCS11 version 0.9.9 pour y accéder (certificats). Comme déjà indiqué, malheureusement, le package a été développé pour la cryptographie RSA et en tenant compte de la norme PKCS # 11 v.2.20. Aujourd'hui, la norme PKCS # 11 v.2.40 est déjà utilisée et c'est le comité technique de cryptographie TK-26 qui s'en guide, émettant des recommandations pour les fabricants nationaux de jetons / cartes à puce qui prennent en charge la cryptographie russe. Et avec tout cela dit, un nouveau package TclPKCS11 version 1.0.1 est apparu . Nous réserverons immédiatement que toutes les interfaces cryptographiques pour RSA dans la nouvelle version du package TclPKCS11 v.10.1 sont enregistrées. La bibliothèque de packages est écrite en langage C.

Alors, quoi de neuf dans le package? Tout d'abord, une commande a été ajoutée qui vous permet d'obtenir une liste des mécanismes cryptographiques pris en charge par le jeton connecté:

::pki::pkcs11::listmechs <handl> <slotid> 

Voici comment obtenir une liste des emplacements avec des jetons connectés (procédure - 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] } 

Prenez un script simple:

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

Ce script vous permet d'obtenir une liste des mécanismes de cryptographie GOSTR3410 pris en charge sur les jetons de la famille RuToken. Pour commencer, prenons, comme Pas l'a écrit dans l' article , «Rutoken Light aimé par toutes sortes d'EDO»:

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

Et naturellement, il s'avère qu'il ne soutient aucun mezanisme GOST, ce qui devait être prouvé. Nous prenons un autre jeton 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 $ 

Oui, ce jeton prend en charge la cryptographie russe, mais uniquement la signature de GOST R 34.10-2001, qui est presque hors d'usage . Mais si vous prenez le jeton Rutoken EDS-2.0, alors tout ira bien, il prend en charge GOST R 34.10-2012 avec des clés de 256 et 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 $ 

Si nous avons parlé de la prise en charge de la cryptographie russe, y compris des algorithmes de cryptage des sauterelles et du magma, avec l'un ou l'autre jeton, alors il est le plus pleinement pris en charge par les logiciels et les jetons cloud , et cela est naturel:

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

Liste des mécanismes
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

 $ 

Nous passons à la prochaine nouvelle fonctionnalité ajoutée au package:

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

Cette fonction renvoie une liste de certificats stockés par aucun jeton. La question se pose naturellement, mais en quoi diffère-t-elle de la fonction existante pki :: pkcs11 :: listcerts?

Tout d'abord, la nouvelle fonction n'utilise pas le package :: pki. L'un des éléments renvoyés est l'élément cert_der, qui contient le certificat complet. Cela est pratique, par exemple, lors de l'exportation d'un certificat ou de la réception de son empreinte digitale. Auparavant, je devais récupérer le certificat complet à partir du certificat tbs et de sa signature. Une liste complète des articles retournés pour chaque certificat est clairement visible lors de l'impression du contenu d'un certificat:

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

L'élément pkcs11_id stocke l'attribut CKA_ID la valeur du hachage SHA-1 de la clé publique. L'élément cert_der est le CKA_VALUE du certificat, pkcs11_label est CKA_LABEL.

L'élément pkcs11_id (CKA_ID dans la terminologie de la norme PKCS # 11) est, avec la bibliothèque pkcs11_handle, et l'identificateur d'emplacement avec le jeton pkcs11_slotid un élément clé pour accéder aux clés et certificats stockés sur les jetons.

Donc, si nous voulons changer le label (pkcs11_label) du certificat ou des clés, nous exécutons une commande du formulaire:

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

Pour supprimer un certificat ou des clés d'un jeton, une commande du formulaire est exécutée:

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

La liste des éléments clés peut être formée comme suit:

 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.
L'appel de fonction dans ce cas ressemble à ceci (nous allons supprimer le certificat et les clés qui lui sont associées):

 pki::pkcs11::delete all $listparam 

Le lecteur a probablement déjà deviné que cette liste peut être arrangée comme un dictionnaire dict:

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

Il existe d'autres façons, par exemple, via un tableau.

Encore une fois, nous notons que les éléments pkcs11_handle et pkcs11_slotid doivent toujours être présents dans la liste des éléments clés, qui identifient de manière unique le jeton connecté. Le reste de la composition est déterminé par une fonction spécifique.

La fonction suivante est utilisée pour installer le certificat sur le jeton:

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

La fonction renvoie la valeur CKA_ID en hexadécimal. La liste des paramètres clés détermine le jeton sur lequel le certificat sera situé:

 {pkcs11_handle <handle> pkcs11_slotid <slotid>} 

Ensuite, notre calcul de hachage. Aujourd'hui, dans la cryptographie russe, trois types de fonctions de hachage sont utilisés:
- GOST R 34.11-94
- GOST R 34 .11-2012 avec une valeur de hachage de 256 bits (stribog256)
- GOST R 34 .11-2012 avec une valeur de hachage de 512 bits (stribog512)
Pour déterminer quel hachage prend en charge le jeton, nous avons la fonction pki :: pkcs11 :: listmechs.

La fonction de calcul du hachage a la forme suivante:

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

Notez que le résultat du calcul du calcul est présenté en hexadécimal:
 . . . 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 

Pour la vérification, prenons openssl avec le support de la cryptographie russe :

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

Comme vous pouvez le voir, le résultat est identique.

Pour vérifier une signature électronique, qu'il s'agisse d'un certificat ou d'une liste de certificats révoqués ou d'un document signé dans un format, nous n'avons maintenant besoin que de la fonction de vérification de signature:

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

Si la signature a réussi la vérification, 1 est renvoyé, sinon 0. Pour vérifier la signature électronique, la signature du document elle-même, le hachage du document, déterminé par le type de signature, et la clé publique avec laquelle la signature a été créée, avec tous les paramètres (valeur, type et paramètres), sont requis. . Toutes les informations sur la clé sous la forme de la structure publickeyinfo asn1 doivent être incluses dans la liste des éléments clés:
lpkar (pkcs11_handle) = pkcsmod0
lpkar (pkcs11_slotid) = 0
lpkar (pubkeyinfo) = 301f06082a85030701010101301306072a85030202240
006082a8503070101020203430004407d9306687af5a8e63af4b09443ed2e03794be
10eba6627bf5fb3da1bb474a3507d2ce2cd24b63c727a02521897d1dd6edbdc7084d
8886a39289c3f81bdf2e179
La structure de la clé publique ASN1 est issue du certificat du signataire:

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

Le texte du script permettant de vérifier la signature électronique des certificats d'un fichier se trouve
ici
 #! /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 


Enregistrez le script dans un fichier et essayez de l'exécuter:

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

On se demande, qu'en est-il des certificats sur le token? Tout d'abord, nous avons résolu le problème de l'utilisation des machines cryptographiques PKCS # 11. Nous les avons utilisés. Et pour diffuser un certificat avec un jeton, il existe une fonction du package pki :: pkcs11 :: listcertsder, qui vous permet de sélectionner le certificat souhaité et de le vérifier. Cela peut être considéré comme un devoir.

L'apparition de la nouvelle version du package TclPKCS11v.1.0.1 a permis d'affiner l' utilitaire de visualisation des certificats en ajoutant les fonctions d'importation d'un certificat pour un jeton, de suppression de certificats et de clés associées d'un jeton, de modification des étiquettes des certificats et des clés, etc.:



La fonctionnalité la plus importante ajoutée est la vérification de la signature numérique du certificat:



Le lecteur attentif a correctement noté que rien n'a été dit sur la génération de la paire de clés. Cette fonctionnalité est également ajoutée au package TclPKCS11:

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

La façon dont les fonctions du package TclPKCS11 sont utilisées, bien sûr, peut être trouvée dans le code source de l'utilitaire.

La fonction de génération d'une paire de clés sera discutée en détail dans l'article suivant, lorsque l'utilitaire créera une demande de certificat qualifié avec la génération d'une paire de clés sur le jeton PKCS # 11, le mécanisme permettant d'obtenir un certificat dans un centre de certification ( CA ) et de l'importer dans un jeton:



Dans le même article, la fonction de signature d'un document sera considérée. Ce sera le dernier article de cette série. Ensuite, une série d'articles est prévue sur la prise en charge de la cryptographie russe dans le langage de script Ruby, qui est désormais à la mode. A très bientôt!

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


All Articles