
小时“ H”无休止地临近:“不允许使用GOST R 34.10-2001签名方案在2018年12月31日之后生成签名!”
但是,出了点问题,有人没有准备好,GOST R 34.10-2001的使用已扩展到2019年。 但是突然之间,每个人都急于将CA转移到GOST R 34.10-2012,并将普通公民转移到新证书。 人们手里拿着几张证书。 在检查证书或电子签名时,开始出现问题,以及如何在受信任的根证书存储中安装根证书。
这适用于Windows上的证书存储以及Firefox和Google Chrome,GnuPG,LibreOffice,电子邮件客户端甚至OpenSSL上的证书存储。 当然,在CA中接收证书并将证书链写入USB闪存驱动器时,必须注意这一点。 另一方面,我们拥有一个数字社会,在任何时候都应该能够从网络获得此链。
Simpleadmin在Habr的页面上
演示了如何执行此操作。 但是,对于普通市民而言,这仍然很困难(特别是如果考虑到其中绝大多数都在Windows上):您需要安装某种opensl,这是我在计算机上没有的获取实用程序,并不是所有人都知道您可以使用wget代替。 以及需要执行多少动作。 当然,有一种方法可以编写脚本,而不仅仅是在openssl及其类似组件之上编写脚本,还可以将其打包在一个适用于各种平台的自包含可执行模块中。
毫无疑问,在
Tcl和Python中可以写些什么。 我们从Tcl开始,
这就是为什么 :
*他妈的Wiki , 甚至还有玩具 (您可以在其中看到有趣的东西:)
* 备忘单
*正常的tclkit构建 (1.5-2 MB作为真正的跨平台的费用)
*和我最喜欢的Evolane eTcl组件(已从死者的站点精心保存:(
在我的个人工具包列表中保持较高的Tcl / Tk评分
是的,是wiki.tcl.tk/16867 (从cgi到Tcl的小型Web服务器,在tclkit下定期以令人羡慕的稳定性使用)
而且它只是美丽而美丽 :)
为此,我将添加
freewrap实用程序的可用性,这将帮助我们为Linux和MS Windows构建独立的实用程序。 结果,我们将拥有chainfromcert实用程序:
bash-4.3$ ./chainfromcert_linux64 Copyright(C)2019 Usage: chainfromcert <file with certificate> <directory for chain certificate> Bad usage! bash-4.3$
该实用程序将使用用户证书(PEM格式和DER格式)以及要保存链中包括的CA证书的目录作为参数设置为参数:
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$
现在考虑该实用程序如何工作。
有关向用户颁发证书的证书颁发机构的信息存储在扩展名为1.3.6.1.5.5.7.1.1的扩展中。 此扩展可以存储CA证书的位置(oid 1.3.6.1.5.5.7.48.2)和有关OCSP CA服务的信息(oid 1.3.6.1.5.5.7.48.1):

并且例如关于电子签名密钥的使用期限的信息被存储在扩展名为2.5.29.16的扩展中。
要解析证书并访问证书扩展,我们将使用pki包:
#!/usr/bin/tclsh -f package require pki
我们还将需要base64软件包:
package require base64
pki软件包以及它加载的asn软件包和base64软件包将帮助我们将证书从PEM编码转换为DER编码,解析ASN结构并实际访问有关CA证书位置的信息。
该实用程序首先检查参数并使用证书下载文件:
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 }
此处的所有内容都很清楚,我们只注意到一件事-带有证书的文件被视为二进制文件:
chan configure $fd -translation binary
这是因为可以同时以DER格式(二进制代码)和PEM格式(base64编码)存储证书。
加载文件后,将调用chainfromcert过程:
set depth [chainfromcert $data $dir]
实际上下载根证书:
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 }
注释中没有添加任何内容,但我们仍未考虑过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 }
此过程基于http包的使用:
package require http
要读取证书,我们使用以下功能:
set token [http::geturl $url -binary 1]
从注释中可以清楚地看到所使用的其余功能的目的。 我们只会为函数http :: ncodel的返回码解密:
200个请求已成功完成
206请求成功完成,但仅下载了文件的一部分
301文件已移动到另一个位置。
302文件临时移动到另一个位置。
要求401服务器身份验证
403拒绝访问该资源
404找不到指定的资源。
500内部错误
还有一个过程需要考虑,即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 }
程序非常简单。 如果这是带有证书的PEM文件(“ ----- BEGIN CERTIFICATE -----”),那么将选择此文件的主体并将其转换为二进制代码:
set asnblock [base64::decode $block]
如果这不是PEM文件,则检查与asn编码的这种“相似性”(零位应为0x30)。
仅此而已,需要添加最后几行:
if {$depth == -1} { puts "Bad file with certificate=$file" usage 1 exit } puts "Goodby!\nLength chain=$depth" usage 0 exit
现在,我们将所有内容收集到一个文件中,名称为
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
您可以使用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 $
结果,我们在/ tmp目录中获得了两个证书链。
但是我们希望获得适用于Linux和Windows平台的可执行模块,以便用户无需考虑任何解释器。
为此,我们将使用
freewrapTCLSH实用程序。 使用该实用程序,我们将为Linux和Windows平台(32位和64位)制作实用程序的可执行模块。 可以在任何平台上为所有平台构建实用程序。 对不起,重言式。 我将在linux_x86_64(Mageia)上构建。
要构建,您将需要:
1. linux_x86_64平台的freewrapTCLSH实用程序;
2.针对每个平台的带有该实用程序的freewrapTCLSH文件:
-freewrapTCLSH_linux32
-freewrapTCLSH_linux64
-freewrapTCLSH_win32
-freewrapTCLSH_win64
3.我们实用程序的源文件:chainfromcert.tcl
因此,针对Linux x86平台的来自certy_linuxx86可执行文件的汇编链:
$freewrapTCLSH chainfromcert.tcl –w freewrapTCLSH_linux32 –o chainfromcerty_linuxx86 $
Windows 64位平台的实用程序的汇编如下所示:
$freewrapTCLSH chainfromcert.tcl –w freewrapTCLSH_win64 –o chainfromcerty_win64.exe $
等等 实用程序可以使用了。 他们吸收了工作所需的一切。
该代码以相同的方式在Python中编写。
在接下来的几天里,我正在考虑对
fsb795软件包(用Python编写)进行补充,以获取根证书链。