Как стать автором
Обновить

Англоязычная кроссплатформенная утилита для просмотра российских квалифицированных сертификатов x509

Время на прочтение 8 мин
Количество просмотров 9.1K
image Сегодня использование цифровых сертификатов X509 v.3 стало обыденным делом. Все больше людей используют их для доступа на сайт Госуслуги, ФНС, электронные торги и т.д. И все больше людей хотят знать, что же находится в этом «сундуке» под названием сертификат. И если сертификат является аналогом паспорта, то как его можно прочитать/просмотреть. Да, в операционных системах присутствуют различные утилиты для просмотра. Но рядовому гражданину они мало что дадут. Возьмем для примера утилиту gcr-viewer, которая, по сути, является стандартным средством для просмотра в Linux-системах, а значит и в отечественных ОС:



Стандартный просмотрщик


Утилита хорошо сделана, удобная. И вообще задумана как универсальная утилита для просмотра файлов, содержащих данные в различных криптографических форматах (сертификаты, запросы, электронные подписи/PKCS#7, защищенные контейнеры PKCS#12 и т.д.). Но, к сожалению, она рассчитана на западную криптографию и не учитывает oid-ы, которые вводятся в вашей стране. И если взглянуть на скриншот, то уже при выводе сведений о владельце сертификата появляются непонятные символы. Слева это сами oid-ы, а справа в 16-ом виде asn1-структура с их значениями. В данном случае это ОГРН (1.2.643.100.1), СНИЛС (1.2.643.100.3) и ИНН (1.2.643.3.131.1.1). И вот как рядовой гражданин должен убедиться, что это его данные. Не надо думать, что так только в Linux, это общая черта любого просмотрщика сертификатов. А если посмотреть дальше, то становится вообще все непонятно:



Появляются какие-то расширения, идентификаторы и значения. В данном случае под oid-ом 1.2.643.100.111 скрывается наименование СКЗИ, которое использовалось пользователем для генерации ключевой пары, закрытый ключ из которой использовался для подписания запроса на сертификат, а открытый ключ из которой лежит в сертификате:



И здесь мало что понятно владельцу сертификата. Он даже не понимает, какой алгоритм использовался при генерации ключа, то ли ГОСТ Р 34.10-2001, то ли ГОСТ Р 34.10-2012 и с какой длиной ключа.

Можно продолжать приводить примеры. Например, если со сроком действия сертификата понятно, то где срок действия ключа?

Есть еще два вопроса, на которые владельцы сертификатов хотели бы иметь ответ: где можно получить цепочку корневых сертификатов (а еще лучше просто получить) и аналогичное вопрос по списку отозванных сертификатов.

И последнее, хотелось бы иметь универсальную утилиту, учитывающую по максимуму особенности российской ИОК/PKI, по настоящему кроссплатформенной и работающей на отечественных и неотечественных ОС. На чем разрабатывать? Конечно на скриптовом языке, хотя бы в силу их кроссплатформенности.

Тут вспомнилось, что совсем недавно отпраздновал свое 30-летие прекрасный скриптовый язык Tcl (Tool Command Language). Программировать на нем одно удовольствие. У него огромное количество расширений (package), которые позволяют практически все. Так, для работы с ASN-структурами имеется пакет asn. Более того, для работы с сертификатами (нас в данном случае интересует их разбор) имеется пакет pki. А для разработки графического интерфейса имеется пакет Tk.

Все то же самое можно сказать и про Pyton с Tkinter, и про perl, и про ruby. Каждый может выбрать по своему вкусу. Мы останавливаемся на связке Tcl/Tk.

Графический дизайн для утилиты позаимствуем у утилиты gcr-viewer. И еще одно требование.

Поскольку у Хабра появилась англоязычная версия, то хотелось, чтобы и утилита имела различные интерфейсы (русский/английский). Но это не главная причина. Важнее то, что все больше граждан западного мира становятся гражданами Российской Федерации, например, всемирно известный актер Депардье. Могут возразить: он француз. Но я тоже военный переводчик с французского языка:



Так что не составит труда добавить и французский интерфейс. Но я думаю, у Депардье нет проблем и с английским языком. С другой стороны, наша страна многонациональная и было бы совсем неплохо, если бы отечественное программное обеспечение, отечественные ОС имели хотя бы несколько национальных интерфейсов.
Немного забегая вперед, вот что из этого вышло:



Приглашаем переводчика


Итак, начнем с «переводчика». На скриншоте он скрывается под национальным флагом. Основное требование к переводчику – это синхронность перевода, т.е. возможность перейти на другой язык в любой момент. Функции переводчика в Tcl/Tk выполняет пакет msgcat:

package require msgcat

Для установки текущего языка используется команда следующего вида:

msgcat::mclocale ru

Словарный запас «переводчика» сохраняется в файле ru.msg в следующем виде:

#Импортируем процедуру msgcat::mcset и в дальнейшем к ней 
# обращаемся по короткому имени mcset 
namespace import -force msgcat::mcset 
#         На английском  Русский перевод
mcset ru "Language" 	"Язык"
…

Ниже приводится текст тестового
скрипта с переводчиком:
#!/usr/bin/wish -f 
#Загружаем пакет msgcat 
package require msgcat 
#Устанавливаем локальный язык ru 
msgcat::mclocale ru 
#Импортируем процедуру ::msgcat::mc и в дальнейшем к ней 
# обращаемся по короткому имени mc 
namespace import msgcat::mc 
#Загружаем словарь из файла [msgcat::mclocale].msg. 
#В данном случае из каталога с данным скриптом. 
msgcat::mcload [file join [file dirname [info script]]] 
#Языковые иконки
image create photo rf_32x21_f -file rf_32x21.png 
image create photo gb_32x21_f -file gb_32x21.png 
#Метка, текст в которой подлежит переводу
label .lab -text "[mc Language]: " -relief flat -bd 0 –bg snow -anchor sw -width 10 
button .but_lang -image rf_32x21_f -command ::changelang -relief flat -bd 0 
pack .lab  -side left -pady {2 0} 
pack .but_lang -side left 
#Процедура смены языка и синхронный перевод 
proc ::changelang {} { 
#Смена языка и синхронный перевод 
#Смена иконки на кнопке 
   if  {[msgcat::mclocale] == "ru"} { 
       msgcat::mclocale en 
       .but_lang configure -image gb_32x21_f 
   } else { 
       msgcat::mclocale ru 
       .but_lang configure -image rf_32x21_f 
   } 
#Перевод текста 
   .lab configure -text "[mc Language]: " 
} 


В данном скрипте в качестве переводчика выступает процедура ::changelang, вызываемая при нажатии кнопки .but_lang с флагом.

Если запустить этот скрипт, то наглядно будет видно, как работает переводчик:



Получаем публичный ключ


Теперь, когда с переводчиком определились, приступим к разбору сертификата. Для этого нам потребуется пакет pki:

package require pki). 

