- PVSM.RU - https://www.pvsm.ru -
Неумолимо приближается час «Ч» [1]: «использование схемы подписи ГОСТ Р 34.10-2001 для формирования подписи после 31 декабря 2018 года не допускается!».
Однако потом что-то пошло не так, кто-то оказался не готов, и использование ГОСТ Р 34.10-2001 продлили на 2019 год. Но вдруг все бросились переводить УЦ на ГОСТ Р 34.10-2012, а простых граждан переводить на новые сертификаты. У людей на руках стало по нескольку сертификатов. При проверки сертификатов или электронной подписи стали возникать вопросы, а где взять корневые сертификаты, чтобы установить в хранилища доверенных корневых сертификатов.
Это касается как хранилища сертификатов в Windows, так и хранилища сертификатов в браузерах Firefox и Google Chrome, GnuPG [1], LibreOffice [2], почтовых клиентах [3] и даже OpenSSL [4]. Конечно, надо было озаботиться этим при получении сертификата в УЦ и записать цепочку сертификатов на флешку. А с другой стороны у нас же цифровое общество и в любой момент должна быть возможность получить эту цепочку из сети. Как это сделать на страницах Хабра показал [5] simpleadmin [6]. Однако для рядового гражданина это все же сложновато (особенно, если иметь ввиду, что абсолютное их большинство сидит на Windows): нужно иметь «какой-то» openssl, утилиту fetch, которой и у меня не оказалось на компьютере, и далеко не каждый знает, что вместо нее можно использовать wget. А сколько действий нужно выполнить. Выход конечно есть, написать скрипт, но не просто скрипт поверх openssl и иже с ним, а упакованный в самодостаточный выполняемый модуль для различных платформ.
На чем писать никаких сомнений не было – на Tcl и Python [7]. И начинаем с Tcl и вот почему [8]:
* охренительной вики [9], где есть даже игрушки [10] (там можно подсмотреть интересное :)
* шпаргалки [11]
* нормальные сборки tclkit [12] (1.5 — 2 Мб как плата за реальную кросс-платформенность)
* и моя любимая сборка eTcl от Evolane [13] (бережно сохранённая с умершего сайта :(
сохраняют высокий рейтинг Tcl/Tk в моём личном списке инструментария
и, да, wiki.tcl.tk/16867 [14] (мелкий web-сервер с cgi на Tcl, периодически используется с завидным постоянством под tclkit)
а ещё — это просто красиво [15] и красиво [16] :)
К этому бы я добавил наличии утилиты freewrap [17], которая нам и поможет собрать автономные (standalone) утилиты для 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) и каталог, в котором будут сохранены сертификаты УЦ, входящие в цепочку:
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$
А теперь рассмотрим как работает утилита.
Информация о центре сертификации, выдавшем сертификат пользователю, хранится в расширении с oid-ом 1.3.6.1.5.5.7.1.1. В этом расширении может хранится как о местонахождении сертификата УЦ (oid 1.3.6.1.5.5.7.48.2), так и информация о службе OCSP УЦ (oid 1.3.6.1.5.5.7.48.1):
А информация, например, о периоде использования ключа электронной подписи хранится в расширении с oid-ом 2.5.29.16.
Для разбора сертификата и доступа к расширениям сертификата воспользуемся пакетом pki:
#!/usr/bin/tclsh -f
package require pki
Нам также потребуются пакет base64:
package require base64
Пакет pki, а также подгружаемые им пакет asn, и пакет base64 и помогут нам преобразовывать сертификаты из PEM-кодировки в DER-кодировку, разобрать ASN-структуры и собственно получить доступ к информации о местонахождении сертификатов УЦ.
Работа утилиты начинается с проверки параметров и загрузки файла с сертификатом:
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
}
Здесь все понятно и отметим только одно – файл с сертификатом рассматривается как бинарный файл:
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 $f w]
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
Теперь все собираем в один файл с именем
#!/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 $f w]
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
Проверить работу этого файла можно с помощью интерпретарора 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 [18]. С помощью этой утилиты мы сделаем выполняемые модули нашей утилиты для платформ Linux и Windows как 32-х разрядных так и 64-х. Сборку утилит можно проводить для всех платформ на любой из платформ. Извините за тавтологию. Я буду собирать на linux_x86_64 (Mageia).
Для сборки потребуется:
1. Утилита freewrapTCLSH для платформы linux_x86_64;
2. Файл freewrapTCLSH с этой утилитой для каждой платформы:
— freewrapTCLSH_linux32
— freewrapTCLSH_linux64
— freewrapTCLSH_win32
— freewrapTCLSH_win64
3. Исходный файл нашей утилиты: chainfromcert.tcl
Итак, собираемый выполняемый файл chainfromcerty_linuxx86 для платформы Linux x86:
$freewrapTCLSH chainfromcert.tcl –w freewrapTCLSH_linux32 –o chainfromcerty_linuxx86
$
Сборка утилиты для платформы Windows 64-х битного выглядит так:
$freewrapTCLSH chainfromcert.tcl –w freewrapTCLSH_win64 –o chainfromcerty_win64.exe
$
И т.д. Утилиты готовы к использованию. Все необходимое для их работы они вобрали в себя.
Аналогичным образом пишется код и на Python-е.
В ближайшие дни я думаю дополнить пакет fsb795 [19] (а он написан на Python-е) функцией получения цепочки корневых сертификатов.
Автор: saipr
Источник [20]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/305496
Ссылки в тексте:
[1] час «Ч»: https://habr.com/ru/post/417735/
[2] LibreOffice: https://habr.com/ru/post/428429/
[3] почтовых клиентах: https://habr.com/ru/post/316736/
[4] OpenSSL: https://habr.com/ru/post/415423/
[5] показал: https://habr.com/ru/post/304458/
[6] simpleadmin: https://habr.com/ru/users/simpleadmin/
[7] Tcl и Python: https://habr.com/ru/post/335842/
[8] вот почему: https://www.linux.org.ru/forum/development/12833014/page1
[9] вики: http://wiki.tcl.tk/
[10] даже игрушки: http://wiki.tcl.tk/20801
[11] шпаргалки: http://pleac.sourceforge.net/pleac_tcl/index.html
[12] сборки tclkit: https://code.google.com/archive/p/tclkit/downloads
[13] eTcl от Evolane: http://wiki.tcl.tk/15260
[14] wiki.tcl.tk/16867: http://wiki.tcl.tk/16867
[15] красиво: https://habrahabr.ru/post/89919/
[16] красиво: https://habrahabr.ru/post/89822/
[17] freewrap: http://freewrap.sourceforge.net/
[18] freewrapTCLSH: https://sourceforge.net/projects/freewrap/files/freewrap/freeWrap%206.64/
[19] fsb795: https://habr.com/ru/post/421107/
[20] Источник: https://habr.com/ru/post/436370/?utm_campaign=436370
Нажмите здесь для печати.