
En sus comentarios sobre el
artículo "Una utilidad multiplataforma de habla inglesa para ver certificados rusos calificados x509",
Pas notó muy correctamente los tokens PKCS # 11 que "todos pueden contar por sí mismos". Sí, los tokens son en realidad computadoras criptográficas. Y es natural querer usar estas computadoras en lenguajes de script, ya sea Python, Perl o Ruby. Ya hemos considerado de alguna manera el
uso de tokens PKCS # 11 con soporte para la criptografía rusa en Python para firmar y encriptar documentos, para crear una solicitud de certificado:

Aquí continuamos la discusión sobre el lenguaje Tcl. En el
artículo anterior, cuando analizamos ver y validar certificados almacenados en tokens / tarjetas inteligentes PKCS # 11, utilizamos el paquete
TclPKCS11 versión 0.9.9 para acceder a ellos (certificados). Como ya se señaló, desafortunadamente, el paquete fue desarrollado para la criptografía RSA y teniendo en cuenta el estándar PKCS # 11 v.2.20. Hoy, el estándar PKCS # 11 v.2.40 ya se usa y es el comité técnico de criptografía TK-26 el que se guía por él, emitiendo recomendaciones para fabricantes nacionales de tokens / tarjetas inteligentes que admiten la criptografía rusa. Y con todo esto dicho,
ha aparecido un nuevo paquete
TclPKCS11 versión 1.0.1 . Haremos una reserva de inmediato para guardar todas las interfaces criptográficas para RSA en la nueva versión del paquete TclPKCS11 v.10.1. La biblioteca de paquetes está escrita en lenguaje C.
Entonces, ¿qué hay de nuevo en el paquete? En primer lugar, se agregó un comando que le permite obtener una lista de mecanismos criptográficos compatibles con el token conectado:
::pki::pkcs11::listmechs <handl> <slotid>
Aquí se muestra cómo obtener una lista de ranuras con tokens conectados (procedimiento - 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] }
Toma un guión 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
Este script le permite obtener una lista de los mecanismos de criptografía GOSTR3410 compatibles con los tokens de la familia RuToken. Para comenzar, tomemos, como
Pas escribió en el
artículo , "Rutoken Light amada por todo tipo de EDO":
$ tclsh TEST_for_HABR.tcl listtok(0) = ruToken Lite 0 {ruToken Lite } $
Y, naturalmente, resulta que él no es compatible con ningún mezanismo GOST, lo que debía probarse. Tomamos otro 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 $
Sí, este token admite la criptografía rusa, pero solo la firma de GOST R 34.10-2001, que está casi
fuera de uso . Pero si toma el token Rutoken EDS-2.0, entonces todo estará bien, es compatible con GOST R 34.10-2012 con claves de 256 y 512 bits de largo:
$ 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 hablamos sobre el soporte de la criptografía rusa, incluidos los algoritmos de cifrado de saltamontes y magma, con uno u otro token, entonces es más compatible con el software y
los tokens en la
nube , y esto es natural:
$ tclsh TEST_for_HABR.tcl listtok(0) = LS11SW2016_LIN_64 0 {LS11SW2016_LIN_64 }
Listado de mecanismosCKM_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
$
Pasamos a la siguiente nueva característica agregada al paquete:
set listcertsder [pki::pkcs11::listcertsder $handle $slotid]
Esta función devuelve una lista de certificados almacenados por ningún token. La pregunta surge naturalmente, pero ¿en qué se diferencia de la función existente pki :: pkcs11 :: listcerts?
En primer lugar, la nueva función no usa el paquete :: pki. Uno de los elementos devueltos es un elemento cert_der que contiene el certificado completo. Esto es conveniente, por ejemplo, al exportar un certificado o al recibir su huella digital.
Anteriormente, tenía que recoger el certificado completo del certificado tbs y su firma. Una lista completa de los artículos devueltos para cada certificado es claramente visible al imprimir el contenido de un certificado:
. . . 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 . . .
El elemento pkcs11_id almacena el atributo CKA_ID del valor del hash SHA-1 de la clave pública. El elemento cert_der es el CKA_VALUE del certificado, pkcs11_label es CKA_LABEL.
El elemento pkcs11_id (CKA_ID en la terminología del estándar PKCS # 11) es, junto con la biblioteca pkcs11_handle, y el identificador de ranura con el token pkcs11_slotid un elemento clave
para acceder a claves y certificados almacenados en tokens.
Entonces, si queremos cambiar la etiqueta (pkcs11_label) del certificado o las claves, ejecutamos un comando de la forma:
pki::pkcs11::rname <cert|key|all> < >
Para eliminar un certificado o claves de un token, un comando del formulario
pki::pkcs11::delete <cert|key|all> < >
La lista de elementos clave se puede formar de la siguiente manera:
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.
La llamada a la función en este caso se ve así (eliminaremos el certificado y sus claves):
pki::pkcs11::delete all $listparam
El lector probablemente ya ha adivinado que esta lista se puede organizar como un diccionario de dictados:
set listparam [dict create pkcs11_handle $pkcs11_handle] dict set listparam pkcs11_slotid $pkcs11_slotid) dict set listparam pkcs11_id $pkcs11_id
Hay otras formas, por ejemplo, a través de una matriz.
Una vez más, observamos que los elementos pkcs11_handle y pkcs11_slotid siempre deben estar presentes en la lista de elementos clave, que identifican de forma única el token conectado. El resto de la composición está determinada por una función específica.
La siguiente función se utiliza para instalar el certificado en el token:
set pkcs11_id_cert [::pki::pkcs11::importcert <cert_der_hex> < >
La función devuelve el valor CKA_ID en hexadecimal. La lista de parámetros clave determina el token en el que se ubicará el certificado:
{pkcs11_handle <handle> pkcs11_slotid <slotid>}
El siguiente es nuestro cálculo de hash. En la criptografía rusa de hoy se utilizan tres tipos de funciones hash:
- GOST R 34.11-94
- GOST R 34 .11-2012 con un valor hash de 256 bits (stribog256)
- GOST R 34 .11-2012 con un valor hash de 512 bits (stribog512)
Para determinar qué hash admite el token, tenemos la función pki :: pkcs11 :: listmechs.
La función de cálculo hash tiene la siguiente forma:
set <> [pki::pkcs11::digest <gostr3411|stribog256|stribog512|sha1> < > < >]
Tenga en cuenta que el resultado del cálculo del cálculo se presenta en hexadecimal:
. . . 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
Para la verificación, tomemos
openssl con soporte para la criptografía rusa :
$ echo -n "0123456789"|/usr/local/lirssl_csp_64/bin/lirssl_s tatic dgst -md_gost12_256 (stdin)= 086f2776f33aae96b9a616416b9d1fe9a0499 51d766709dbe00888852c9 cc021 $
Como puede ver, el resultado es idéntico.
Para verificar una firma electrónica, ya sea un certificado o una lista de certificados revocados o un documento firmado en un formato, ahora solo necesitamos la función de verificación de firma:
set result [pki::pkcs11::verify < > < > < >]]
Si la firma pasó la verificación, se devuelve 1; de lo contrario, 0. Para verificar la firma electrónica, se requiere la firma del documento en sí, el hash del documento, determinado por el tipo de firma, y la clave pública con la que se creó la firma, con todos los parámetros (valor, tipo y parámetros). . Toda la información sobre la clave en forma de publickeyinfo asn1 debe incluirse en la lista de elementos clave:
lpkar (pkcs11_handle) = pkcsmod0
lpkar (pkcs11_slotid) = 0
lpkar (pubkeyinfo) = 301f06082a85030701010101301306072a85030202240
006082a8503070101020203430004407d9306687af5a8e63af4b09443ed2e03794be
10eba6627bf5fb3da1bb474a3507d2ce2cd24b63c727a02521897d1dd6edbdc7084d
8886a39289c3f81bdf2e179
La estructura de clave pública ASN1 se toma del certificado firmante:
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) }
Se encuentra el texto del script para verificar la firma electrónica de los certificados de un archivo
aqui #! /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
Guarde el script en un archivo e intente ejecutarlo:
$./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> $
Uno se pregunta, ¿qué pasa con los certificados en el token? Primero, resolvimos el problema del uso de máquinas criptográficas PKCS # 11. Los usamos Y para emitir un certificado con un token, existe una función del paquete pki :: pkcs11 :: listcertsder, que le permite seleccionar el certificado deseado y verificarlo. Esto se puede considerar como tarea.
La aparición de la nueva versión del paquete TclPKCS11v.1.0.1 permitió refinar la
utilidad de visualización de certificados
al agregar las funciones de importar un certificado para un token, eliminar certificados y claves asociadas de un token, cambiar las etiquetas de certificados y claves, etc.

La característica más importante agregada es la verificación de firma digital del certificado:

El atento lector notó correctamente que no se dijo nada sobre la generación del par de claves. Esta característica también se agrega al paquete TclPKCS11:
array set genkey [pki::pkcs11::keypair < > <> < >]
Cómo se utilizan las funciones del paquete TclPKCS11, por supuesto, se puede encontrar en el código fuente de la utilidad.
La función de generar un par de claves se discutirá en detalle en el próximo artículo, cuando la utilidad creará una solicitud de un certificado calificado con la generación de un par de claves en el token PKCS # 11, el mecanismo para obtener un certificado en un centro de certificación (
CA ) e importarlo en un token:

En el mismo artículo, se considerará la función de firmar un documento. Este será el último artículo de esta serie. A continuación, se planea una serie de artículos para apoyar la criptografía rusa en el lenguaje de scripts Ruby, que ahora está de moda. Hasta pronto!