Infraestructura de clave pública Cadena de certificados raíz X509 v.3

La hora "H" se acerca inexorablemente: "¡el uso del esquema de firma GOST R 34.10-2001 para generar una firma después del 31 de diciembre de 2018 no está permitido!"
Sin embargo, algo salió mal, alguien no estaba listo y el uso de GOST R 34.10-2001 se extendió para 2019. Pero, de repente, todos se apresuraron a transferir el CA a GOST R 34.10-2012, y a transferir ciudadanos comunes a nuevos certificados. Las personas tienen varios certificados en sus manos. Al verificar los certificados o las firmas electrónicas, comenzaron a surgir preguntas y dónde obtener certificados raíz para instalar en el almacén de certificados raíz de confianza.

Esto se aplica tanto a los almacenes de certificados en Windows como a los almacenes de certificados en Firefox y Google Chrome, GnuPG, LibreOffice, clientes de correo electrónico e incluso OpenSSL. Por supuesto, era necesario ocuparse de esto al recibir el certificado en la CA y escribir la cadena de certificados en la unidad flash USB. Y, por otro lado, tenemos una sociedad digital y en cualquier momento deberíamos poder obtener esta cadena de la red. Simpleadmin mostró cómo hacer esto en las páginas de Habr. Sin embargo, para un ciudadano común todavía es difícil (especialmente si tiene en cuenta que la gran mayoría de ellos están en Windows): necesita tener algún tipo de openssl, una utilidad de recuperación que no tenía en mi computadora, y no todos lo saben que puedes usar wget en su lugar. Y cuántas acciones deben realizarse. Por supuesto, hay una salida, escribir un script, pero no solo un script encima de openssl y sus características, sino que está empaquetado en un módulo ejecutable autónomo para varias plataformas.

