
Nos comentários ao artigo “Um utilitário de plataforma cruzada em inglês para exibição de certificados x509 qualificados russos”, havia um desejo de um usuário do
Pas de ter não apenas “análise de certificado”, mas também receber “cadeias de certificados raiz e realizar validação de PKI, pelo menos para certificados com tokens com uma chave extraível " A obtenção de uma cadeia de certificados foi descrita em um dos artigos anteriores. É verdade que se tratava de certificados armazenados em arquivos, mas prometemos adicionar mecanismos para trabalhar com certificados armazenados nos tokens PKCS # 11. E foi o que aconteceu no final.

O utilitário de análise e visualização é escrito em Tcl / Tk, e para adicionar tokens / smartcards PKCS # 11 a ele, visualizando certificados e verificando a validade dos certificados, foi necessário resolver vários problemas:
- determinar o mecanismo para obter certificados do token / cartão inteligente;
- verifique o certificado em relação à lista de certificados revogados CRL;
- verifique o certificado de validade pelo mecanismo OCSP.
Acesso ao token PKCS # 11
Para acessar o token e os certificados armazenados nele, usaremos o pacote
TclPKCS11 . O pacote é distribuído em binários e em códigos-fonte. Os códigos-fonte serão úteis mais tarde, quando adicionarmos o suporte ao token com criptografia russa no pacote. Há duas maneiras de baixar o pacote TclPKCS11 ou use o comando tcl do formulário:
load < tclpkcs11> Tclpkcs11
Ou faça o download simplesmente como o pacote pki :: pkcs11, depois de colocar a biblioteca tclpkcs11 e o arquivo pkgIndex.tcl em um diretório conveniente para você (no nosso caso, este é o subdiretório pkcs11 do diretório atual) e adicioná-lo ao caminho auto_path:
#lappend auto_path [file dirname [info scrypt]] lappend auto_path pkcs11 package require pki package require pki::pkcs11
Como estamos interessados em tokens principalmente com suporte para criptografia russa, a partir do pacote TclPKCS11, usaremos as seguintes
funções :
::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
Faça imediatamente uma reserva de que as funções de login e logout não serão consideradas aqui. Isso ocorre porque, neste artigo, trataremos apenas de certificados, e eles são objetos públicos de token. Para acessar objetos públicos, não é necessário fazer login através do código PIN no token.
A primeira função :: pki :: pkcs11 :: loadmodule é carregar a biblioteca PKCS # 11, que suporta o token / cartão inteligente no qual os certificados estão localizados. Uma biblioteca pode ser obtida comprando um token, ou baixada da Internet, ou foi pré-instalada em um computador. De qualquer forma, você precisa saber qual biblioteca suporta seu token. A função loadmodule retorna um identificador para a biblioteca carregada:
set filelib "/usr/local/lib64/librtpkcs11ecp_2.0.so" set handle [::pki::pkcs11::loadmodule $filelib]
Por conseguinte, existe uma função para descarregar uma biblioteca carregada:
::pki::pkcs11::unloadmodule $handle
Depois que a biblioteca for carregada e tivermos seu identificador, você poderá obter uma lista de slots suportados por esta biblioteca:
::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}}
Neste exemplo, a lista contém 15 (quinze de 0 a 14) elementos. É esse o número de slots que a família de tokens RuToken pode suportar. Por sua vez, cada elemento da lista em si é uma lista de três elementos:
{{ } { } { }}
O primeiro elemento da lista é o número do slot. O segundo elemento da lista é o rótulo localizado no slot de token (32 bytes). Se o slot estiver vazio, o segundo elemento conterá 32 espaços. E o último, terceiro elemento da lista contém sinalizadores. Não consideraremos todo o conjunto de sinalizadores. O que nos interessa nesses sinalizadores é a presença do sinalizador TOKEN_PRESENT. É esse sinalizador que indica que o token está no slot e os certificados de interesse para nós podem estar no token. Os sinalizadores são uma coisa muito útil, descrevem o estado do token, o status dos códigos PIN, etc. Com base no valor dos sinalizadores, os tokens PKCS # 11 são gerenciados:

Agora, nada impede que você escreva o procedimento slots_with_token, que retornará uma lista de slots com os rótulos dos tokens:
#!/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" }
Se você executar esse script, depois de salvá-lo no arquivo slots_with_token.tcl, como resultado, obteremos:
$ ./slots_with_token.tcl listtok(0) = ruToken ECP listtok(1) = RuTokenECP20 Number slot: 0 Label token: RuTokenECP20 Number slot: 1 Label token: ruToken ECP $
Dos 15 slots disponíveis para esta biblioteca, apenas dois estão envolvidos, zero e o primeiro.
Agora nada impede a obtenção de uma lista de certificados localizados em um token específico:
set listcerts [::pki::pkcs11::listcerts $handle $slotid]
Cada item da lista contém informações sobre um certificado. Para obter informações do certificado, a função :: pki :: pkcs11 :: listcerts usa a função :: pki :: x509 :: parse_cert do pacote pki. Mas a função :: pki :: pkcs11 :: listcerts complementa essa lista com dados inerentes ao protocolo PKCS # 11, a saber:
- elemento de etiqueta pkcs11_ (na terminologia do atributo CKA_LABEL do PKCS # 11);
- elemento pkcs11_id (na terminologia do atributo CKA_ID do PKCS # 11);
- elemento pkcs11_handle que contém uma indicação da biblioteca PKCS # 11 carregada;
- elemento pkcs11_slotid que contém o número do slot com o token no qual este certificado está localizado;
- um elemento de tipo que contém o valor pkcs11 para o certificado que está no token.
Lembre-se de que os elementos restantes são determinados principalmente pela função pki :: parse_cert.
Abaixo está o procedimento para obter uma lista de rótulos (listCert) de certificados (CKA_LABEL, pkcs11_label) e uma matriz de identificadores analisados (:: certs_p11). A chave para acessar o elemento da matriz de certificados é o rótulo do certificado (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 }
E agora que analisamos os certificados, exibimos silenciosamente uma lista de seus rótulos na caixa de combinação:

Como analisar chaves públicas GOST que consideramos no artigo anterior.
Duas palavras sobre exportação de certificados. Os certificados são exportados tanto na codificação PEM quanto na codificação DER (botões DER, formato PEM). O pacote pki tem uma função conveniente pki :: _ encode_pem para converter para o formato PEM:
set bufpem [::pki::_encode_pem <der-buffer> <Headline> <Lastline>]
por exemplo:
set certpem [::pki::encode_pen $cert_der "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"]
Ao selecionar o rótulo do certificado séptico na caixa de combinação, obtemos acesso ao corpo do certificado:
# 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"
Um outro mecanismo para analisar o certificado e exibi-lo foi discutido anteriormente
aqui .
Validação de certificado
Ao analisar o certificado, as variáveis :: notbefore e :: notafter armazenam a data a partir da qual o certificado pode ser usado em operações criptográficas (assinar, criptografar etc.) e a data de validade do certificado. O procedimento para verificar a validade de um certificado tem a forma:
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] }
A lista retornada contém dois itens. O primeiro elemento pode conter 0 (zero) ou 1 (um). Um valor de "1" indica que o certificado é válido e 0 indica que o certificado não é válido. O motivo pelo qual o certificado não é válido é divulgado no segundo elemento. Este elemento pode conter um dos três valores:
- certificado válido (o primeiro elemento da lista é 1):
- certificado ainda não é válido (o certificado ainda não expirou)
- certificado expirou.
A validade do certificado é determinada não apenas pelo seu período de validade. O certificado pode ser suspenso ou rescindido pelo centro de certificação, por sua própria iniciativa e a pedido do titular do certificado, por exemplo, em caso de perda da mídia com a chave privada. Nesse caso, o certificado é incluído pela autoridade de certificação na lista de certificados COS / CRL revogados que são distribuídos pela CA. Normalmente, o ponto de distribuição da CRL está incluído no certificado. É da lista de certificados revogados que a validade do certificado é verificada.
Validação da validade do certificado por SOS / CRL
O primeiro passo é obter o SOS, analisá-lo e verificar o certificado.
A lista de pontos de emissão COC / CRL está na extensão de certificado com 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" }
Na verdade, o carregamento do arquivo com SOS / CRL é o seguinte:
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" }
Na verdade, o procedimento readca é usado para carregar o 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 }
A variável dir armazena o caminho para o diretório no qual a COS / CRL será salva e a variável url contém a lista recebida anteriormente de pontos de distribuição da CRL.
Ao receber o SOS / CRL, repentinamente tive que enfrentar o fato de que para alguns certificados essa lista tinha que ser recebida pelo protocolo https (tls) no modo anônimo. Honestamente, isso é surpreendente: a lista da CRL é um documento público e sua integridade é protegida por uma assinatura eletrônica e, na minha opinião, tenho acesso a ele por https anônimo. Mas não há nada a fazer, você precisa conectar o pacote tls - o pacote exige tls.
Se não foi possível fazer o download do SOS / CRL, o certificado não poderá ser verificado se o ponto de acesso com o serviço OCSP não estiver especificado no certificado. Mas isso será discutido em um dos seguintes artigos.
Portanto, existe um certificado para verificação, existe uma lista de SOS / CRL, resta verificar o certificado. Infelizmente, não há funções correspondentes no pacote pki. Portanto, tive que escrever um procedimento para verificar a validade do certificado (sua não revogação) na lista de certificados revogados
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] }
Os parâmetros para esta função são a lista de revogação de certificado (crl), o número de série do certificado que está sendo verificado (sernum) e seu editor (emissor).
A lista de revogação de certificado (crl) é carregada da seguinte maneira:
set f [open $filecrl r] chan configure $f -translation binary set crl [read $f] close $f
O número de série do certificado verificado (sernum) e seu editor (emissor) são obtidos do certificado analisado e armazenados nas variáveis :: sncert e :: issuercert.
Todos os procedimentos podem ser encontrados no código fonte. O código fonte do utilitário e suas distribuições para Linux, OS X (macOS) e MS Windows podem ser encontrados aqui.
O utilitário também mantém a capacidade de visualizar e verificar certificados armazenados em um arquivo:

A propósito, os certificados visualizados dos arquivos também podem ser exportados, bem como os armazenados no token. Isso facilita a conversão de arquivos de certificado do formato DER para PEM e vice-versa.
Agora, temos um visualizador único para certificados armazenados em arquivos e em tokens / smartcards PKCS # 11.
Sim, eu perdi o ponto: para verificar a validade do certificado, clique no botão “Adicionalmente” e selecione o item de menu “Validação por CRL” ou pressione o botão direito do mouse e quando o cursor estiver nas informações principais campo e também selecione o item de menu “Validaty by CRL”:

Esta captura de tela mostra a navegação e a validação de certificados em um token na nuvem.
Em conclusão, observamos o seguinte. Em seus comentários ao
artigo , o usuário do
Pas observou muito corretamente sobre os tokens do PKCS # 11 que "eles mesmos podem contar tudo". Sim, os tokens são realmente computadores criptográficos. Nos artigos a seguir, falaremos não apenas sobre como os certificados são verificados usando o protocolo OCSP, mas também sobre como usar mecanismos criptográficos (estamos falando, é claro, da criptografia GOST) de tokens / smarts para calcular o hash (GOST R 34-10- 94/2012), a formação e verificação de assinaturas, etc.