Fichas criptográficas PKCS # 11: ver y exportar certificados, verificar su validez

imagen En los comentarios al artículo "Una utilidad multiplataforma de habla inglesa para ver certificados x509 calificados en Rusia", un usuario de Pas deseaba no solo "analizar los certificados", sino también recibir "cadenas de certificados raíz y llevar a cabo la validación PKI, al menos para certificados con tokens con una clave extraíble ". La obtención de una cadena de certificados se describió en uno de los artículos anteriores. Es cierto que se trataba de certificados almacenados en archivos, pero prometimos agregar mecanismos para trabajar con certificados almacenados en tokens PKCS # 11. Y entonces, ¿qué pasó al final?



La utilidad de análisis y visualización está escrita en Tcl / Tk y para agregarle tokens / tarjetas inteligentes PKCS # 11, además de verificar la validez de los certificados, se requirieron varias tareas:

  • determinar el mecanismo para obtener certificados del token / tarjeta inteligente;
  • verificar el certificado con la lista de certificados CRL revocados;
  • verificar el certificado de validez por el mecanismo OCSP.

Acceso a la ficha PKCS # 11


Para acceder al token y los certificados almacenados en él, utilizaremos el paquete TclPKCS11 . El paquete se distribuye tanto en binarios como en códigos fuente. Los códigos fuente serán útiles más adelante cuando agreguemos soporte de tokens con criptografía rusa al paquete. Hay dos formas de descargar el paquete TclPKCS11, o usar el comando tcl del formulario:

load < tclpkcs11> Tclpkcs11 

O descárguelo simplemente como el paquete pki :: pkcs11, después de colocar la biblioteca tclpkcs11 y el archivo pkgIndex.tcl en un directorio conveniente para usted (en nuestro caso, este es el subdirectorio pkcs11 del directorio actual) y agregarlo a la ruta auto_path:

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

Como estamos interesados ​​en tokens principalmente con soporte para la criptografía rusa, desde el paquete TclPKCS11 usaremos las siguientes funciones :
 ::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 
Haga inmediatamente una reserva de que las funciones de inicio y cierre de sesión no se considerarán aquí. Esto se debe al hecho de que en este artículo solo trataremos con certificados, y son objetos de token públicos. Para acceder a objetos públicos no es necesario iniciar sesión a través del código PIN en el token.

La primera función :: pki :: pkcs11 :: loadmodule es cargar la biblioteca PKCS # 11, que admite el token / tarjeta inteligente en la que se encuentran los certificados. Se puede obtener una biblioteca mediante la compra de un token, o descargarse de Internet, o se instaló previamente en una computadora. En cualquier caso, debe saber qué biblioteca admite su token. La función loadmodule devuelve un identificador a la biblioteca cargada:

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

En consecuencia, hay una función para descargar una biblioteca cargada:

 ::pki::pkcs11::unloadmodule $handle 

Una vez que se ha cargado la biblioteca y tenemos su identificador, puede obtener una lista de ranuras compatibles con esta biblioteca:

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

En este ejemplo, la lista contiene 15 (quince de 0 a 14) elementos. Esa es la cantidad de espacios que la familia de tokens RuToken puede admitir. A su vez, cada elemento de la lista en sí es una lista de tres elementos:

 {{ } { } {   }} 

El primer elemento de la lista es el número de ranura. El segundo elemento de la lista es la etiqueta ubicada en la ranura del token (32 bytes). Si la ranura está vacía, el segundo elemento contiene 32 espacios. Y el último, tercer elemento de la lista contiene banderas. No consideraremos todo el conjunto de banderas. Lo que nos interesa en estas banderas es la presencia de la bandera TOKEN_PRESENT. Es esta bandera la que indica que el token está en la ranura, y los certificados de interés para nosotros pueden estar en el token. Las banderas son algo muy útil, describen el estado del token, el estado de los códigos PIN, etc. Según el valor de los indicadores, se administran los tokens PKCS # 11:



Ahora, nada le impide escribir el procedimiento slots_with_token, que devolverá una lista de ranuras con las etiquetas de los tokens:

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

Si ejecuta este script, después de guardarlo en el archivo slots_with_token.tcl, como resultado obtenemos:

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

De los 15 espacios disponibles para esta biblioteca, solo dos están involucrados, cero y el primero.
Ahora nada impide obtener una lista de certificados ubicados en un token en particular:

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

