Infraestrutura de chave pública Cadeia de certificados raiz X509 v.3

A hora "H" está inexoravelmente se aproximando: "o uso do esquema de assinaturas GOST R 34.10-2001 para gerar uma assinatura após 31 de dezembro de 2018 não é permitido!".
No entanto, algo deu errado, alguém não estava pronto e o uso do GOST R 34.10-2001 foi estendido para 2019. Mas, de repente, todos correram para transferir a CA para o GOST R 34.10-2012 e para transferir cidadãos comuns para novos certificados. As pessoas têm vários certificados em suas mãos. Ao verificar certificados ou assinaturas eletrônicas, começaram a surgir perguntas e onde obter certificados raiz para instalar no armazenamento de certificados raiz confiáveis.

Isso se aplica aos armazenamentos de certificados no Windows e armazenamentos de certificados no Firefox e Google Chrome, GnuPG, LibreOffice, clientes de email e até OpenSSL. Obviamente, era necessário cuidar disso ao receber o certificado na CA e gravar a cadeia de certificados na unidade flash USB. Por outro lado, temos uma sociedade digital e, a qualquer momento, devemos conseguir essa cadeia da rede. O Simpleadmin mostrou como fazer isso nas páginas da Habr. No entanto, para um cidadão comum ainda é difícil (especialmente se você levar em conta que a grande maioria deles está no Windows): você precisa ter algum tipo de openssl, um utilitário de busca que eu não tinha no meu computador e nem todo mundo sabe que você pode usar o wget. E quantas ações precisam ser executadas. É claro que existe uma saída, escreva um script, mas não apenas um script sobre o openssl e seu tipo, mas empacotado em um módulo executável independente para várias plataformas.

Não havia dúvida sobre o que escrever - no Tcl e no Python . E começamos com Tcl e é por isso :
* porra de wiki, onde existem até brinquedos (você pode ver coisas interessantes lá :)
* folhas de dicas
* compilações normais do tclkit (1,5 - 2 MB como taxa para plataformas reais reais)
* e minha montagem eTcl favorita da Evolane (cuidadosamente preservada do local do falecido :(
manter uma classificação Tcl / Tk alta na minha lista de kits de ferramentas pessoais
e, sim, wiki.tcl.tk/16867 (um pequeno servidor web de cgi para Tcl, usado periodicamente com constância invejável no tclkit)
e também é lindo e lindo :)
Para isso, acrescentaria a disponibilidade do utilitário freewrap , que nos ajudará a criar utilitários autônomos para Linux e MS Windows. Como resultado, teremos o utilitário chainfromcert:

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

Como parâmetros, o utilitário define um arquivo com um certificado de usuário (tanto no formato PEM quanto no formato DER) e o diretório no qual os certificados de CA incluídos na cadeia serão salvos:

 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$ 

Agora considere como o utilitário funciona.
As informações sobre a autoridade de certificação que emitiu o certificado para o usuário são armazenadas na extensão com oid 1.3.6.1.5.5.7.1.1. Essa extensão pode armazenar o local do certificado da CA (oid 1.3.6.1.5.5.7.48.2) e as informações sobre o serviço de CA do OCSP (oid 1.3.6.1.5.5.7.48.1):



E informações, por exemplo, sobre o período de uso da chave de assinatura eletrônica são armazenadas na extensão com oid 2.5.29.16.

Para analisar o certificado e acessar as extensões de certificado, usaremos o pacote pki:

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

Também precisaremos do pacote base64:

 package require base64 

O pacote pki, assim como o pacote asn e o pacote base64 que ele carrega, nos ajudarão a converter certificados de uma codificação PEM para DER, analisar estruturas ASN e acessar informações sobre a localização dos certificados da CA.

O utilitário começa com a verificação dos parâmetros e o download do arquivo com o certificado:

 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 } 

Tudo está claro aqui e observamos apenas uma coisa - o arquivo com o certificado é considerado como um arquivo binário:

 chan configure $fd -translation binary 

Isso ocorre porque o certificado pode ser armazenado no formato DER (código binário) e no formato PEM (codificação base64).

Depois que o arquivo é carregado, o procedimento chainfromcert é chamado:

 set depth [chainfromcert $data $dir] 
que efetivamente baixa os certificados raiz:

 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 } 

Não há nada a acrescentar aos comentários, mas ainda não consideramos o procedimento 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 } 

Este procedimento é baseado no uso do pacote http:

 package require http 

Para ler o certificado, usamos a seguinte função:

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

O objetivo das demais funções utilizadas é claro nos comentários. Apenas forneceremos uma descriptografia de códigos de retorno para a função http :: ncodel:
200 Pedido concluído com sucesso
206 Pedido concluído com sucesso, mas apenas parte do arquivo foi baixada
301 Arquivo movido para outro local.
302 Arquivo movido temporariamente para outro local.
401 autenticação de servidor necessária
403 O acesso a este recurso foi negado
404 O recurso especificado não pode ser encontrado.
500 Erro interno
Um procedimento ainda precisa ser considerado, a saber 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 } 

O procedimento é muito simples. Se este for um arquivo PEM com um certificado ("----- BEGIN CERTIFICATE -----"), o corpo desse arquivo será selecionado e convertido em um código de binários:

 set asnblock [base64::decode $block] 

Se este não é um arquivo PEM, essa “semelhança” com a codificação asn é verificada (o bit zero deve ser 0x30).

Isso é tudo, resta adicionar as linhas finais:

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

Agora coletamos tudo em um arquivo com o nome

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 


Você pode verificar a operação desse arquivo usando o interpretador 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 $ 

Como resultado, obtivemos uma cadeia de dois certificados no diretório / tmp.

Mas queríamos obter módulos executáveis ​​para as plataformas Linux e Windows, para que os usuários não pensassem em nenhum intérprete.

Para esse fim, usaremos o utilitário freewrapTCLSH . Usando esse utilitário, criaremos módulos executáveis ​​de nosso utilitário para plataformas Linux e Windows, de 32 e 64 bits. Utilitários podem ser criados para todas as plataformas em qualquer plataforma. Desculpe pela tautologia. Vou construir no linux_x86_64 (Mageia).

Para construir, você precisará de:
1. O utilitário freewrapTCLSH para a plataforma linux_x86_64;
2. O arquivo freewrapTCLSH com este utilitário para cada plataforma:
- freewrapTCLSH_linux32
- freewrapTCLSH_linux64
- freewrapTCLSH_win32
- freewrapTCLSH_win64
3. O arquivo de origem do nosso utilitário: chainfromcert.tcl
Portanto, o executável chainfromcerty_linuxx86 montado para a plataforma Linux x86:

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

A montagem do utilitário para a plataforma Windows de 64 bits é assim:

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

Etc. Os utilitários estão prontos para uso. Eles absorveram tudo o necessário para o seu trabalho.
O código é escrito da mesma maneira em Python.

Nos próximos dias, estou pensando em complementar o pacote fsb795 (escrito em Python) com a função de obter a cadeia de certificados raiz.

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


All Articles