
Dans les commentaires de l'article «Un utilitaire multiplateforme anglophone pour visualiser les certificats x509 qualifiés russes», un utilisateur de
Pas souhaitait non seulement «l'analyse syntaxique des certificats», mais également recevoir des «chaînes de certificats racine et effectuer une validation PKI, au moins pour les certificats avec des jetons avec une clé extractible ". L'obtention d'une chaîne de certificats a été décrite dans l'un des articles précédents. Certes, il s'agissait de certificats stockés dans des fichiers, mais nous avons promis d'ajouter des mécanismes pour travailler avec des certificats stockés sur des jetons PKCS # 11. Et c'est ce qui s'est finalement produit.

L'utilitaire d'analyse et de visualisation est écrit en Tcl / Tk, et pour y ajouter des jetons / cartes à puce PKCS # 11, afficher les certificats ainsi que vérifier la validité des certificats, il était nécessaire de résoudre plusieurs problèmes:
- déterminer le mécanisme d'obtention des certificats à partir du jeton / de la carte à puce;
- vérifier le certificat par rapport à la liste des certificats CRL révoqués;
- vérifier le certificat de validité par le mécanisme OCSP.
Accès au jeton PKCS # 11
Pour accéder au jeton et aux certificats qui y sont stockés, nous utiliserons le package
TclPKCS11 . Le package est distribué à la fois en binaires et en codes sources. Les codes sources seront utiles plus tard lorsque nous ajouterons la prise en charge des jetons avec la cryptographie russe au package. Il existe deux façons de télécharger le package TclPKCS11 ou d'utiliser la commande tcl du formulaire:
load < tclpkcs11> Tclpkcs11
Ou téléchargez-le simplement en tant que package pki :: pkcs11, après avoir placé la bibliothèque tclpkcs11 et le fichier pkgIndex.tcl dans un répertoire qui vous convient (dans notre cas, c'est le sous-répertoire pkcs11 du répertoire actuel) et en l'ajoutant au chemin auto_path:
#lappend auto_path [file dirname [info scrypt]] lappend auto_path pkcs11 package require pki package require pki::pkcs11
Puisque nous sommes intéressés par les jetons principalement avec le support de la cryptographie russe, à partir du package TclPKCS11, nous utiliserons les
fonctions suivantes:
::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
Réservez immédiatement que les fonctions de connexion et de déconnexion ne seront pas prises en compte ici. Cela est dû au fait que dans cet article, nous ne traiterons que des certificats, et ce sont des objets à jeton public. Pour accéder aux objets publics, il n'est pas nécessaire de se connecter via le code PIN sur le token.
La première fonction :: pki :: pkcs11 :: loadmodule consiste à charger la bibliothèque PKCS # 11, qui prend en charge le jeton / la carte à puce sur laquelle se trouvent les certificats. Une bibliothèque peut être obtenue soit en achetant un jeton, soit téléchargée sur Internet, soit préinstallée sur un ordinateur. Dans tous les cas, vous devez savoir quelle bibliothèque prend en charge votre jeton. La fonction loadmodule renvoie un handle vers la bibliothèque chargée:
set filelib "/usr/local/lib64/librtpkcs11ecp_2.0.so" set handle [::pki::pkcs11::loadmodule $filelib]
En conséquence, il existe une fonction pour décharger une bibliothèque chargée:
::pki::pkcs11::unloadmodule $handle
Une fois que la bibliothèque a été chargée et que nous avons sa poignée, vous pouvez obtenir une liste des emplacements pris en charge par cette bibliothèque:
::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}}
Dans cet exemple, la liste contient 15 (quinze de 0 à 14) éléments. C'est le nombre d'emplacements que la famille de jetons RuToken peut prendre en charge. À son tour, chaque élément de la liste elle-même est une liste de trois éléments:
{{ } { } { }}
Le premier élément de la liste est le numéro d'emplacement. Le deuxième élément de la liste est l'étiquette située dans l'emplacement de jeton (32 octets). Si l'emplacement est vide, le deuxième élément contient 32 espaces. Et le dernier, troisième élément de la liste contient des drapeaux. Nous ne considérerons pas l'ensemble des drapeaux. Ce qui nous intéresse dans ces drapeaux, c'est la présence du drapeau TOKEN_PRESENT. C'est ce drapeau qui indique que le jeton est dans la fente, et les certificats qui nous intéressent peuvent être sur le jeton. Les drapeaux sont très utiles, ils décrivent l'état du jeton, l'état des codes PIN, etc. En fonction de la valeur des indicateurs, les jetons PKCS # 11 sont gérés:

Maintenant, rien ne vous empêche d'écrire la procédure slots_with_token, qui renverra une liste d'emplacements avec les étiquettes des jetons:
#!/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 vous exécutez ce script, après l'avoir enregistré dans le fichier slots_with_token.tcl, alors nous obtenons:
$ ./slots_with_token.tcl listtok(0) = ruToken ECP listtok(1) = RuTokenECP20 Number slot: 0 Label token: RuTokenECP20 Number slot: 1 Label token: ruToken ECP $
Sur les 15 emplacements disponibles pour cette bibliothèque, seuls deux sont impliqués, zéro et le premier.
Maintenant, rien n'empêche d'obtenir une liste de certificats situés sur un jeton particulier:
set listcerts [::pki::pkcs11::listcerts $handle $slotid]
Chaque élément de la liste contient des informations sur un certificat. Pour obtenir des informations du certificat, la fonction :: pki :: pkcs11 :: listcerts utilise la fonction :: pki :: x509 :: parse_cert du package pki. Mais la fonction :: pki :: pkcs11 :: listcerts complète cette liste avec des données inhérentes au protocole PKCS # 11, à savoir:
- élément d'étiquette pkcs11_ (dans la terminologie de l'attribut PKCS # 11 CKA_LABEL);
- élément pkcs11_id (dans la terminologie de l'attribut PKCS # 11 CKA_ID);
- élément pkcs11_handle contenant une indication de la bibliothèque PKCS # 11 chargée;
- élément pkcs11_slotid contenant le numéro de l'emplacement avec le jeton sur lequel se trouve ce certificat;
- un élément type qui contient la valeur pkcs11 pour le certificat qui se trouve sur le jeton.
Rappelons que les éléments restants sont principalement déterminés par la fonction pki :: parse_cert.
Voici la procédure pour obtenir une liste d'étiquettes (listCert) de certificats (CKA_LABEL, pkcs11_label) et un tableau d'identificateurs analysés (:: certs_p11). La clé pour accéder à l'élément du tableau de certificats est l'étiquette de certificat (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 }
Et maintenant que nous avons analysé les certificats, nous affichons tranquillement une liste de leurs étiquettes dans la zone de liste déroulante:

Comment analyser les clés publiques GOST que nous avons examinées dans l'article précédent.
Deux mots sur l'exportation de certificats. Les certificats sont exportés à la fois en encodage PEM et en encodage DER (boutons DER, format PEM). Le paquet pki a une fonction pratique pki :: _ encode_pem pour la conversion au format PEM:
set bufpem [::pki::_encode_pem <der-buffer> <Headline> <Lastline>]
par exemple:
set certpem [::pki::encode_pen $cert_der "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"]
En sélectionnant l'étiquette du certificat septique dans la combobox, nous avons accès au corps du certificat:
# 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 autre mécanisme pour analyser le certificat et l'afficher a déjà été discuté
ici .
Validation du certificat
Lors de l'analyse du certificat, les variables :: notbefore et :: notafter stockent la date à partir de laquelle le certificat peut être utilisé dans les opérations cryptographiques (signer, chiffrer, etc.) et la date d'expiration du certificat. La procédure de vérification de la période de validité d'un certificat est la suivante:
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 liste renvoyée contient deux éléments. Le premier élément peut contenir 0 (zéro) ou 1 (un). Une valeur de «1» indique que le certificat est valide et 0 indique que le certificat n'est pas valide. La raison pour laquelle le certificat n'est pas valide est indiquée dans le deuxième élément. Cet élément peut contenir l'une des trois valeurs:
- certificat valide (le premier élément de la liste est 1):
- le certificat n'est pas encore valide (le certificat n'a pas encore expiré)
- le certificat a expiré.
La validité du certificat est déterminée non seulement par sa période de validité. Le certificat peut être suspendu ou résilié par le centre de certification, à la fois de sa propre initiative et à la demande du titulaire du certificat, par exemple, en cas de perte du support avec la clé privée. Dans ce cas, le certificat est inclus par l'autorité de certification dans la liste des certificats COS / CRL révoqués qui sont distribués par l'AC. En règle générale, le point de distribution CRL est inclus dans le certificat. C'est à partir de la liste des certificats révoqués que la validité du certificat est vérifiée.
Validation de la validité du certificat par SOS / CRL
La première étape consiste à obtenir le SOS, puis à l'analyser et à vérifier le certificat pour cela.
La liste des points d'émission COC / CRL se trouve dans l'extension de certificat avec l'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 fait, le chargement du fichier avec SOS / CRL est le suivant:
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 fait, la procédure readca est utilisée pour charger le 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 stocke le chemin d'accès au répertoire dans lequel le COS / CRL sera enregistré, et la variable url contient la liste des points de distribution CRL précédemment reçue.
Lors de la réception de SOS / CRL, j'ai soudainement dû faire face au fait que pour certains certificats, cette liste devait être reçue via le protocole https (tls) en mode anonyme. Honnêtement, cela est surprenant: la liste CRL est un document public et son intégrité est protégée par une signature électronique et j'y ai accès via https anonyme à mon avis. Mais il n'y a rien à faire, vous devez connecter le paquet tls - le paquet nécessite tls.
Si SOS / CRL n'a pas pu être téléchargé, le certificat ne peut pas être vérifié si le point d'accès avec le service OCSP n'est pas spécifié dans le certificat. Mais cela sera discuté dans l'un des articles suivants.
Donc, il y a un certificat de vérification, il y a une liste de SOS / CRL, il reste à vérifier le certificat. Malheureusement, il n'y a pas de fonctions correspondantes dans le package pki. J'ai donc dû écrire une procédure pour vérifier la validité du certificat (sa non-révocation) à partir de la liste des certificats révoqués
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] }
Les paramètres de cette fonction sont la liste de révocation de certificats (crl), le numéro de série du certificat en cours de vérification (sernum) et son éditeur (émetteur).
La liste de révocation de certificats (crl) est chargée comme suit:
set f [open $filecrl r] chan configure $f -translation binary set crl [read $f] close $f
Le numéro de série du certificat vérifié (sernum) et de son éditeur (émetteur) sont extraits du certificat analysé et stockés dans les variables :: sncert et :: issuercert.
Toutes les procédures se trouvent dans le code source. Le code source de l'utilitaire et ses distributions pour Linux, OS X (macOS) et MS Windows peuvent être trouvés ici.
L'utilitaire conserve également la possibilité d'afficher et de vérifier les certificats stockés dans un fichier:

Soit dit en passant, les certificats affichés à partir de fichiers peuvent également être exportés, ainsi que stockés sur un jeton. Cela facilite la conversion des fichiers de certificat du format DER en PEM et vice versa.
Nous avons maintenant une visionneuse unique pour les certificats stockés à la fois dans des fichiers et sur des jetons / cartes à puce PKCS # 11.
Oui, j'ai raté le point: pour vérifier la validité du certificat, cliquez sur le bouton «Additionnel» et sélectionnez l'élément de menu «Validaty by CRL» ou appuyez sur le bouton droit de la souris et lorsque le curseur est sur les informations principales et sélectionnez également l'élément de menu «Validaty by CRL»:

Cette capture d'écran montre la navigation et la validation des certificats dans un jeton cloud.
En conclusion, nous notons ce qui suit. Dans ses commentaires sur l'
article , l'utilisateur de
Pas a très correctement noté à propos des jetons PKCS # 11 qu'ils «peuvent tout compter eux-mêmes». Oui, les jetons sont en fait des ordinateurs cryptographiques. Et dans les articles suivants, nous parlerons non seulement de la façon dont les certificats sont vérifiés à l'aide du protocole OCSP, mais également de la façon d'utiliser les mécanismes cryptographiques (nous parlons, bien sûr, la cryptographie GOST) de jetons / smarts pour calculer le hachage (GOST R 34-10- 94/2012), la formation et la vérification des signatures, etc.