Cada elemento de la lista contiene información sobre un certificado. Para obtener información del certificado, la función :: pki :: pkcs11 :: listcerts utiliza la función :: pki :: x509 :: parse_cert del paquete pki. Pero la función :: pki :: pkcs11 :: listcerts complementa esta lista con datos inherentes al protocolo PKCS # 11, a saber:

  • elemento etiqueta pkcs11_ (en la terminología del atributo PKCS # 11 CKA_LABEL);
  • elemento pkcs11_id (en la terminología del atributo PKCS # 11 CKA_ID);
  • elemento pkcs11_handle que contiene una indicación de la biblioteca PKCS # 11 cargada;
  • pkcs11_slotid elemento que contiene el número de la ranura con el token en el que se encuentra este certificado;
  • un elemento de tipo que contiene el valor pkcs11 para el certificado que está en el token.

Recuerde que los elementos restantes están determinados principalmente por la función pki :: parse_cert.
A continuación se muestra el procedimiento para obtener una lista de etiquetas (listCert) de certificados (CKA_LABEL, pkcs11_label) y una matriz de identificadores analizados (:: certs_p11). La clave para acceder al elemento de la matriz de certificados es la etiqueta del certificado (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 } 

Y ahora que hemos analizado los certificados, mostramos en silencio una lista de sus etiquetas en el cuadro combinado:



Cómo analizar las claves públicas de GOST que consideramos en el artículo anterior.

Dos palabras sobre la exportación de certificados. Los certificados se exportan tanto en codificación PEM como en codificación DER (botones DER, formato PEM). El paquete pki tiene una función conveniente pki :: _ encode_pem para convertir a formato PEM:

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

por ejemplo:

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

Al seleccionar la etiqueta del certificado séptico en el cuadro combinado, obtenemos acceso al cuerpo del certificado:

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

Un mecanismo adicional para analizar el certificado y mostrarlo se discutió anteriormente aquí .

Validación de certificado


Al analizar el certificado, las variables :: notbefore y :: notafter almacenan la fecha a partir de la cual se puede usar el certificado en operaciones criptográficas (firma, cifrado, etc.) y la fecha de vencimiento del certificado. El procedimiento para verificar el período de validez de un certificado es:

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

La lista devuelta contiene dos elementos. El primer elemento puede contener 0 (cero) o 1 (uno). Un valor de "1" indica que el certificado es válido y 0 indica que el certificado no es válido. La razón por la cual el certificado no es válido se revela en el segundo elemento. Este elemento puede contener uno de tres valores:

  • certificado válido (el primer elemento de la lista es 1):
  • el certificado aún no es válido (el certificado aún no ha caducado)
  • El certificado ha expirado.

La validez del certificado está determinada no solo por su período de validez. El centro de certificación puede suspender o rescindir el certificado, tanto por iniciativa propia como a solicitud del titular del certificado, por ejemplo, en caso de pérdida del medio con la clave privada. En este caso, la autoridad de certificación incluye el certificado en la lista de certificados COS / CRL revocados que son distribuidos por la CA. Por lo general, el punto de distribución de CRL se incluye en el certificado. Es de la lista de certificados revocados que se verifica la validez del certificado.

Validación de la validez del certificado por SOS / CRL


El primer paso es obtener el SOS, luego analizarlo y verificar el certificado.
La lista de puntos de emisión COC / CRL se encuentra en la extensión del certificado con oid 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" } 

En realidad, cargar el archivo con SOS / CRL es el siguiente:

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

En realidad, el procedimiento de lectura se usa para cargar el 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 } 

La variable dir almacena la ruta al directorio en el que se guardará el COS / CRL, y la variable url contiene la lista de puntos de distribución CRL recibida previamente.

Al recibir SOS / CRL, de repente tuve que enfrentar el hecho de que para algunos certificados esta lista tenía que recibirse a través del protocolo https (tls) en modo anónimo. Honestamente, esto es sorprendente: la lista de CRL es un documento público y su integridad está protegida por una firma electrónica y, en mi opinión, tengo acceso a ella a través de https anónimos. Pero no hay nada que hacer, debe conectar el paquete tls: el paquete requiere tls.

Si SOS / CRL no se pudo descargar, entonces el certificado no se puede verificar si el punto de acceso con el servicio OCSP no se especifica en el certificado. Pero esto se discutirá en uno de los siguientes artículos.

Entonces, hay un certificado para la verificación, hay una lista de SOS / CRL, queda por verificar el certificado. Desafortunadamente, no hay funciones correspondientes en el paquete pki. Por lo tanto, tuve que escribir un procedimiento para verificar la validez del certificado (su no revocación) de la lista de certificados revocados

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

Los parámetros para esta función son la lista de revocación de certificados (crl), el número de serie del certificado que se verifica (sernum) y su editor (emisor).

La lista de revocación de certificados (crl) se carga de la siguiente manera:

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

El número de serie del certificado verificado (sernum) y su editor (emisor) se toman del certificado analizado y se almacenan en las variables :: sncert y :: issuercert.

Todos los procedimientos se pueden encontrar en el código fuente. El código fuente de la utilidad y sus distribuciones para Linux, OS X (macOS) y MS Windows se pueden encontrar aquí.


La utilidad también conserva la capacidad de ver y verificar los certificados almacenados en un archivo:



Por cierto, los certificados vistos de los archivos también se pueden exportar, así como los almacenados en el token. Esto facilita la conversión de archivos de certificados del formato DER a PEM y viceversa.

Ahora tenemos un único visor para certificados almacenados tanto en archivos como en tokens / tarjetas inteligentes PKCS # 11.

Sí, perdí el punto: para verificar la validez del certificado, haga clic en el botón "Adicional" y seleccione el elemento de menú "Validación por CRL" o presione el botón derecho del mouse y cuando el cursor esté en la información principal campo y también seleccione el elemento de menú "Validez por CRL":



Esta captura de pantalla muestra la exploración y validación de certificados en un token en la nube.

En conclusión, observamos lo siguiente. En sus comentarios sobre el artículo , el usuario de Pas notó muy correctamente acerca de los tokens PKCS # 11 que "ellos mismos pueden contar todo". Sí, los tokens son en realidad computadoras criptográficas. Y en los siguientes artículos hablaremos no solo de cómo se verifican los certificados utilizando el protocolo OCSP, sino también de cómo usar mecanismos criptográficos (estamos hablando, por supuesto, de la criptografía GOST) de tokens / inteligencia para calcular el hash (GOST R 34-10- 94/2012), la formación y verificación de firmas, etc.

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


All Articles