В комментариях к статье «Использование механизмов криптографических токенов PKCS#11 в скриптовых языках» читатель kovserg написал:«С нетерпением ждём статью с функцией подписания документа и добавлением метки времени».Еще раньше другой участник хабра pas писал о том, что было бы здорово для токенов PKCS#11, «которые сами все умеют считать» (имеются ввиду прежде всего криптографические операции по генерации ключей, формирования и проверки электронной), избавиться от всевозможных прослоек и иметь одну утилиту, которая могла бы, используя механизмы самого токена, и формировать запрос на сертификат, и подписывать документы, проверять подпись документов, проверять подпись и валидность сертификатов.
Знакомство с утилитой
В итоге мы представляем на суд пользователей утилиту

Движущей силой этой утилиты является криптографический токен PKCS#11 c поддержкой российской криптографии как минимум ГОСТ Р 34.10-2012. Если вы собираетесь пользоваться госуслугами и т.п., то необходимо приобрести все же токен, выдерживший сертифицированные испытания в системе сертификации ФСБ России. Если это ваш внутренний электронный документооборот, то это, естественно, на ваше усмотрение. Токены PKCS#11 могут быть разные: программные, аппаратные и даже облачные. Утилита написана на скриптовом языке Tcl/Tk. Когда вы приобретаете токен PKCS#11 не забудьте получить или поинтересоваться, где можно скачать библиотеки для приобретаемого токена для тех или иных платформ. Библиотеки, как правило, находятся в свободном доступе. Для доступа к криптографическим и другим возможностях токена в утилите используется пакет TclPKCS11. Работа утилиты начинается с выбора библиотеки, которая поддерживает ваши токены. Отметим, что библотеки могут поддерживать одновременно работу с несколькими токенами (combobox «выберите токен/смаркарту»):

Может возникнуть вопрос, а что делать, если токен не проинициализирован или требуется поменять PIN-коды и т.д.? Ответ простой – воспользоваться утилитой конфигурирования токенов p11conf.
Для обновления списка токенов (отключили токен, добавили новый) достаточно щелкнуть
по иконке
, находящейся справа от combobox-а «Выберите токен/смарткарту»В combox-е «Сертификат» представлены метки всех сертификатов, хранящихся на выбранном (текущем) токене:

Если нажать на иконку
, находящуюся справа от combobox-а «Сертификат», то появится окно с содержимым сертификата:
Если нажать на кнопку «Save/Сохранить» в окне просмотра, то содержимое разобранного сертификата в текстовом формате будет сохранено в указанном вами файле.
Для просмотра информации о текущем токене можно нажать кнопку « 6. Информация на токене» или подвести курсор на метку токена:

Для того, чтобы узнать, какие криптографические механизмы поддерживает текущий токен, достаточно нажать на кнопку « 5. Список механизмов»:

Создаем запрос на сертификат
Переходим к основным функциям утилиты. И первой такой функцией является создание запроса на сертификат (кнопка « 3. Запрос на сертификат»):
Сообщение «Токен не поддерживает ключи …» появляется в том случае, если выбранный токен не поддерживает генерацию данн��го типа ключа. В этом случае необходимо выбрать другой тип ключа или другой токен. В данном примере можно использвать токен RuToken ECP 2.0 (см. второй скриншот). Combobox «Владелец сертификата» позволяет указать кому будет принадлежать сертификат: физическому лицу, юридическому лицу или индивидуальному предпринимателю. В зависимости от этого будут формироваться на следующих страницах поля для заполнения.
Значимым полем здесь является поле «Наименование СКЗИ». Оно является неотъемлемой частью квалифицированного сертификата и указывает на то, каким СКЗИ будет сгенерирована ключевая пара. Наименование СКЗИ вы можете узнать в формуляре на изделие или в момент приобретения токена. Совпадает ли поле тип в информации о токене с наименованием СКЗИ, я затрудняюсь сказать. Так что лучше посмотреть формуляр. Нажимаем кнопку Next и заполняем требуемые поля:

При заполнении утилита старается контролировать правильность заполнения полей (email, ОГРН и т.п.), выдавая соответствующие предупреждения. После заполнения основных полей будет предложено определиться с форматом и местом хранения на вашем компьютере запроса на сертификата. От вас также потребуется ввести PIN-код вашего токена:

После нажатия кнопки «Next» вам будет предложено еще раз взглянуть на то, что вы ввели, и утвердить ваше решение нажатием клавиши «Finish»:

И, если вы нажмете кнопку «Finish», то на вашем токене будет сгенерирована ключевая пара, создан и подписан запрос:

А можно убедиться, что ключи сгенерированы и сохранены на токене? Да. Нажимаем кнопку « 7. Объекты токена», вводим PIN-код для доступа к токену и ищем объекты СКО_PRIVATE_KEY и CKO_PUBLIV_KEY, у которых метки совпадают с полем «COMMON NANE» (CN), которое вы заполнили при создании запроса на сертификат. В нашем примере это было «Всесильный Хабр»:

Посмотрели и сразу уходите на другую страницу. Лучшим доказательством, что ключевая пара успешно создана, является наличие самого подписанного запроса. Для того чтобы убедиться в этом нажимаем кнопку «Просмотр запроса/сертификата», выбираем сохраненный запрос, нажимаем кновку «Просмотр запроса на сертификат» и смотрим информацию о ключевой паре:

После того как убедились, что запрос успешно создан, надежно прячем токен с закрытым ключом или кладем поближе к сердцу (люди старшего поколения знают, как хранили партийный или комсомольский билеты), копируем запрос на флэшку, берем необходимые документы (паспорт и т.д) и идем в УЦ для получения сертификата. Да, если это не ведомственный УЦ, то еще придется заплатить деньги. Все как с паспортом.
Отправляемся за сертификатом в УЦ
В данном случае для выдачи сертификата достопочтенному Хабру, мы воспользуемся УЦ со страниц все тоже Хабра. УЦ начинает рассмотрение нашей заявки:

После того как заявка поступила в базу данных УЦ, уполномоченный администратор рассматривает ее и либо отклоняет либо утверждает:

После утверждения заявки заявитель вместе с уполномоченным лицом от УЦ определяют цели использования сертификата:

И после этого уже ничто не мешает выпуску сертификата:


После того как заявка поступила в базу данных УЦ, уполномоченный администратор рассматривает ее и либо отклоняет либо утверждает:

После утверждения заявки заявитель вместе с уполномоченным лицом от УЦ определяют цели использования сертификата:

И после этого уже ничто не мешает выпуску сертификата:

После выпуска сертификата сотрудник УЦ экспортирует выпущенный сертификат на флэшку Достопочтенного Хабра:

Кладем сертификат на токен
И вот счастливый обладатель сертификата возвращается в родные пенаты и первым делом решает положить сертификат на токен рядом с ключевой парой. Для этого на главном окне утилиты нажимаем кнопку « 4. Просмотр запроса/сертификата», выбираем файл с сертификатом и операцию «Просмотр сертификата» и нажимаем кнопку выполнить операцию»:

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