Пакет pki заточен на работу с ключами и сертификатами алгоритма RSA. Если сертификат (proc ::pki::x509::parse_cert) создан с другим типом ключа, то информацию об этом ключе мы не получим:

# Handle RSA public keys by extracting N and E
switch -- $ret(pubkey_algo) {
	"rsaEncryption" {
		set pubkey [binary format B* $pubkey]
		binary scan $pubkey H* ret(pubkey)
			::asn::asnGetSequence pubkey pubkey_parts
			::asn::asnGetBigInteger pubkey_parts ret(n)
			::asn::asnGetBigInteger pubkey_parts ret(e)
			set ret(n) [::math::bignum::tostr $ret(n)]
			set ret(e) [::math::bignum::tostr $ret(e)]
			set ret(l) [expr {int([::pki::_bits $ret(n)] / 8.0000 + 0.5) * 8}]
			set ret(type) rsa
	}
 }

Удивительно то, что алгоритм публичного ключа все же возвращается (ret(pubkey_algo))
Аналогичным образом обстоит дело и с разбором запроса на сертификат (proc ::pki::pkcs::parse_csr):

# Parse public key, based on type
switch -- $pubkey_type {
	"rsaEncryption" {
		set pubkey [binary format B* $pubkey]
		::asn::asnGetSequence pubkey pubkey_parts
		::asn::asnGetBigInteger pubkey_parts key(n)
		::asn::asnGetBigInteger pubkey_parts key(e)
		set key(n) [::math::bignum::tostr $key(n)]
		set key(e) [::math::bignum::tostr $key(e)]
		set key(l) [expr {2**int(ceil(log([::pki::_bits $key(n)])/log(2)))}]
		set key(type) rsa
	}
	default {
		return -code error "Unsupported key type: $pubkey_type"
	}
}