No había duda sobre qué escribir, en Tcl y Python . Y comenzamos con Tcl y esta es la razón :
* puto wiki donde incluso hay juguetes (puedes ver cosas interesantes allí :)
* hojas de trucos
* compilaciones normales de tclkit (1.5 - 2 MB como tarifa para multiplataforma real)
* y mi ensamblaje eTcl favorito de Evolane (cuidadosamente preservado del sitio fallecido :(
mantener una alta calificación Tcl / Tk en mi lista de herramientas personales
y, sí, wiki.tcl.tk/16867 (un pequeño servidor web de cgi a Tcl, usado periódicamente con una constancia envidiable bajo tclkit)
y también es hermoso y hermoso :)
A esto agregaría la disponibilidad de la utilidad freewrap , que nos ayudará a construir utilidades independientes para Linux y MS Windows. Como resultado, tendremos la utilidad chainfromcert:

bash-4.3$ ./chainfromcert_linux64 Copyright(C)2019 Usage: chainfromcert <file with certificate> <directory for chain certificate> Bad usage! bash-4.3$ 

Como parámetros, la utilidad establece un archivo con un certificado de usuario (tanto en formato PEM como en formato DER) y el directorio en el que se guardarán los certificados de CA incluidos en la cadena:

 bash-4.3$ ./chainfromcert_linux64 ./cert_test.der /tmp Loading file: cert_test.der Directory for chain: . cert 1 from http://ca.ekey.ru/cdp/ekeyUC2012.cer cert 2 from http://reestr-pki.ru/cdp/guc_gost12.crt Goodby! Length chain=2 Copyright(C) 2019 bash-4.3$ 

Ahora considere cómo funciona la utilidad.
La información sobre la autoridad de certificación que emitió el certificado al usuario se almacena en la extensión con oid 1.3.6.1.5.5.7.1.1. Esta extensión puede almacenar tanto la ubicación del certificado de CA (oid 1.3.6.1.5.5.7.48.2) como la información sobre el servicio OCSP CA (oid 1.3.6.1.5.5.7.48.1):



Y la información, por ejemplo, sobre el período de uso de la clave de firma electrónica se almacena en la extensión con oid 2.5.29.16.

Para analizar el certificado y acceder a las extensiones de certificado, utilizaremos el paquete pki:

 #!/usr/bin/tclsh -f package require pki 

También necesitaremos el paquete base64:

 package require base64 

El paquete pki, así como el paquete asn y el paquete base64 que carga, nos ayudarán a convertir certificados de una codificación PEM a una codificación DER, analizar estructuras ASN y acceder a información sobre la ubicación de los certificados CA.

La utilidad comienza con la verificación de los parámetros y la descarga del archivo con el certificado:

 proc usage {use } { puts "Copyright(C) 2011-2019" if {$use == 1} { puts "Usage:\nchainfromcert <file with certificate> <directory for chain certificate>\n" } } if {[llength $argv] != 2 } { usage 1 puts "Bad usage!" exit } set file [lindex $argv 0] if {![file exists $file]} { puts "File $file not exist" usage 1 exit } puts "Loading file: $file" set dir [lindex $argv 1] if {![file exists $dir]} { puts "Dir $dir not exist" usage 1 exit } puts "Directory for chain: $dir" set fd [open $file] chan configure $fd -translation binary set data [read $fd] close $fd if {$data == "" } { puts "Bad file with certificate=$file" usage 1 exit } 

Aquí todo está claro y solo notamos una cosa: el archivo con el certificado se considera un archivo binario:

 chan configure $fd -translation binary 

Esto se debe al hecho de que el certificado puede almacenarse tanto en formato DER (código binario) como en formato PEM (codificación base64).

Después de cargar el archivo, se llama al procedimiento chainfromcert:

 set depth [chainfromcert $data $dir] 
que realmente descarga los certificados raíz:

 proc chainfromcert {cert dir} { if {$cert == "" } { exit } set asndata [cert_to_der $cert] if {$asndata == "" } { #    ,    return -1 } array set cert_parse [::pki::x509::parse_cert $asndata] array set extcert $cert_parse(extensions) if {![info exists extcert(1.3.6.1.5.5.7.1.1)]} { #    return 0 } set a [lindex $extcert(1.3.6.1.5.5.7.1.1) 0] # if {$a == "false"} { # puts $a # } # ASN1-   Hex- set b [lindex $extcert(1.3.6.1.5.5.7.1.1) 1] #    set c [binary format H* $b] #Sequence 1.3.6.1.5.5.7.1.1 ::asn::asnGetSequence c c_par_first #     1.3.6.1.5.5.7.1.1 while {[string length $c_par_first] > 0 } { #   (sequence) ::asn::asnGetSequence c_par_first c_par # oid   ::asn::asnGetObjectIdentifier c_par c_type set tas1 [::pki::_oid_number_to_name $c_type] #   ::asn::asnGetContext c_par c_par_two # oid     if {$tas1 == "1.3.6.1.5.5.7.48.2" } { #    set certca [readca $c_par $dir] if {$certca == ""} { #   .      continue } else { global count #      set f [file join $dir [file tail $c_par]] set fd [open $fw] chan configure $fd -translation binary puts -nonewline $fd $certca close $fd incr count puts "cert $count from $c_par" #     chainfromcert $certca $dir continue } } elseif {$tas1 == "1.3.6.1.5.5.7.48.1" } { # puts "OCSP server (oid=$tas1)=$c_par" } } #   return $count } 

No hay nada que agregar a los comentarios, pero aún no hemos considerado el procedimiento de readca:
 proc readca {url dir} { set cer "" #     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 } 

Este procedimiento se basa en el uso del paquete http:

 package require http 

Para leer el certificado, utilizamos la siguiente función:

 set token [http::geturl $url -binary 1] 

El propósito de las funciones restantes utilizadas es claro en los comentarios. Solo daremos un descifrado de los códigos de retorno para la función http :: ncodel:
200 Solicitud completada con éxito
206 Solicitud completada con éxito, pero solo se descargó parte del archivo
301 Archivo movido a otra ubicación.
302 Archivo movido temporalmente a otra ubicación.
Se requiere autenticación de servidor 401
403 Se deniega el acceso a este recurso
404 No se puede encontrar el recurso especificado.
500 error interno
Queda por considerar un procedimiento, a saber, cert_to_der:

 proc cert_to_der {data} { set lines [split $data \n] set hlines 0 set total 0 set first 0 # PEM-   foreach line $lines { incr total if {[regexp {^-----BEGIN CERTIFICATE-----$} $line]} { if {$first} { incr total -1 break } else { set first 1 incr hlines } } if {[regexp {^(.*):(.*)$} $line ]} { incr hlines } } if { $first == 0 && [string range $data 0 0 ] == "0" } { #   DER- "0" == 0x30 return $data } if {$first == 0} {return ""} set block [join [lrange $lines $hlines [expr {$total-1}]]] #from PEM to DER set asnblock [base64::decode $block] return $asnblock } 

El procedimiento es muy simple. Si se trata de un archivo PEM con un certificado ("----- BEGIN CERTIFICATE -----"), el cuerpo de este archivo se selecciona y se convierte en un código binario:

 set asnblock [base64::decode $block] 

Si este no es un archivo PEM, entonces se verifica esta "similitud" con la codificación asn (el bit cero debe ser 0x30).

Eso es todo, queda por agregar las líneas finales:

 if {$depth == -1} { puts "Bad file with certificate=$file" usage 1 exit } puts "Goodby!\nLength chain=$depth" usage 0 exit 

Ahora recopilamos todo en un archivo con el nombre

chainfromcert.tcl
 #!/usr/bin/tclsh encoding system utf-8 package require pki package require base64 #package require asn package require http global count set count 0 proc chainfromcert {cert dir} { if {$cert == "" } { exit } set asndata [cert_to_der $cert] if {$asndata == "" } { #    ,    return -1 } array set cert_parse [::pki::x509::parse_cert $asndata] array set extcert $cert_parse(extensions) if {![info exists extcert(1.3.6.1.5.5.7.1.1)]} { #    return 0 } set a [lindex $extcert(1.3.6.1.5.5.7.1.1) 0] # if {$a == "false"} { # puts $a # } # ASN1-   Hex- set b [lindex $extcert(1.3.6.1.5.5.7.1.1) 1] #    set c [binary format H* $b] #Sequence 1.3.6.1.5.5.7.1.1 ::asn::asnGetSequence c c_par_first #     1.3.6.1.5.5.7.1.1 while {[string length $c_par_first] > 0 } { #   (sequence) ::asn::asnGetSequence c_par_first c_par # oid   ::asn::asnGetObjectIdentifier c_par c_type set tas1 [::pki::_oid_number_to_name $c_type] #   ::asn::asnGetContext c_par c_par_two # oid     if {$tas1 == "1.3.6.1.5.5.7.48.2" } { #    set certca [readca $c_par $dir] if {$certca == ""} { #   .      continue } else { global count #      set f [file join $dir [file tail $c_par]] set fd [open $fw] chan configure $fd -translation binary puts -nonewline $fd $certca close $fd incr count puts "cert $count from $c_par" #     chainfromcert $certca $dir continue } } elseif {$tas1 == "1.3.6.1.5.5.7.48.1" } { # puts "OCSP server (oid=$tas1)=$c_par" } } #   return $count } proc readca {url dir} { set cer "" #     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 } proc cert_to_der {data} { set lines [split $data \n] set hlines 0 set total 0 set first 0 # PEM-   foreach line $lines { incr total # if {[regexp {^-----(.*?)-----$} $line]} {} if {[regexp {^-----BEGIN CERTIFICATE-----$} $line]} { if {$first} { incr total -1 break } else { set first 1 incr hlines } } if {[regexp {^(.*):(.*)$} $line ]} { incr hlines } } if { $first == 0 && [string range $data 0 0 ] == "0" } { #   DER- "0" == 0x30 return $data } if {$first == 0} {return ""} set block [join [lrange $lines $hlines [expr {$total-1}]]] #from PEM to DER set asnblock [base64::decode $block] return $asnblock } proc usage {use } { puts "Copyright(C) Orlov Vladimir 2011-2019" if {$use == 1} { puts "Usage:\nchainfromcert <file with certificate> <directory for chain certificate>\n" } } if {[llength $argv] != 2 } { usage 1 puts "Bad usage!" exit } set file [lindex $argv 0] if {![file exists $file]} { puts "File $file not exist" usage 1 exit } puts "Loading file: $file" set dir [lindex $argv 1] if {![file exists $dir]} { puts "Dir $dir not exist" usage 1 exit } puts "Directory for chain: $dir" set fd [open $file] chan configure $fd -translation binary set data [read $fd] close $fd if {$data == "" } { puts "Bad file with certificate=$file" usage 1 exit } set depth [chainfromcert $data $dir] if {$depth == -1} { puts "Bad file with certificate=$file" usage 1 exit } puts "Goodby!\nLength chain=$depth" usage 0 exit 


Puede verificar el funcionamiento de este archivo utilizando el intérprete tclsh:

 $ tclsh ./chainfromcert.tcl cert_orlov.der /tmp Loading file: cert_test.der Directory for chain: /tmp cert 1 from http://ca.ekey.ru/cdp/ekeyUC2012.cer cert 2 from http://reestr-pki.ru/cdp/guc_gost12.crt Goodby! Length chain=2 Copyright(C) 2019 $ 

Como resultado, obtuvimos una cadena de dos certificados en el directorio / tmp.

Pero queríamos obtener módulos ejecutables para las plataformas Linux y Windows y para que los usuarios no pensaran en ningún intérprete.

Para este propósito usaremos la utilidad freewrapTCLSH . Con esta utilidad, crearemos módulos ejecutables de nuestra utilidad para plataformas Linux y Windows, tanto de 32 bits como de 64 bits. Se pueden construir utilidades para todas las plataformas en cualquier plataforma. Perdón por la tautología. Construiré sobre linux_x86_64 (Mageia).

Para construir, necesitarás:
1. La utilidad freewrapTCLSH para la plataforma linux_x86_64;
2. El archivo freewrapTCLSH con esta utilidad para cada plataforma:
- freewrapTCLSH_linux32
- freewrapTCLSH_linux64
- freewrapTCLSH_win32
- freewrapTCLSH_win64
3. El archivo fuente de nuestra utilidad: chainfromcert.tcl
Entonces, el ejecutable chainfromcerty_linuxx86 ensamblado para la plataforma Linux x86:

 $freewrapTCLSH chainfromcert.tcl –w freewrapTCLSH_linux32 –o chainfromcerty_linuxx86 $ 

El ensamblaje de la utilidad para la plataforma Windows de 64 bits se ve así:

 $freewrapTCLSH chainfromcert.tcl –w freewrapTCLSH_win64 –o chainfromcerty_win64.exe $ 

Etc. Las utilidades están listas para usar. Han absorbido todo lo necesario para su trabajo.
El código está escrito de la misma manera en Python.

En los próximos días, estoy pensando en complementar el paquete fsb795 (que está escrito en Python) con la función de obtener la cadena de certificados raíz.

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


All Articles