Код утилиты проверки подписи сертификата
#!/usr/bin/env tclsh package require pki load ./tclpkcs11.so Tclpkcs11 #Задайте путь к вашей библиотеке PKCS#11 #set pkcs11_module "/usr/local/lib64/librtpkcs11ecp_2.0.so" set pkcs11_module "/usr/local/lib64/libls11sw2016.so" puts "Connect the Token and press Enter" gets stdin yes set handle [pki::pkcs11::loadmodule $pkcs11_module] set slots [pki::pkcs11::listslots $handle] set i 0 foreach slotinfo $slots { set slotid [lindex $slotinfo 0] set slotlabel [lindex $slotinfo 1] set slotflags [lindex $slotinfo 2] if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} { set token_slotlabel $slotlabel set token_slotid $slotid #Найден слот с токеном incr i break } } if {$i == 0} { puts "Нет ни одного токена. Вставьте." exit } #Из PEM в DER proc ::cert_to_der {data} { if {[string first "-----BEGIN CERTIFICATE-----" $data] != -1} { set data [string map {"\r\n" "\n"} $data] } array set parsed_cert [::pki::_parse_pem $data "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"] if {[string range $parsed_cert(data) 0 0 ] == "0" } { #Очень похоже на DER-кодировка "0" == 0x30 set asnblock $parsed_cert(data) } else { set asnblock "" } return $asnblock } proc usage {use error} { puts "Copyright(C) Orlov Vladimir (http://museum.lissi-crypto.ru/) 2019" if {$use == 1} { puts $error puts "Usage:\nverify_cert_with_pkcs11 <file with certificate> \[<file with CA certificate>\]\n" } } set countcert [llength $argv] if { $countcert < 1 || $countcert > 2 } { usage 1 "Bad usage!" exit } set file [lindex $argv 0] if {![file exists $file]} { usage 1 "File $file not exist" exit } #Проверяемый сертификат cert_user puts "Loading user certificate: $file" set fd [open $file] chan configure $fd -translation binary set cert_user [read $fd] close $fd if {$cert_user == "" } { usage 1 "Bad file with certificate user: $file" exit } set cert_user [cert_to_der $cert_user] if {$cert_user == ""} { puts "User certificate bad" exit } catch {array set cert_parse [::pki::x509::parse_cert $cert_user]} if {![info exists cert_parse]} { puts "User certificate bad" exit } #parray cert_parse if {$countcert == 1} { if {$cert_parse(issuer) != $cert_parse(subject)} { puts "Bad usage: not self signed certificate" } else { set cert_CA $cert_user } } else { set fileca [lindex $argv 1] if {![file exists $fileca]} { usage 1 "File $fileca not exist" exit } #Сертификат издателя cert_CA puts "Loading CA certificate: $fileca" set fd [open $fileca] chan configure $fd -translation binary set cert_CA [read $fd] close $fd if {$cert_CA == "" } { usage 1 "Bad file with certificate CA=$fileca" exit } set cert_CA [cert_to_der $cert_CA] if {$cert_CA == ""} { puts "CA certificate bad" exit } } foreach slotinfo $slots { set slotid [lindex $slotinfo 0] set slotlabel [lindex $slotinfo 1] set slotflags [lindex $slotinfo 2] if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} { set token_slotlabel $slotlabel set token_slotid $slotid } } #Ключ от корневого сертификата catch {array set cert_parse_CA [::pki::x509::parse_cert $cert_CA]} if {![info exists cert_parse_CA]} { puts "CA certificate bad" exit } #Проверяем издателя if {$cert_parse(issuer) != $cert_parse_CA(subject)} { puts "Bad issuer" exit } set aa [dict create pkcs11_handle $handle pkcs11_slotid $token_slotid] set tbs_cert [binary format H* $cert_parse(cert)] catch {set signature_algo_number [::pki::_oid_name_to_number $cert_parse(signature_algo)]} if {![info exists signature_algo_number]} { set signature_algo_number $cert_parse(signature_algo) } switch -- $signature_algo_number { "1.2.643.2.2.3" - "1 2 643 2 2 3" { # "GOST R 34.10-2001 with GOST R 34.11-94" set digest_algo "gostr3411" } "1.2.643.7.1.1.3.2" - "1 2 643 7 1 1 3 2" { # "GOST R 34.10-2012-256 with GOSTR 34.11-2012-256" set digest_algo "stribog256" } "1.2.643.7.1.1.3.3" - "1 2 643 7 1 1 3 3" { # "GOST R 34.10-2012-512 with GOSTR 34.11-2012-512" set digest_algo "stribog512" } default { puts "Неизвестная алгоритм подписи:$signature_algo_number" exit } } #Посчитать хэш от tbs-сертификата!!!! set digest_hex [pki::pkcs11::digest $digest_algo $tbs_cert $aa] #Получаем asn-структуру публичного ключа #Создаем список ключевых элементов binary scan $cert_CA H* cert_CA_hex array set infopk [pki::pkcs11::pubkeyinfo $cert_CA_hex [list pkcs11_handle $handle pkcs11_slotid $token_slotid]] set lpk [dict create pkcs11_handle $handle pkcs11_slotid $token_slotid] #Добавляем pybkeyinfo в список ключевых элементов lappend lpk "pubkeyinfo" lappend lpk $infopk(pubkeyinfo) array set lpkar $lpk puts "Enter PIN user for you token \"$token_slotlabel\":" gets stdin password if { [pki::pkcs11::login $handle $token_slotid $password] == 0 } { puts "Bad PIN" exit } if {[catch {set verify [pki::pkcs11::verify $digest_hex $cert_parse(signature) $lpk]} res] } { puts "Ошибка проверки подписи=$res" exit } if {$verify != 1} { puts "BAD SIGNATURE=$verify" } else { puts "SIGNATURE OK=$verify" }
Но нас сейчас интересует операция импорта полученного сертификата на наш токен. Выбираем операцию «Импорт сертификата на токен» и нажимаем кнопку «Выполнить операцию». Утилита проверит электронную подпись сертификата. Для этого от вас потребуется ввести PIN-код к токену. И если все пройдет нормально, то сертификат будет импортирован на токен:

Метку (nickname ) сертификата можно будет лицезреть в списке сертификатов:

Это наш личный сертификат, сертификат для которого есть ключевая пара. И если еще раз просмотреть список объектов на токене, то мы найдем три объекта, имеющую метку «Всесильный Хабр from УЦ 12_512» и одинаковые CKA_ID. Этими тремя объектами являются сам сертификат (CKO_CERTIFICATE), открытый (CKO_PUBLIC_KEY) и закрытый (CKO_PRIVATE_KEY) ключи. Метка для этой тройки объектов устанавливается следующим образом:
<CN владельца сертификат> from <CN издателя сертификата>.
Ниже мы покажем как изменить метку.
Теперь, когда мы положили сертификат на токен, как получить доступ к нему? Для того, чтобы получить доступ к функциям работы с сертификатами, находящимися на токене, достаточно подвести курсор на метку «��ертификат» и нажать правую кнопку мыши:

Подписываем электронной подписью первый документ
Дождемся следующей статьи. Ждать придется пару дней. Что там еще будет рассмотрено можно понять из скриншота:

Продолжение здесь.