Но здесь он хоть возвращает информацию об ошибке. Но сегодня помимо RSA в ходу, например, ключи на эллиптических кривых ЕС, в том числе и ГОСТ Р 34.10-2012 (ГОСТ Р 34.10-2001 тоже пока ходит).

А ведь достаточно по умолчанию (default) возвращать ASN-структуру публичного ключа, лежащего в сертификате или запросе, а пользователь уже сам в зависимости от типа ключа разберет публичный ключ. Для этого достаточно добавить в возвращаемые значения ASN-структуру публичного ключа в шестнадцатеричном виде:

proc ::pki::x509::parse_cert {cert} {
	. . . 
	::asn::asnGetSequence cert subject
	::asn::asnGetSequence cert pubkeyinfo
#Добавляем в возвращаемый массив ASN-структуру публичного ключа.
       binary scan $pubkeyinfo H* ret(pubkey_pubkeyinfo)
	. . .
}

Все, больше ничего делать не надо. Именно таким образом процедура ::pki::x509::parse_cert возвращает большинство расширений сертификата по той простой причине, что не знает как их разобрать (например, subjectSignTool у наших квалифицированных сертификатов), т.е. отдает на усмотрения пользователя.

С другой стороны, процедура ::pki::x509::parse_cert возвращает одним из результатов tbs-сертификат, который содержит всю информацию из сертификата, кроме его подписи (signature) и типа подписи (signature_algo):

#Читаем сертификат из файла
set fd [open «cert.pem» r]
chan configure –translation binary
set datacert [read $fd]
close $fd 
#разбираем сертификат
array set cert_parse [::pki::x509::parse_cert  $datacert]
#Сохряняем tbs-сертификат
set cert_tbs_hex $cert_parse(cert)

Пишем процедуру извлечения информации о публичном ключе из tbs-сертификата:

proc ::pki::x509::parse_cert_pubkeyinfo {cert_tbs_hex} {
	array set ret [list]
	set wholething [binary format H* $cert_tbs_hex]
	::asn::asnGetSequence wholething cert

	::asn::asnPeekByte cert peek_tag
	if {$peek_tag != 0x02} {
		# Version number is optional, if missing assumed to be value of 0
		::asn::asnGetContext cert - asn_version
		::asn::asnGetInteger asn_version ret(version)
	}
	::asn::asnGetBigInteger cert ret(serial_number)
	::asn::asnGetSequence cert data_signature_algo_seq
		::asn::asnGetObjectIdentifier data_signature_algo_seq ret(data_signature_algo)
	::asn::asnGetSequence cert issuer
	::asn::asnGetSequence cert validity
		::asn::asnGetUTCTime validity ret(notBefore)
		::asn::asnGetUTCTime validity ret(notAfter)
	::asn::asnGetSequence cert subject
	::asn::asnGetSequence cert pubkeyinfo
#Сохраняем и возвращаем в hex asn-структуру публичного ключа
	binary scan $pubkeyinfo H* ret(pubkeyinfo)
	return $ret(pubkeyinfo)
}

А поскольку нас интересует российская криптография, то сразу напишем и процедуру разбора ГОСТ-ового публичного ключа:

proc parse_key_gost {pubkeyinfo_hex} {
    array set ret [list]
    set pubkeyinfo [binary format H* $pubkeyinfo_hex]
    ::asn::asnGetSequence pubkeyinfo pubkey_algoid
	::asn::asnGetObjectIdentifier pubkey_algoid ret(pubkey_algo)
#Убеждаемся, что это ГОСТ-ключ
    if {[string first "1 2 643 " $ret(pubkey_algo)] == -1} {
	return [array get ret]
    }
    ::asn::asnGetBitString pubkeyinfo pubkey
    set pubkey [binary format B* $pubkey]
#Значение публичного ключа
    binary scan $pubkey H* ret(pubkey)
    ::asn::asnGetSequence pubkey_algoid pubalgost
#OID - параметра
    ::asn::asnGetObjectIdentifier pubalgost ret(paramkey)
#OID - Функция хэша
    ::asn::asnGetObjectIdentifier pubalgost ret(hashkey)
#puts "ret(paramkey)=$ret(paramkey)\n"
#puts "ret(hashkey)=$ret(hashkey)\n"
#parray ret
#Возвращаем разобранный ключ: алгоритм ключа, значение ключа и параметры
    return [array get ret]
}

