Kryptografische Token von PKCS # 11: Anzeigen und Exportieren von Zertifikaten, Überprüfen ihrer Gültigkeit

Bild In den Kommentaren zum Artikel „Ein plattformübergreifendes Dienstprogramm in englischer Sprache zum Anzeigen russisch qualifizierter x509-Zertifikate“ wurde von einem Pas- Benutzer der Wunsch gefordert, nicht nur „Zertifikatsanalyse“ durchzuführen, sondern auch „Ketten von Stammzertifikaten zu erhalten und eine PKI-Validierung durchzuführen, zumindest für Zertifikate mit Token mit einem nicht extrahierenden Schlüssel ". Das Erhalten einer Zertifikatskette wurde in einem der vorherigen Artikel beschrieben. Es ging zwar um in Dateien gespeicherte Zertifikate, aber wir haben versprochen, Mechanismen für die Arbeit mit Zertifikaten hinzuzufügen, die auf PKCS # 11-Token gespeichert sind. Und genau das ist am Ende passiert.



Das Dienstprogramm zum Parsen und Anzeigen ist in Tcl / Tk geschrieben. Um PKCS # 11-Token / Smartcards hinzuzufügen, Zertifikate anzuzeigen und die Gültigkeit von Zertifikaten zu überprüfen, mussten verschiedene Probleme gelöst werden:

  • den Mechanismus zum Erhalten von Zertifikaten vom Token / der Smartcard bestimmen;
  • Überprüfen Sie das Zertifikat anhand der Liste der widerrufenen CRL-Zertifikate.
  • Überprüfen Sie das Gültigkeitszertifikat durch den OCSP-Mechanismus.

Zugriff auf PKCS # 11 Token


Um auf das Token und die darauf gespeicherten Zertifikate zuzugreifen, verwenden wir das TclPKCS11- Paket. Das Paket wird sowohl in Binärdateien als auch in Quellcodes verteilt. Die Quellcodes werden später nützlich sein, wenn wir dem Paket Token-Unterstützung mit russischer Kryptographie hinzufügen. Es gibt zwei Möglichkeiten, das TclPKCS11-Paket herunterzuladen oder den Befehl tcl des Formulars zu verwenden:

load < tclpkcs11> Tclpkcs11 

Oder laden Sie es einfach als pki :: pkcs11-Paket herunter, nachdem Sie die Bibliothek tclpkcs11 und die Datei pkgIndex.tcl in ein für Sie geeignetes Verzeichnis gestellt haben (in unserem Fall ist dies das Unterverzeichnis pkcs11 des aktuellen Verzeichnisses) und dem Pfad auto_path hinzugefügt haben:

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

Da wir an Token interessiert sind, die hauptsächlich die russische Kryptographie unterstützen, werden wir aus dem TclPKCS11-Paket die folgenden Funktionen verwenden :
 ::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 
Reservieren Sie sofort, dass die Anmelde- und Abmeldefunktionen hier nicht berücksichtigt werden. Dies liegt an der Tatsache, dass in diesem Artikel nur Zertifikate behandelt werden und es sich um öffentliche Tokenobjekte handelt. Um auf öffentliche Objekte zugreifen zu können, müssen Sie sich nicht über den PIN-Code auf dem Token anmelden.

Die erste Funktion :: pki :: pkcs11 :: loadmodule besteht darin, die PKCS # 11-Bibliothek zu laden, die das Token / die Smartcard unterstützt, auf der sich die Zertifikate befinden. Eine Bibliothek kann entweder durch den Kauf eines Tokens erworben oder aus dem Internet heruntergeladen oder auf einem Computer vorinstalliert werden. In jedem Fall müssen Sie wissen, welche Bibliothek Ihr Token unterstützt. Die Funktion loadmodule gibt ein Handle an die geladene Bibliothek zurück:

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

Dementsprechend gibt es eine Funktion zum Entladen einer geladenen Bibliothek:

 ::pki::pkcs11::unloadmodule $handle 

Nachdem die Bibliothek geladen wurde und wir das Handle haben, können Sie eine Liste der von dieser Bibliothek unterstützten Slots abrufen:

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

In diesem Beispiel enthält die Liste 15 (fünfzehn von 0 bis 14) Elemente. So viele Slots kann die Token-Familie RuToken unterstützen. Jedes Element der Liste selbst ist wiederum eine Liste von drei Elementen:

 {{ } { } {   }} 

Das erste Element der Liste ist die Steckplatznummer. Das zweite Element der Liste ist die Bezeichnung im Token-Slot (32 Byte). Wenn der Steckplatz leer ist, enthält das zweite Element 32 Leerzeichen. Und das letzte, dritte Element der Liste enthält Flags. Wir werden nicht den gesamten Satz von Flags betrachten. Was uns an diesen Flags interessiert, ist das Vorhandensein des TOKEN_PRESENT-Flags. Dieses Flag zeigt an, dass sich das Token im Steckplatz befindet und sich die für uns interessanten Zertifikate auf dem Token befinden können. Flags sind eine sehr nützliche Sache, sie beschreiben den Status des Tokens, den Status von PIN-Codes usw. Basierend auf dem Wert der Flags werden PKCS # 11-Token verwaltet:



Jetzt hindert Sie nichts mehr daran, die Prozedur slots_with_token zu schreiben, die eine Liste der Slots mit den Beschriftungen der darin enthaltenen Token zurückgibt:

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

Wenn Sie dieses Skript ausführen, nachdem Sie es in der Datei slots_with_token.tcl gespeichert haben, erhalten wir als Ergebnis:

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

Von den 15 verfügbaren Slots für diese Bibliothek sind nur zwei betroffen, null und der erste.
Jetzt hindert nichts mehr daran, eine Liste von Zertifikaten zu erhalten, die sich auf einem bestimmten Token befinden:

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

Jedes Listenelement enthält Informationen zu einem Zertifikat. Um Informationen aus dem Zertifikat zu erhalten, verwendet die Funktion :: pki :: pkcs11 :: listcerts die Funktion :: pki :: x509 :: parse_cert aus dem pki-Paket. Die Funktion :: pki :: pkcs11 :: listcerts ergänzt diese Liste jedoch mit Daten, die dem PKCS # 11-Protokoll inhärent sind, nämlich:

  • pkcs11_ label element (in der Terminologie des PKCS # 11-Attributs CKA_LABEL);
  • pkcs11_id-Element (in der Terminologie des PKCS # 11-Attributs CKA_ID);
  • pkcs11_handle-Element, das eine Angabe der geladenen PKCS # 11-Bibliothek enthält;
  • pkcs11_slotid-Element, das die Nummer des Steckplatzes mit dem Token enthält, auf dem sich dieses Zertifikat befindet;
  • Ein Tybelement, das den Wert pkcs11 für das Zertifikat enthält, das sich auf dem Token befindet.

Denken Sie daran, dass die verbleibenden Elemente hauptsächlich von der Funktion pki :: parse_cert bestimmt werden.
Im Folgenden wird beschrieben, wie Sie eine Liste von Labels (listCert) von Zertifikaten (CKA_LABEL, pkcs11_label) und ein Array von analysierten Bezeichnern (:: certs_p11) erhalten. Der Schlüssel für den Zugriff auf das Element des Zertifikatarrays ist die Zertifikatbezeichnung (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 } 

Und jetzt, da wir Zertifikate analysiert haben, zeigen wir leise eine Liste ihrer Labels in der Combobox an:



Wie man öffentliche GOST-Schlüssel analysiert, haben wir im vorherigen Artikel betrachtet.

Zwei Wörter zum Zertifikatsexport. Zertifikate werden sowohl in PEM-Codierung als auch in DER-Codierung (DER-Schaltflächen, PEM-Format) exportiert. Das pki-Paket verfügt über eine praktische Funktion pki :: _ encode_pem zum Konvertieren in das PEM-Format:

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

zum Beispiel:

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

Durch Auswahl des Etiketts für septische Zertifikate in der Combobox erhalten wir Zugriff auf den Hauptteil des Zertifikats:

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

Ein weiterer Mechanismus zum Parsen und Anzeigen des Zertifikats wurde hier bereits erläutert.

Zertifikatsvalidierung


Beim Parsen des Zertifikats speichern die Variablen :: notbefore und :: notafter das Datum, ab dem das Zertifikat für kryptografische Vorgänge (Signieren, Verschlüsseln usw.) verwendet werden kann, sowie das Ablaufdatum des Zertifikats. Das Verfahren zur Überprüfung der Gültigkeitsdauer eines Zertifikats hat folgende Form:

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

Die zurückgegebene Liste enthält zwei Elemente. Das erste Element kann entweder 0 (Null) oder 1 (Eins) enthalten. Der Wert "1" zeigt an, dass das Zertifikat gültig ist, und 0 gibt an, dass das Zertifikat ungültig ist. Der Grund, warum das Zertifikat nicht gültig ist, wird im zweiten Element angegeben. Dieses Element kann einen von drei Werten enthalten:

  • Zertifikat gültig (das erste Element der Liste ist 1):
  • Zertifikat ist noch nicht gültig (Zertifikat ist noch nicht abgelaufen)
  • Zertifikat ist abgelaufen.

Die Gültigkeit des Zertifikats wird nicht nur durch seine Gültigkeitsdauer bestimmt. Das Zertifikat kann vom Zertifizierungszentrum sowohl von sich aus als auch auf Antrag des Zertifikatsinhabers ausgesetzt oder gekündigt werden, beispielsweise bei Verlust des Mediums mit dem privaten Schlüssel. In diesem Fall wird das Zertifikat von der Zertifizierungsstelle in die Liste der widerrufenen COS / CRL-Zertifikate aufgenommen, die von der Zertifizierungsstelle verteilt werden. In der Regel ist der CRL-Verteilungspunkt im Zertifikat enthalten. Aus der Liste der widerrufenen Zertifikate wird die Gültigkeit des Zertifikats überprüft.

Validierung der Zertifikatsgültigkeit durch SOS / CRL


Der erste Schritt besteht darin, das SOS abzurufen, es dann zu analysieren und das Zertifikat darauf zu überprüfen.
Die Liste der COC / CRL-Ausstellungspunkte befindet sich in der Zertifikatserweiterung mit 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" } 

Das Laden der Datei mit SOS / CRL ist wie folgt:

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

Tatsächlich wird die Readca-Prozedur verwendet, um die COC / CRL zu laden:

 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 } 

Die dir-Variable speichert den Pfad zu dem Verzeichnis, in dem die COS / CRL gespeichert wird, und die URL-Variable enthält die zuvor empfangene Liste der CRL-Verteilungspunkte.

Beim Empfang von SOS / CRL musste ich mich plötzlich der Tatsache stellen, dass diese Liste für einige Zertifikate über das https (tls) -Protokoll im anonymen Modus empfangen werden musste. Ehrlich gesagt ist dies überraschend: Die CRL-Liste ist ein öffentliches Dokument und ihre Integrität wird durch eine elektronische Signatur geschützt. Meiner Meinung nach habe ich über anonymes https Zugriff darauf. Aber es gibt nichts zu tun, Sie müssen das tls-Paket verbinden - Paket erfordert tls.

Wenn SOS / CRL nicht heruntergeladen werden konnte, kann das Zertifikat nicht überprüft werden, wenn der Zugriffspunkt mit dem OCSP-Dienst nicht im Zertifikat angegeben ist. Dies wird jedoch in einem der folgenden Artikel erörtert.

Es gibt also ein Zertifikat zur Überprüfung, es gibt eine Liste von SOS / CRL, es bleibt das Zertifikat zu überprüfen. Leider enthält das pki-Paket keine entsprechenden Funktionen. Daher musste ich eine Prozedur schreiben, um die Gültigkeit des Zertifikats (dessen Nicht-Widerruf) aus der Liste der widerrufenen Zertifikate zu überprüfen

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

Die Parameter für diese Funktion sind die Zertifikatsperrliste (crl), die Seriennummer des zu überprüfenden Zertifikats (sernum) und dessen Herausgeber (Aussteller).

Die Zertifikatsperrliste (crl) wird wie folgt geladen:

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

Die Seriennummer des verifizierten Zertifikats (sernum) und seines Herausgebers (Ausstellers) wird aus dem analysierten Zertifikat entnommen und in den Variablen :: sncert und :: issuercert gespeichert.

Alle Prozeduren finden Sie im Quellcode. Den Quellcode des Dienstprogramms und seiner Distributionen für Linux, OS X (macOS) und MS Windows finden Sie hier.


Das Dienstprogramm behält auch die Möglichkeit, in einer Datei gespeicherte Zertifikate anzuzeigen und zu überprüfen:



Übrigens können auch die angezeigten Zertifikate aus Dateien sowie die auf dem Token gespeicherten exportiert werden. Dies erleichtert das Konvertieren von Zertifikatdateien vom DER-Format in PEM und umgekehrt.

Jetzt haben wir einen einzigen Viewer für Zertifikate, die sowohl in Dateien als auch auf PKCS # 11-Token / Smartcards gespeichert sind.

Ja, ich habe den Punkt verpasst: Um die Gültigkeit des Zertifikats zu überprüfen, klicken Sie auf die Schaltfläche „Additionaly“ und wählen Sie den Menüpunkt „Validaty by CRL“ oder drücken Sie die rechte Maustaste, wenn sich der Cursor auf den Hauptinformationen befindet Feld und wählen Sie auch den Menüpunkt "Validation by CRL":



Dieser Screenshot zeigt das Durchsuchen und Überprüfen von Zertifikaten in einem Cloud-Token.

Abschließend stellen wir Folgendes fest. In seinen Kommentaren zu dem Artikel bemerkte der Pas- Benutzer sehr korrekt über PKCS # 11-Token, dass sie „selbst alles zählen können“. Ja, Token sind eigentlich kryptografische Computer. In den folgenden Artikeln werden wir nicht nur darüber sprechen, wie Zertifikate mithilfe des OCSP-Protokolls überprüft werden, sondern auch darüber, wie kryptografische Mechanismen (wir sprechen natürlich von GOST-Kryptografie) von Token / Smarts zur Berechnung des Hashs (GOST R 34-10-) verwendet werden. 94/2012), die Bildung und Überprüfung von Unterschriften usw.

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


All Articles