📜 ⬆️ ⬇️

Public key infrastructure. X509 v.3 Root Certificate Chain

The “H” time is inexorably approaching: “the use of the signature scheme GOST R 34.10-2001 for the formation of a signature after December 31, 2018 is not allowed!”.
However, then something went wrong, someone was not ready, and the use of GOST R 34.10-2001 was extended to 2019. But suddenly everyone rushed to transfer the TC to GOST R 34.10-2012, and to transfer ordinary citizens to new certificates. There were several certificates in the hands of people. When checking certificates or an electronic signature, questions began to arise, and where to get the root certificates to install in the trusted root certificate stores.

This applies to both the certificate store in Windows and the certificate store in Firefox and Google Chrome, GnuPG , LibreOffice , mail clients and even OpenSSL . Of course, it was necessary to attend to this when obtaining a certificate in the CA and record the chain of certificates on a USB flash drive. On the other hand, we also have a digital society and at any time should be able to get this chain from the network. How to do this on the pages of Habr showed simpleadmin . However, for an ordinary citizen it is still difficult (especially if we bear in mind that the absolute majority of them are on Windows): you need to have “some” openssl, the fetch utility, which I didn’t have on the computer, and not everyone knows that you can use wget instead. And how many actions you need to perform. There is a way out, of course, write a script, but not just a script on top of openssl and others like it, but packaged in a self-sufficient executable module for various platforms.

On what to write there were no doubts - on Tcl and Python . And we start with Tcl and this is why :
* Awesome wiki , where there are even toys (there you can peek at interesting :)
* cheat sheets
* normal tclkit builds (1.5 - 2 MB as payment for real cross-platform)
* and my favorite eTcl build from Evolane (carefully saved from the deceased site :(
keep a high Tcl / Tk rating on my personal tool list
and yes, wiki.tcl.tk/16867 (a small web server from cgi to Tcl, periodically used with an enviable constancy under tclkit)
and more - it's just beautiful and beautiful :)
To this, I would add the presence of the freewrap utility, which will help us build standalone utilities for Linux and MS Windows. As a result, we will have the chainfromcert utility:

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

As parameters, the utility is given a file with a user certificate (both in PEM and DER format) and a directory in which CA certificates that are included in the chain will be saved:

 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$ 

Now consider how the utility works.
Information about the certification center that issued the certificate to the user is stored in the extension with oid-ohm 1.3.6.1.5.5.7.1.1. This extension can store both the CA certificate location (oid 1.3.6.1.5.5.7.48.2) and the CA OCSP service information (oid 1.3.6.1.5.5.7.48.1):



And information, for example, about the period of use of a key of an electronic signature is stored in the extension with oid-ohm 2.5.29.16.

To parse the certificate and access the certificate extensions, we will use the pki package:

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

We will also need the base64 package:

 package require base64 

The pki package, as well as the asn package that it loads, and the base64 package will help us convert certificates from PEM to DER encoding, parse ASN structures, and actually access information about the location of CA certificates.

The utility starts with checking the parameters and downloading the file with the certificate:

 proc usage {use } { puts "Copyright(C) LISSI-Soft Ltd (http://soft.lissi.ru) 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 } 

Here everything is clear and we only note one thing - the file with the certificate is treated as a binary file:

 chan configure $fd -translation binary 

This is due to the fact that the certificate can be stored both in DER format (binary code) and in PEM format (base64 - encoding).

After the file is loaded, the chainfromcert procedure is called:

 set depth [chainfromcert $data $dir] 
which actually loads root certificates:

 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 } 

There is nothing to add to the comments, but the readca procedure remains unresolved:
 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 } 

This procedure is based on using the http package:

 package require http 

To read the certificate, we use the following function:

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

The purpose of the remaining functions used is clear from the comments. We give only the decoding of return codes for the function http :: ncodel:
200 Request successfully completed
206 The request was successful, but only a part of the file could be downloaded.
301 File moved to another location.
302 File is temporarily moved to another location.
401 Server authentication required
403 Access to this resource is denied.
404 The specified resource could not be found.
500 Internal error
There is one procedure not considered, namely 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 } 

The procedure is very simple. If this is a PEM file with a certificate ("----- BEGIN CERTIFICATE -----"), then the body of this file is selected and converted to bin code:

 set asnblock [base64::decode $block] 

If it is not a PEM file, then this “similarity” to the asn encoding is checked (the zero bit must be 0x30).

That's all, it remains to add the final lines:

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

Now we collect everything in a single file with the name

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) LISSI-Soft Ltd (http://soft.lissi.ru) 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 


You can check the operation of this file using the tclsh interpreter:

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

As a result, we got a chain of two certificates in the / tmp directory.

But we wanted to get executable modules for the Linux and Windows platforms, and so that users would not think about any interpreters.

For this purpose, we will use the freewrapTCLSH utility. With the help of this utility we will make the executable modules of our utility for Linux and Windows platforms, both 32-bit and 64-bit. Utility assembly can be carried out for all platforms on any of the platforms. Sorry for the tautology. I will collect on linux_x86_64 (Mageia).

For the assembly will require:
1. The freewrapTCLSH utility for the linux_x86_64 platform;
2. File freewrapTCLSH with this utility for each platform:
- freewrapTCLSH_linux32
- freewrapTCLSH_linux64
- freewrapTCLSH_win32
- freewrapTCLSH_win64
3. The source file of our utility: chainfromcert.tcl
So, the executable chainfromcerty_linuxx86 executable for the Linux x86 platform:

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

Building a utility for the Windows 64-bit platform looks like this:

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

Etc. Utilities are ready to use. Everything necessary for their work they have absorbed.
The code is written in the same way in Python.

In the coming days, I think to supplement the fsb795 package (and it is written in Python) with the function of obtaining the root certificate chain.

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