Да, чуть не упустил: после загрузки пакета pki, необходимо добавить в массив ::pki::oids oid-ы, характеризующие ГОСТ и квалифицированный сертификат или просто отсутствующих в этом массиве:

package require pki
#Добавляемые oid-ы
set ::pki::oids(1.2.643.100.1)  "OGRN"
set ::pki::oids(1.2.643.100.5)  "OGRNIP"
set ::pki::oids(1.2.643.3.131.1.1) "INN"
set ::pki::oids(1.2.643.100.3) "SNILS"
 set ::pki::oids(1.2.643.2.2.19) "GOST R 34.10-2001"
 set ::pki::oids(1.2.643.7.1.1.1.1) "GOST R 34.10-2012-256"
 set ::pki::oids(1.2.643.7.1.1.1.2) "GOST R 34.10-2012-512"
set ::pki::oids(1.2.643.2.2.3) "GOST R 34.10-2001 with GOST R 34.11-94"
 set ::pki::oids(1.2.643.7.1.1.3.2) "GOST R 34.10-2012-256 with GOSTR 34.11-2012-256"
 set ::pki::oids(1.2.643.7.1.1.3.3) "GOST R 34.10-2012-512 with GOSTR 34.11-2012-512"
 set ::pki::oids(1.2.643.100.113.1) "KC1 Class Sign Tool"
 set ::pki::oids(1.2.643.100.113.2) "KC2 Class Sign Tool"
. . . 

Можно также пополнить словарный запас переводчика, пополнив файл ru.msg:

mcset ru "GOST R 34.10-2001" "ГОСТ Р 34.10-2001"
mcset ru "GOST R 34.10-2012-256" "ГОСТ Р 34.10-2012-256"
mcset ru "GOST R 34.10-2012-512" "ГОСТ Р 34.10-2012-512"
mcset ru "GOST R 34.10-2001 with GOST R 34.11-94" "ГОСТ Р 34.10-2001 с ГОСТ Р 34.11-94"
mcset ru "GOST R 34.10-2012-256 with GOSTR 34.11-2012-256" "ГОСТ Р 34.10-2012-256 с ГОСТ Р 34.11-2012-256"
mcset ru "GOST R 34.10-2012-512 with GOSTR 34.11-2012-512" "ГОСТ Р 34.10-2012-512 с ГОСТ Р 34.11-2012-512"
. . .
:


Цепочка корневых сертификатов и список отозванных сертификатов


Как получить цепочку корневых сертификатов, уже было рассмотрено ранее. По аналогии пишется процедура получения списка отозванных сертификатов СОС/CRL. Исходный код утилиты и ее дистрибутивы для платформ Linux, OS X (macOS) и MS Windows можно найти


В исходном коде можно найти все процедуры для разбора расширений сертификата.
Противникам Tk (Tcl/Tk, Python/Tkinter и т.д.) предлагаю найти, как говорят, 10 (десять) различий между двумя утилитами: утилитой gcr-viewer, написанной на gtk, и утилитой certViewer, разработанной на Tk:



Сертификаты на токенах/смарткартах PKCS#11


Выше мы говорили о работе с сертификатами (просмотреть, получить цепочку корневых сертификатов, списки отозванных сертификатов, отпечатки по sha1 и sha256 и т.д.), хранящимися в файлах. Но есть еще сертификаты, хранящиеся на токенах/смаркартах PKCS#11. И естественное желание не только их посмотреть, то и экспортировать в файл. Как это сделать, мы расскажем в следующей статье:

Теги:
Хабы:
+18
Комментарии 33
Комментарии Комментарии 33

Публикации

Истории

Работа

Data Scientist
58 вакансий
Python разработчик
128 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн