Infrastructure à clé publique Chaîne de certificats racine X509 v.3

L'heure "H" approche inexorablement: "l'utilisation du schéma de signature GOST R 34.10-2001 pour générer une signature après le 31 décembre 2018 n'est pas autorisée!"
Cependant, quelque chose a mal tourné, quelqu'un n'était pas prêt et l'utilisation de GOST R 34.10-2001 a été prolongée pour 2019. Mais tout à coup, tout le monde s'est précipité pour transférer l'AC vers GOST R 34.10-2012, et pour transférer les citoyens ordinaires vers de nouveaux certificats. Les gens ont plusieurs certificats en main. Lors de la vérification des certificats ou des signatures électroniques, des questions ont commencé à se poser et à savoir où obtenir les certificats racine à installer dans le magasin de certificats racine approuvés.

Cela s'applique aux magasins de certificats sur Windows et aux magasins de certificats sur Firefox et Google Chrome, GnuPG, LibreOffice, les clients de messagerie et même OpenSSL. Bien sûr, il était nécessaire de prendre soin de cela lors de la réception du certificat dans l'AC et d'écrire la chaîne de certificats sur la clé USB. Et d'autre part, nous avons une société numérique et à tout moment, nous devrions pouvoir obtenir cette chaîne du réseau. Simpleadmin a montré comment procéder sur les pages de Habr. Cependant, pour un citoyen ordinaire, c'est toujours difficile (surtout si vous prenez en compte que la grande majorité d'entre eux sont sous Windows): vous devez avoir une sorte de openssl, un utilitaire de récupération que je n'avais pas sur mon ordinateur, et tout le monde ne sait pas que vous pouvez utiliser wget à la place. Et combien d'actions doivent être effectuées. Bien sûr, il existe un moyen de sortir, d'écrire un script, mais pas seulement un script au-dessus de openssl et ses semblables, mais emballé dans un module exécutable autonome pour diverses plates-formes.

Il n'y avait aucun doute sur quoi écrire - en Tcl et Python . Et nous commençons par Tcl et c'est pourquoi :
* putain de wiki où il y a même des jouets (vous pouvez y voir des choses intéressantes :)
* feuilles de triche
* builds tclkit normaux (1,5 - 2 Mo en tant que frais pour une véritable multiplateforme)
* et mon assemblage eTcl préféré d'Evolane (soigneusement conservé du site du défunt :(
garder une cote Tcl / Tk élevée sur ma liste d'outils personnels
et, oui, wiki.tcl.tk/16867 (un petit serveur web de cgi à Tcl, périodiquement utilisé avec une constance enviable sous tclkit)
et c'est juste beau et beau :)
J'ajouterais à cela la disponibilité de l'utilitaire freewrap , qui nous aidera à créer des utilitaires autonomes pour Linux et MS Windows. En conséquence, nous aurons l'utilitaire chainfromcert:

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

En tant que paramètres, l'utilitaire définit un fichier avec un certificat utilisateur (au format PEM et DER) et le répertoire dans lequel les certificats CA inclus dans la chaîne seront enregistrés:

 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$ 

Considérez maintenant le fonctionnement de l'utilitaire.
Les informations sur l'autorité de certification qui a délivré le certificat à l'utilisateur sont stockées dans l'extension avec oid 1.3.6.1.5.5.7.1.1. Cette extension peut stocker à la fois l'emplacement du certificat CA (oid 1.3.6.1.5.5.7.48.2) et des informations sur le service OCSP CA (oid 1.3.6.1.5.5.7.48.1):



Et des informations, par exemple, sur la période d'utilisation de la clé de signature électronique sont stockées dans l'extension avec l'oid 2.5.29.16.

Pour analyser le certificat et accéder aux extensions de certificat, nous utiliserons le package pki:

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

Nous aurons également besoin du package base64:

 package require base64 

Le package pki, ainsi que le package asn et le package base64 qu'il charge, nous aideront à convertir les certificats d'un codage PEM en codage DER, à analyser les structures ASN et à accéder réellement aux informations sur l'emplacement des certificats CA.

L'utilitaire commence par vérifier les paramètres et télécharger le fichier avec le certificat:

 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 } 

Tout est clair ici et nous notons une seule chose - le fichier avec le certificat est considéré comme un fichier binaire:

 chan configure $fd -translation binary 

Cela est dû au fait que le certificat peut être stocké à la fois au format DER (code binaire) et au format PEM (encodage base64).

Une fois le fichier chargé, la procédure chainfromcert est appelée:

 set depth [chainfromcert $data $dir] 
qui télécharge en fait les certificats racine:

 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 } 

Il n'y a rien à ajouter aux commentaires, mais nous n'avons toujours pas considéré la procédure 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 } 

Cette procédure est basée sur l'utilisation du package http:

 package require http 

Pour lire le certificat, nous utilisons la fonction suivante:

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

Le but des autres fonctions utilisées ressort clairement des commentaires. Nous ne donnerons qu'un décryptage des codes retour pour la fonction http :: ncodel:
200 Demande terminée avec succès
206 La demande s'est terminée avec succès, mais seule une partie du fichier a été téléchargée
301 Le fichier a été déplacé vers un autre emplacement.
302 Le fichier a été temporairement déplacé vers un autre emplacement.
401 Authentification serveur requise
403 L'accès à cette ressource est refusé
404 La ressource spécifiée est introuvable.
500 Erreur interne
Une procédure reste à considérer, à savoir 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 } 

La procédure est très simple. S'il s'agit d'un fichier PEM avec un certificat ("----- BEGIN CERTIFICATE -----"), le corps de ce fichier est sélectionné et converti en code binaire:

 set asnblock [base64::decode $block] 

S'il ne s'agit pas d'un fichier PEM, cette «similitude» avec le codage asn est vérifiée (le bit zéro doit être 0x30).

C'est tout, il reste à ajouter les dernières lignes:

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

Maintenant, nous collectons tout dans un fichier avec le nom

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 


Vous pouvez vérifier le fonctionnement de ce fichier à l'aide de l'interpréteur 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 $ 

En conséquence, nous avons obtenu une chaîne de deux certificats dans le répertoire / tmp.

Mais nous voulions obtenir des modules exécutables pour les plates-formes Linux et Windows et pour que les utilisateurs ne pensent à aucun interprète.

Pour cela, nous utiliserons l'utilitaire freewrapTCLSH . En utilisant cet utilitaire, nous créerons des modules exécutables de notre utilitaire pour les plates-formes Linux et Windows, à la fois 32 bits et 64 bits. Les utilitaires peuvent être créés pour toutes les plateformes sur n'importe quelle plateforme. Désolé pour la tautologie. Je vais construire sur linux_x86_64 (Mageia).

Pour construire, vous aurez besoin de:
1. L'utilitaire freewrapTCLSH pour la plate-forme linux_x86_64;
2. Le fichier freewrapTCLSH avec cet utilitaire pour chaque plate-forme:
- freewrapTCLSH_linux32
- freewrapTCLSH_linux64
- freewrapTCLSH_win32
- freewrapTCLSH_win64
3. Le fichier source de notre utilitaire: chainfromcert.tcl
Ainsi, l'exécutable chainfromcerty_linuxx86 assemblé pour la plate-forme Linux x86:

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

L'assemblage de l'utilitaire pour la plate-forme Windows 64 bits ressemble à ceci:

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

Etc. Les utilitaires sont prêts à l'emploi. Ils ont absorbé tout le nécessaire pour leur travail.
Le code est écrit de la même manière en Python.

Dans les prochains jours, je pense à compléter le package fsb795 (qui est écrit en Python) avec la fonction d’obtention de la chaîne de certificats racine.

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


All Articles