Infrastruktur für öffentliche Schlüssel X509 v.3 Stammzertifikatskette

Die Stunde "H" rückt unaufhaltsam näher: "Die Verwendung des Signaturschemas GOST R 34.10-2001 zur Erzeugung einer Signatur nach dem 31. Dezember 2018 ist nicht zulässig!"
Dann ging jedoch etwas schief, jemand war nicht bereit und die Verwendung von GOST R 34.10-2001 wurde für 2019 verlängert. Aber plötzlich beeilten sich alle, die CA auf GOST R 34.10-2012 zu übertragen und normale Bürger auf neue Zertifikate zu übertragen. Menschen haben mehrere Zertifikate in der Hand. Bei der Überprüfung von Zertifikaten oder elektronischen Signaturen tauchten Fragen auf und wo Stammzertifikate im Speicher vertrauenswürdiger Stammzertifikate installiert werden können.

Dies gilt sowohl für Zertifikatspeicher unter Windows als auch für Zertifikatspeicher unter Firefox und Google Chrome, GnuPG, LibreOffice, E-Mail-Clients und sogar OpenSSL. Natürlich musste dies beim Empfang des Zertifikats in der Zertifizierungsstelle berücksichtigt und die Zertifikatskette auf das USB-Flash-Laufwerk geschrieben werden. Andererseits haben wir eine digitale Gesellschaft und sollten diese Kette jederzeit aus dem Netzwerk holen können. Simpleadmin zeigte auf den Seiten von Habr, wie das geht. Für einen normalen Bürger ist es jedoch immer noch schwierig (insbesondere wenn Sie berücksichtigen, dass die überwiegende Mehrheit von ihnen unter Windows ausgeführt wird): Sie benötigen eine Art OpenSL, ein Abrufdienstprogramm, das ich nicht auf meinem Computer hatte, und nicht jeder weiß es dass Sie stattdessen wget verwenden können. Und wie viele Aktionen müssen ausgeführt werden. Natürlich gibt es einen Ausweg, um ein Skript zu schreiben, aber nicht nur ein Skript über openssl und seiner Art, sondern es ist in einem eigenständigen ausführbaren Modul für verschiedene Plattformen verpackt.

Es gab keinen Zweifel, worüber man schreiben sollte - in Tcl und Python . Und wir beginnen mit Tcl und deshalb :
* verdammtes Wiki, wo es sogar Spielzeug gibt (dort kann man interessante Dinge sehen :)
* Spickzettel
* normale tclkit-Builds (1,5 - 2 MB als Gebühr für echte plattformübergreifende)
* und meine Lieblings- eTcl- Baugruppe von Evolane (sorgfältig von der verstorbenen Stelle erhalten :(
Behalte eine hohe Tcl / Tk-Bewertung auf meiner persönlichen Toolkit-Liste
und ja, wiki.tcl.tk/16867 (ein kleiner Webserver von cgi bis Tcl, der regelmäßig mit beneidenswerter Konstanz unter tclkit verwendet wird)
und es ist einfach wunderschön und wunderschön :)
Dazu möchte ich die Verfügbarkeit des Dienstprogramms freewrap hinzufügen, mit dessen Hilfe wir eigenständige Dienstprogramme für Linux und MS Windows erstellen können. Als Ergebnis haben wir das Dienstprogramm chainfromcert:

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

Als Parameter legt das Dienstprogramm eine Datei mit einem Benutzerzertifikat (sowohl im PEM-Format als auch im DER-Format) und dem Verzeichnis fest, in dem in der Kette enthaltene CA-Zertifikate gespeichert werden:

 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$ 

Überlegen Sie nun, wie das Dienstprogramm funktioniert.
Informationen über die Zertifizierungsstelle, die das Zertifikat an den Benutzer ausgestellt hat, werden in der Erweiterung mit oid 1.3.6.1.5.5.7.1.1 gespeichert. Diese Erweiterung kann sowohl den Speicherort des CA-Zertifikats (oid 1.3.6.1.5.5.7.48.2) als auch Informationen zum OCSP-CA-Dienst (oid 1.3.6.1.5.5.7.48.1) speichern:



Informationen zur Nutzungsdauer des elektronischen Signaturschlüssels werden beispielsweise in der Erweiterung mit oid 2.5.29.16 gespeichert.

Um das Zertifikat zu analysieren und auf die Zertifikatserweiterungen zuzugreifen, verwenden wir das pki-Paket:

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

Wir benötigen auch das base64-Paket:

 package require base64 

Das pki-Paket sowie das geladene asn-Paket und das base64-Paket helfen uns, Zertifikate von einer PEM-Codierung in eine DER-Codierung zu konvertieren, ASN-Strukturen zu analysieren und tatsächlich auf Informationen über den Speicherort von CA-Zertifikaten zuzugreifen.

Das Dienstprogramm beginnt mit der Überprüfung der Parameter und dem Herunterladen der Datei mit dem Zertifikat:

 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 } 

Hier ist alles klar und wir stellen nur eines fest - die Datei mit dem Zertifikat wird als Binärdatei betrachtet:

 chan configure $fd -translation binary 

Dies liegt daran, dass das Zertifikat sowohl im DER-Format (Binärcode) als auch im PEM-Format (Base64-Codierung) gespeichert werden kann.

Nachdem die Datei geladen wurde, wird die Chainfromcert-Prozedur aufgerufen:

 set depth [chainfromcert $data $dir] 
welches tatsächlich die Stammzertifikate herunterlädt:

 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 } 

Es gibt nichts zu den Kommentaren hinzuzufügen, aber wir haben das Readca-Verfahren immer noch nicht berücksichtigt:
 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 } 

Dieses Verfahren basiert auf der Verwendung des http-Pakets:

 package require http 

Zum Lesen des Zertifikats verwenden wir die folgende Funktion:

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

Der Zweck der verbleibenden Funktionen ist aus den Kommentaren ersichtlich. Wir werden nur eine Entschlüsselung der Rückkehrcodes für die Funktion http :: ncodel geben:
200 Anfrage erfolgreich abgeschlossen
206 Anforderung erfolgreich abgeschlossen, aber nur ein Teil der Datei wurde heruntergeladen
301 Datei an einen anderen Speicherort verschoben.
302 Datei vorübergehend an einen anderen Speicherort verschoben.
401 Serverauthentifizierung erforderlich
403 Der Zugriff auf diese Ressource wird verweigert
404 Die angegebene Ressource wurde nicht gefunden.
500 Interner Fehler
Ein Verfahren muss noch berücksichtigt werden, nämlich 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 } 

Das Verfahren ist sehr einfach. Wenn es sich um eine PEM-Datei mit einem Zertifikat handelt ("----- BEGIN CERTIFICATE -----"), wird der Hauptteil dieser Datei ausgewählt und in einen Binärcode konvertiert:

 set asnblock [base64::decode $block] 

Wenn dies keine PEM-Datei ist, wird diese „Ähnlichkeit“ mit der ASN-Codierung überprüft (das Nullbit sollte 0x30 sein).

Das ist alles, es bleibt noch die letzten Zeilen hinzuzufügen:

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

Jetzt sammeln wir alles in einer Datei mit dem Namen

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 


Sie können den Betrieb dieser Datei mit dem tclsh-Interpreter überprüfen:

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

Als Ergebnis haben wir eine Kette von zwei Zertifikaten im Verzeichnis / tmp.

Wir wollten jedoch ausführbare Module für die Linux- und Windows-Plattformen, damit die Benutzer nicht an Interpreter denken.

Zu diesem Zweck verwenden wir das Dienstprogramm freewrapTCLSH . Mit diesem Dienstprogramm erstellen wir ausführbare Module unseres Dienstprogramms für Linux- und Windows-Plattformen, sowohl 32-Bit als auch 64-Bit. Dienstprogramme können für alle Plattformen auf jeder Plattform erstellt werden. Entschuldigung für die Tautologie. Ich werde auf linux_x86_64 (Mageia) aufbauen.

Zum Bauen benötigen Sie:
1. Das Dienstprogramm freewrapTCLSH für die Plattform linux_x86_64.
2. Die freewrapTCLSH-Datei mit diesem Dienstprogramm für jede Plattform:
- freewrapTCLSH_linux32
- freewrapTCLSH_linux64
- freewrapTCLSH_win32
- freewrapTCLSH_win64
3. Die Quelldatei unseres Dienstprogramms: chainfromcert.tcl
Also, die zusammengestellte Chainfromcerty_linuxx86-ausführbare Datei für die Linux x86-Plattform:

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

Die Zusammenstellung des Dienstprogramms für die Windows 64-Bit-Plattform sieht folgendermaßen aus:

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

Usw. Dienstprogramme sind einsatzbereit. Sie haben alles aufgenommen, was für ihre Arbeit notwendig ist.
Der Code wird in Python auf die gleiche Weise geschrieben.

In den kommenden Tagen denke ich darüber nach, das in Python geschriebene Paket fsb795 durch die Funktion zum Abrufen der Stammzertifikatskette zu ergänzen.

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


All Articles