КриптоАРМ на базе контейнера PKCS#12. Создание электронной подписи CadES-X Long Type 1. Часть 3

от автора

image Прошло время и утилита, начатая как просмотрщик сертификатов, дополненная функциями работы с криптографическими токенами PKCS#11 и создания запросов (PKCS#10) на квалифицированный сертификат, пополнилась, как и было заявлено, функциями работы с контейнерами PKCS#12.

Итак, для работы с контейнерами PKCS#12 потребуется утилита cryptoarmpkcs :

Загружаем, запускаем утилиту cryptoarmpkcs и нажимаем кнопку «PKCS12»:

Скриншот наглядно демонстрирует, что позволяет делать утилита, имея на руках контейнер PKCS#12:
— просмотреть сертификат владельца, для чего достаточно будет кликнуть на иконку, находящуюся справа от поля «friendlyName»;
— сформировать одну из трех типов электроноой подписи CAdes:
— сохранить сертификат из контейнера в файле;
— сохранить сертификат на токене;
— сохранить закрытый ключ владельца на токене.
Две последние операции возможны только при подключенном токене (выбрана библиотека PKCS#11 и подключен токен). Отметим также, что не все токены позволяют импортировать закрытые ключи. Но эта операция важна, если мы хотим работать с облачными токенами PKCS#11.
В отличие от криптографического токена PKCS#11, защищенный контейнер PKCS#12 не является криптографической машиной, т.е. он только хранит в защищенном виде (зашифрованном на пароле) сертификат и закрытый ключ к нему и не выполняет никаких криптографических операций. Требования к защищенному контейнеру на базе российской криптографии сформулированы ТК-26 в документе «Р 50.1.112-2016. Транспортный ключевой контейнер.», который утвержден и введен в действие Приказом Федерального агентства по техническому регулированию и метрологии от 23 ноября 2016 г. No 1753-ст.
Как получить контейнер PKCS#12 из криптопровайдера MS CSP хорошо описано в одной из статей на Хабр.
Для работы с PKCS#12 пришлось разработать два новых пакета tcl, помимо ранее созданного TclPKCS11. Первый пакет Lcc, для поддержки российской криптографии с учетом рекомендаций ТК-26.

Команды, поддерживаемые пакетом Lcc можно увидеть здесь:

// Digest commands // gost3411_2012 Tcl_CreateObjCommand(interp, "lcc_gost3411_2012", gost3411_2012_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_ctx_create", gost3411_2012_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_ctx_update", gost3411_2012_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_ctx_final", gost3411_2012_ctx_final_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_ctx_delete", gost3411_2012_ctx_delete_Cmd, NULL, NULL); // gost3411_94 Tcl_CreateObjCommand(interp, "lcc_gost3411_94", gost3411_94_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_94_ctx_create", gost3411_94_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_94_ctx_update", gost3411_94_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_94_ctx_final", gost3411_94_ctx_final_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_94_ctx_delete", gost3411_94_ctx_delete_Cmd, NULL, NULL); // sha1 Tcl_CreateObjCommand(interp, "lcc_sha1", sha1_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_sha1_ctx_create", sha1_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_sha1_ctx_update", sha1_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_sha1_ctx_final", sha1_ctx_final_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_sha1_ctx_delete", sha1_ctx_delete_Cmd, NULL, NULL); // HMAC commands // gost3411hmac Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_hmac", gost3411_2012_hmac_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_hmac_ctx_create", gost3411_2012_hmac_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_hmac_ctx_update", gost3411_2012_hmac_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_hmac_ctx_final", gost3411_2012_hmac_ctx_final_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_hmac_ctx_delete", gost3411_2012_hmac_ctx_delete_Cmd, NULL, NULL); // gost3411_94_hmac Tcl_CreateObjCommand(interp, "lcc_gost3411_94_hmac", gost3411_94_hmac_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_94_hmac_ctx_create", gost3411_94_hmac_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_94_hmac_ctx_update", gost3411_94_hmac_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_94_hmac_ctx_final", gost3411_94_hmac_ctx_final_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_94_hmac_ctx_delete", gost3411_94_hmac_ctx_delete_Cmd, NULL, NULL); // PKCS#5 commands Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_pkcs5", gost3411_2012_pkcs5_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_94_pkcs5", gost3411_94_pkcs5_Cmd, NULL, NULL); // PKCS#12 PBA Tcl_CreateObjCommand(interp, "lcc_gost3411_94_pkcs12_pba", gost3411_94_pkcs12_pba_Cmd, NULL, NULL); // gost3411_2012 KDF Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_256_kdf", gost3411_2012_256_kdf_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_256_kdf_tree", gost3411_2012_256_kdf_tree_Cmd, NULL, NULL); // PRF TLS Tcl_CreateObjCommand(interp, "lcc_gost3411_94_prf_tls", gost3411_94_prf_tls_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_256_prf_tls", gost3411_2012_256_prf_tls_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3411_2012_512_prf_tls", gost3411_2012_512_prf_tls_Cmd, NULL, NULL); // gost3410-2012 commands // 256 bits Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_256_getGroupByOid", gost3410_2012_256_getGroupByOid_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_256_getGroupByDerOid", gost3410_2012_256_getGroupByDerOid_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_256_getGroupById", gost3410_2012_256_getGroupById_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_256_createPrivateKey", gost3410_2012_256_createPrivateKey_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_256_createPublicKey", gost3410_2012_256_createPublicKey_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_256_sign", gost3410_2012_256_sign_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_256_verify", gost3410_2012_256_verify_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_256_vko", gost3410_2012_256_vko_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_256_keg", gost3410_2012_256_keg_Cmd, NULL, NULL); // 512 bits Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_512_getGroupByOid", gost3410_2012_512_getGroupByOid_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_512_getGroupByDerOid", gost3410_2012_512_getGroupByDerOid_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_512_getGroupById", gost3410_2012_512_getGroupById_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_512_createPrivateKey", gost3410_2012_512_createPrivateKey_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_512_createPublicKey", gost3410_2012_512_createPublicKey_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_512_sign", gost3410_2012_512_sign_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_512_verify", gost3410_2012_512_verify_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_512_vko", gost3410_2012_512_vko_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost3410_2012_512_keg", gost3410_2012_512_keg_Cmd, NULL, NULL); // gost3410-2001-vko (with 3411-94) Tcl_CreateObjCommand(interp, "lcc_gost3410_2001_vko", gost3410_2001_vko_Cmd, NULL, NULL); // Magma commands // ECB Tcl_CreateObjCommand(interp, "lcc_magma_ecb_ctx_create", magma_ecb_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_ecb_ctx_update", magma_ecb_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_ecb_ctx_delete", magma_ecb_ctx_delete_Cmd, NULL, NULL); // CBC Tcl_CreateObjCommand(interp, "lcc_magma_cbc_ctx_create", magma_cbc_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_cbc_ctx_update", magma_cbc_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_cbc_ctx_delete", magma_cbc_ctx_delete_Cmd, NULL, NULL); // CTR Tcl_CreateObjCommand(interp, "lcc_magma_ctr_ctx_create", magma_ctr_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_ctr_ctx_update", magma_ctr_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_ctr_ctx_delete", magma_ctr_ctx_delete_Cmd, NULL, NULL); // OFB Tcl_CreateObjCommand(interp, "lcc_magma_ofb_ctx_create", magma_ofb_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_ofb_ctx_update", magma_ofb_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_ofb_ctx_delete", magma_ofb_ctx_delete_Cmd, NULL, NULL); // CFB Tcl_CreateObjCommand(interp, "lcc_magma_cfb_ctx_create", magma_cfb_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_cfb_ctx_update", magma_cfb_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_cfb_ctx_delete", magma_cfb_ctx_delete_Cmd, NULL, NULL); // OMAC Tcl_CreateObjCommand(interp, "lcc_magma_omac_ctx_create", magma_omac_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_omac_ctx_update", magma_omac_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_omac_ctx_final", magma_omac_ctx_final_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_omac_ctx_delete", magma_omac_ctx_delete_Cmd, NULL, NULL); // CTR_ACPKM Tcl_CreateObjCommand(interp, "lcc_magma_ctr_acpkm_ctx_create", magma_ctr_acpkm_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_ctr_acpkm_ctx_update", magma_ctr_acpkm_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_ctr_acpkm_ctx_delete", magma_ctr_acpkm_ctx_delete_Cmd, NULL, NULL); // OMAC_ACPKM Tcl_CreateObjCommand(interp, "lcc_magma_omac_acpkm_ctx_create", magma_omac_acpkm_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_omac_acpkm_ctx_update", magma_omac_acpkm_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_omac_acpkm_ctx_final", magma_omac_acpkm_ctx_final_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_omac_acpkm_ctx_delete", magma_omac_acpkm_ctx_delete_Cmd, NULL, NULL); // key export/import Tcl_CreateObjCommand(interp, "lcc_magma_key_export", magma_key_export_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_magma_key_import", magma_key_import_Cmd, NULL, NULL); // Kuznyechik commands // ECB Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ecb_ctx_create", kuznyechik_ecb_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ecb_ctx_update", kuznyechik_ecb_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ecb_ctx_delete", kuznyechik_ecb_ctx_delete_Cmd, NULL, NULL); // CBC Tcl_CreateObjCommand(interp, "lcc_kuznyechik_cbc_ctx_create", kuznyechik_cbc_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_cbc_ctx_update", kuznyechik_cbc_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_cbc_ctx_delete", kuznyechik_cbc_ctx_delete_Cmd, NULL, NULL); // CTR Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ctr_ctx_create", kuznyechik_ctr_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ctr_ctx_update", kuznyechik_ctr_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ctr_ctx_delete", kuznyechik_ctr_ctx_delete_Cmd, NULL, NULL); // OFB Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ofb_ctx_create", kuznyechik_ofb_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ofb_ctx_update", kuznyechik_ofb_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ofb_ctx_delete", kuznyechik_ofb_ctx_delete_Cmd, NULL, NULL); // CFB Tcl_CreateObjCommand(interp, "lcc_kuznyechik_cfb_ctx_create", kuznyechik_cfb_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_cfb_ctx_update", kuznyechik_cfb_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_cfb_ctx_delete", kuznyechik_cfb_ctx_delete_Cmd, NULL, NULL); // OMAC Tcl_CreateObjCommand(interp, "lcc_kuznyechik_omac_ctx_create", kuznyechik_omac_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_omac_ctx_update", kuznyechik_omac_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_omac_ctx_final", kuznyechik_omac_ctx_final_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_omac_ctx_delete", kuznyechik_omac_ctx_delete_Cmd, NULL, NULL); // CTR_ACPKM Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ctr_acpkm_ctx_create", kuznyechik_ctr_acpkm_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ctr_acpkm_ctx_update", kuznyechik_ctr_acpkm_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_ctr_acpkm_ctx_delete", kuznyechik_ctr_acpkm_ctx_delete_Cmd, NULL, NULL); // OMAC_ACPKM Tcl_CreateObjCommand(interp, "lcc_kuznyechik_omac_acpkm_ctx_create", kuznyechik_omac_acpkm_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_omac_acpkm_ctx_update", kuznyechik_omac_acpkm_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_omac_acpkm_ctx_final", kuznyechik_omac_acpkm_ctx_final_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_omac_acpkm_ctx_delete", kuznyechik_omac_acpkm_ctx_delete_Cmd, NULL, NULL); // key export/import Tcl_CreateObjCommand(interp, "lcc_kuznyechik_key_export", kuznyechik_key_export_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_kuznyechik_key_import", kuznyechik_key_import_Cmd, NULL, NULL); // gost28147 commands Tcl_CreateObjCommand(interp, "lcc_gost28147_getParamsByOid", gost28147_getParamsByOid_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_getParamsByDerOid", gost28147_getParamsByDerOid_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_getParamsById", gost28147_getParamsById_Cmd, NULL, NULL); // ECB Tcl_CreateObjCommand(interp, "lcc_gost28147_ecb_ctx_create", gost28147_ecb_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_ecb_ctx_update", gost28147_ecb_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_ecb_ctx_delete", gost28147_ecb_ctx_delete_Cmd, NULL, NULL); // CBC Tcl_CreateObjCommand(interp, "lcc_gost28147_cbc_ctx_create", gost28147_cbc_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_cbc_ctx_update", gost28147_cbc_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_cbc_ctx_delete", gost28147_cbc_ctx_delete_Cmd, NULL, NULL); // CNT Tcl_CreateObjCommand(interp, "lcc_gost28147_cnt_ctx_create", gost28147_cnt_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_cnt_ctx_update", gost28147_cnt_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_cnt_ctx_delete", gost28147_cnt_ctx_delete_Cmd, NULL, NULL); // CFB Tcl_CreateObjCommand(interp, "lcc_gost28147_cfb_ctx_create", gost28147_cfb_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_cfb_ctx_update", gost28147_cfb_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_cfb_ctx_delete", gost28147_cfb_ctx_delete_Cmd, NULL, NULL); // OMAC Tcl_CreateObjCommand(interp, "lcc_gost28147_omac_ctx_create", gost28147_omac_ctx_create_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_omac_ctx_update", gost28147_omac_ctx_update_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_omac_ctx_final", gost28147_omac_ctx_final_Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lcc_gost28147_omac_ctx_delete", gost28147_omac_ctx_delete_Cmd, NULL, NULL); // KDF Tcl_CreateObjCommand(interp, "lcc_gost28147_kdf", gost28147_kdf_Cmd, NULL, NULL);

Как видно, в пакете есть поддержка не только ГОСТ Р 34.10-2012 и ГОСТ Р 34.11-2012, но и поддержка алгоритмов шифрования Кузнечик и Магма.
Второй пакет GostPfx предназначен для распарсивания контейнера PKCS#11. Эти пакеты создавались на базе библиотек из состава сертифицированного СКЗИ. Эта скрупулезная работа была проделана моим коллегой и товарищем, автором многих иллюстраций к нашим публикациям Блажновым В.Ю. Именно реализация этих пакетов позволила создать по настоящему платформонезависимую утилиту для использования ее в электронном документеобороте. При работе с контейнером PKCS#12 на скриптовом языке Tcl выяснилась интересная особенность. Контейнер, создаваемый с использованием openssl с поддержкой российской криптографии, создается строго в DER-кодировке, а контейнер, создаваемый с использованием пакета NSS, создается с использованием BER-кодировки, в которой используются tag-и с неопределенной длиной (в поле длины tag-а находится значение 0x80). И при использовании стандартного пакета asn языка Tcl для разбора контейнера PKCS#12 выяснолось, что он не понимает asn-конструкций в кодировке BER:

    if {$length == 0x080} {         return -code error "Indefinite length BER encoding not yet supported"     }

Проведенный анализ пакета asn показал, что достаточно подменить функцию ::asn::asnGetLength:

package require asn #Переименовываем оригинальную функцию rename ::asn::asnGetLength ::asn::asnGetLength.orig #Добавляем доработанную функцию proc ::asn::asnGetLength {data_var length_var} {     upvar 1 $data_var data  $length_var length     asnGetByte data length     if {$length == 0x080} { #Вычисление длины тэга неопределенной длины # Indefinite length BER encoding yet supported 	set lendata [string length $data] 	set tvl 1 	set length 0 	set data1 $data 	while {$tvl != 0} { 	    ::asn::asnGetByte data1 peek_tag  	    ::asn::asnPeekByte data1 peek_tag1 #Конец тэга неопределенной длины 	    if {$peek_tag == 0x00 && $peek_tag1 == 0x00} { 		incr tvl -1 		::asn::asnGetByte data1 tag  		incr length 2 		continue 	    } #Начало тэга неопределенной длины 	    if {$peek_tag1 == 0x80} { 		incr tvl 		if {$tvl > 0} { 		    incr length 2 		} 		::asn::asnGetByte data1 tag  	    } else { 		set l1 [string length $data1] 		::asn::asnGetLength data1 ll 		set l2 [string length $data1] 		set l3 [expr $l1 - $l2] 		incr length $l3 		incr length $ll 		incr length 		::asn::asnGetBytes data1 $ll strt 	    } 	} 	return #        return -code error "Indefinite length BER encoding not yet supported"     }     if {$length > 0x080} {     # The retrieved byte is a prefix value, and the integer in the     # lower nibble tells us how many bytes were used to encode the     # length data following immediately after this prefix.         set len_length [expr {$length & 0x7f}]         if {[string length $data] < $len_length} {             return -code error \ 		"length information invalid, not enough octets left"          }         asnGetBytes data $len_length lengthBytes         switch $len_length {             1 { binary scan $lengthBytes     cu length }             2 { binary scan $lengthBytes     Su length }             3 { binary scan \x00$lengthBytes Iu length }             4 { binary scan $lengthBytes     Iu length }             default {                                 binary scan $lengthBytes H* hexstr 		scan $hexstr %llx length             }         }     }     return }

Такая подмена позволяет обрабатывать tag-и неопределенной длины.

Для распарсивания контейнера PKCS#12 написан пакет GostPfx:

 set OS [lindex $tcl_platform(os) 0] # Подключаем пакет Lcc # Подключаем пакет Lrnd  namespace eval ::GostPfx {   namespace export pfxParse pfxMacDataParse pfxGetAuthSafeTbs pfxHmacVerify   namespace export pfxTbsParse pfxPbeDataParse pfxPbeKey pfxDataDecrypt   namespace export pfxIdentityDataParse pfxCertBagsParse pfxKeyBagsParse   namespace export pfxKeyBagDecrypt pfxKeyDataParse pfxDataEncrypt   namespace export pfxCreateSingleCertKey pfxGetSingleCertKey pfxCreateLocalKeyID       variable INTEGER_TAG 2   variable OCTET_STRING_TAG 4   variable OBJECT_IDENTIFIER_TAG 6   variable SEQUENCE_TAG 16   variable SET_TAG 17   variable BMP_STRING_TAG 30     variable oid_Gost_3411_94 "1 2 643 2 2 9"   variable oid_Gost3411_2012_512 "1 2 643 7 1 1 2 3"   variable oid_pkcs7_data "1 2 840 113549 1 7 1"   variable oid_pkcs7_encrypted_data "1 2 840 113549 1 7 6"   variable oid_pkcs5PBES2 "1 2 840 113549 1 5 13"   variable oid_pBKDF2 "1 2 840 113549 1 5 12"   variable oid_HMACgostR3411_94 "1 2 643 2 2 10"   variable oid_tc26_hmac_gost_3411_2012_512 "1 2 643 7 1 1 4 2"   variable oid_gost28147_89 "1 2 643 2 2 21"   variable oid_PKCS12CertificateBag "1 2 840 113549 1 12 10 1 3"   variable oid_PKCS9x509Certificate "1 2 840 113549 1 9 22 1"   variable oid_friendlyName "1 2 840 113549 1 9 20"   variable oid_localKeyID "1 2 840 113549 1 9 21"   variable oid_pkcs8ShroudedKeyBag "1 2 840 113549 1 12 10 1 2"   variable oid_tc26_gost_28147_89_param_A "1 2 643 7 1 2 5 1 1"   variable oid_GostR3410_2001 "1 2 643 2 2 19"   variable oid_GostR3411_94_with_GostR3410_2001 "1 2 643 2 2 3"   variable oid_tc26_gost3410_2012_256 "1 2 643 7 1 1 1 1"   variable oid_tc26_signwithdigest_gost3410_2012_256 "1 2 643 7 1 1 3 2"   variable oid_tc26_gost3410_2012_512 "1 2 643 7 1 1 1 2"   variable oid_tc26_signwithdigest_gost3410_2012_256 "1 2 643 7 1 1 3 3"    } proc reverse {args} {     set res [list]     if {[llength $args] == 1} {         set args [lindex $args 0]     }     foreach elem $args {         set res [linsert $res 0 $elem]     }     return $res } #----------------------------------------------------------------------------- # asnGetOctetString : Retrieve arbitrary string. #----------------------------------------------------------------------------- proc ::asn::asnGet24OctetString {data_var string_var} {     # Here we need the full decoder for length data.      upvar 1 $data_var data $string_var string          asnGetByte data tag     if {$tag != 0x24} {          return -code error \             [format "Expected Octet String 24 (0x24), but got %02x" $tag]     }     asnGetLength data length     asnGetBytes  data $length temp     set string $temp     return }   # indata => dict: authSafe macData digestAlg digestParamset hmac  proc ::GostPfx::pfxParse {indata} {   variable INTEGER_TAG    variable SEQUENCE_TAG      set data $indata   set dres [dict create]   set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $SEQUENCE_TAG} {     asn::asnGetSequence data seqValue     set tag_len [asn::asnPeekTag seqValue tag_var tag_type_var constr_var]      if {$tag_var == $INTEGER_TAG} {       asn::asnGetInteger seqValue version       dict set dres "version" $version     } else {       error "pfxParse: Invalid PFX DER structure 1 tag=$tag_var"     }         set tag_len [asn::asnPeekTag seqValue tag_var tag_type_var constr_var]     if {$tag_var == $SEQUENCE_TAG} {       asn::asnGetSequence seqValue authSafe       dict set dres "authSafe" $authSafe     } else {       error "pfxParse: Invalid PFX DER structure 2"     }         set tag_len [asn::asnPeekTag seqValue tag_var tag_type_var constr_var]     # Optional     if {$tag_var == $SEQUENCE_TAG} {       asn::asnGetSequence seqValue macData       dict set dres "macData" $macData     }   } else {     error "pfxParse: Invalid PFX DER structure 3"   }   return $dres }  # macData => dict: salt iter  proc ::GostPfx::pfxMacDataParse {macData} {   variable INTEGER_TAG    variable OCTET_STRING_TAG    variable OBJECT_IDENTIFIER_TAG    variable SEQUENCE_TAG    set data $macData   set dres [dict create]   set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $SEQUENCE_TAG} {     asn::asnGetSequence data seqValue1     set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]     if {$tag_var == $SEQUENCE_TAG} {       asn::asnGetSequence seqValue1 seqValue2       set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]       if {$tag_var == $OBJECT_IDENTIFIER_TAG} {         asn::asnGetObjectIdentifier seqValue2 digestAlg         dict set dres "digestAlg" $digestAlg       } else {         error "pfxMacDataParse: Invalid MAC data structure"       }         set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]       # Optional       if {$tag_var == $OBJECT_IDENTIFIER_TAG} {         asn::asnGetObjectIdentifier seqValue2 digestParamset         dict set dres "digestParamset" $digestParamset       }     }     set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]     if {$tag_var == $OCTET_STRING_TAG} {       asn::asnGetOctetString seqValue1 hmac       dict set dres "hmac" $hmac         }  else {       error "pfxMacDataParse: Invalid MAC data structure"     }     } else {     error "pfxMacDataParse: Invalid MAC data structure"   }     set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $OCTET_STRING_TAG} {     asn::asnGetOctetString data salt     dict set dres "salt" $salt   } else {     error "pfxMacDataParse: Invalid MAC data structure"   }     set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $INTEGER_TAG} {     asn::asnGetInteger data iter     dict set dres "iter" $iter   } else {     error "pfxMacDataParse: Invalid MAC data structure"   }       return $dres  }  # authSafeData => tbs proc ::GostPfx::pfxGetAuthSafeTbs {authSafeData} {   variable OCTET_STRING_TAG    variable OBJECT_IDENTIFIER_TAG    set data $authSafeData   set tbs ""   set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $OBJECT_IDENTIFIER_TAG} {     asn::asnGetObjectIdentifier data oid   } else {     error "pfxGetAuthSafeTbs: Invalid AuthSafe structure"   }    set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_type_var == "CONTEXT"} {     asn::asnGetContext data contextNumber contextVar encoding_type     ::asn::asnPeekByte contextVar peek_tag      ::asn::asnPeekByte contextVar peek_tag1 1 	if {$peek_tag == 0x24 && $peek_tag1 == 0x80} { 	    set contextVar1 $contextVar 	    ::asn::asnGet24OctetString contextVar1 contextVar 	    puts "pfxGetAuthSafeTbs=0x240x80" 	}     set tag_len [asn::asnPeekTag contextVar tag_var tag_type_var constr_var]      if {$tag_var == $OCTET_STRING_TAG} {       asn::asnGetOctetString contextVar tbs     } else {       error "pfxGetAuthSafeTbs: Invalid AuthSafe structure"     }   } else {     error "pfxGetAuthSafeTbs: Invalid AuthSafe structure"   }   return $tbs }  # Для старого алгоритма PBA пароль должен быть представлен в UTF-16 без BOM # с двумя нулевыми байтами в конце. # После преобразования пароля в unicode нужно папарно переставить байты # и добавить два нулевых байта в конец строки.    proc ::GostPfx::passwordToUtf16 {password} {   binary scan [encoding convertto unicode $password] c* bytes   set bytes_len [llength $bytes]   for {set i 0} {$i < $bytes_len} {incr i 2} {     set left [lindex $bytes $i]     set right [lindex $bytes [expr {$i+1}]]     lset bytes $i $right     lset bytes [expr {$i+1}] $left   }   lappend bytes 0 0   set out [binary format c* $bytes]   return $out }  # returns 1 or 0 proc ::GostPfx::pfxHmacVerify {tbs hmac password hmacDigestAlg hmacKeySalt hmacKeyIter} {   variable oid_Gost_3411_94    variable oid_Gost3411_2012_512      if {$hmacDigestAlg == $oid_Gost_3411_94} {       set passwordUtf16 [passwordToUtf16 $password]     set hmacPbaKey [lcc_gost3411_94_pkcs12_pba $passwordUtf16 $hmacKeySalt $hmacKeyIter]     set hmacPba [lcc_gost3411_94_hmac $tbs $hmacPbaKey]     if {$hmacPba != $hmac} {       # В случае контейнера ТК 26 v 1.0 ключ HMAC нужно генерировать по-другому       set hmacPbaKey [string range [lcc_gost3411_94_pkcs5 $password $hmacKeySalt $hmacKeyIter 96] 64 95]       set hmacPba [lcc_gost3411_94_hmac $tbs $hmacPbaKey]       if {$hmacPba != $hmac} {         #puts "hmacPba != hmac"       } else {         #puts "TC 26 v 1.0 PBA HMAC OK"         return 1       }     } else {       #puts "Obsolete PBA HMAC OK"       return 1     }   } else {     if {$hmacDigestAlg == $oid_Gost3411_2012_512} {       set hmacPbaKey [string range [lcc_gost3411_2012_pkcs5 $password $hmacKeySalt $hmacKeyIter 96] 64 95]       set hmacPba [lcc_gost3411_2012_hmac 64 $tbs $hmacPbaKey]       if {$hmacPba != $hmac} {         #puts "hmacPba != hmac"       } else {         #puts "TC 26 v 2.0 PBA HMAC OK"         return 1       }     } else {       error "pfxHmacVerify: Unsupported digest algorithm: $hmacDigestAlg"     }   }   return 0 }  proc ::GostPfx::p7mParse {p7m} {   variable OCTET_STRING_TAG    variable OBJECT_IDENTIFIER_TAG    variable SEQUENCE_TAG    variable INTEGER_TAG   variable oid_pkcs7_data       set seqValue2 $p7m     set dres [dict create]           set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]           if {$tag_type_var == "CONTEXT"} {             asn::asnGetContext seqValue2 contextNumber contextData encoding_type             set tag_len [asn::asnPeekTag contextData tag_var tag_type_var constr_var]             if {$tag_var == $SEQUENCE_TAG} {               asn::asnGetSequence contextData seqValue3               set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]               if {$tag_var == $INTEGER_TAG} {                 asn::asnGetInteger seqValue3 version                 set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]                 if {$tag_var == $SEQUENCE_TAG} {                   asn::asnGetSequence seqValue3 seqValue4                   set tag_len [asn::asnPeekTag seqValue4 tag_var tag_type_var constr_var]                   if {$tag_var == $OBJECT_IDENTIFIER_TAG} {                     asn::asnGetObjectIdentifier seqValue4 oid2                     if {$oid2 == $oid_pkcs7_data} {                       set tag_len [asn::asnPeekTag seqValue4 tag_var tag_type_var constr_var]                       if {$tag_var == $SEQUENCE_TAG} {                         asn::asnGetSequence seqValue4 certsPbeData                                           dict set dres "certsPbeData" $certsPbeData                         set tag_len [asn::asnPeekTag seqValue4 tag_var tag_type_var constr_var]                         if {$tag_type_var == "CONTEXT"} { 			  ::asn::asnPeekByte seqValue4 peek_tag  			  ::asn::asnPeekByte seqValue4 peek_tag1 1 			  if {$peek_tag == 0xA0 && $peek_tag1 == 0x80} { 				set seqValue4_80 $seqValue4 				set seqValue4 [string range $seqValue4_80 2 {end-2}]                         	asn::asnGetOctetString seqValue4 encrCerts  			  } else {                             asn::asnGetContext seqValue4 contextNumber encrCerts encoding_type                           }                           dict set dres "encrCerts" $encrCerts                         } else {                           error "pfxTbsParse: Invalid encrypted certificates structure 1"                         }                         } else {                         error "pfxTbsParse: Invalid encrypted certificates structure 2"                       }                     } else {                       error "pfxTbsParse: Invalid encrypted certificates structure 3"                     }                   } else {                     error "pfxTbsParse: Invalid encrypted certificates structure 4"                   }                 } else {                   error "pfxTbsParse: Invalid encrypted certificates structure 5"                 }               } else {                 error "pfxTbsParse: Invalid encrypted certificates structure 6"               }             } else {               error "pfxTbsParse: Invalid encrypted certificates structure 7"             }           } else {               error "pfxTbsParse: Invalid encrypted certificates structure 8"     	  }     	  return $dres }  proc ::GostPfx::p7dParse {p7d} {   variable OCTET_STRING_TAG    variable OBJECT_IDENTIFIER_TAG    variable SEQUENCE_TAG    variable INTEGER_TAG   variable oid_pkcs7_data      set dres [dict create]      set seqValue2 $p7d           set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]           if {$tag_type_var == "CONTEXT"} {             asn::asnGetContext seqValue2 contextNumber contextData encoding_type ############## 	    ::asn::asnPeekByte contextData peek_tag  	    ::asn::asnPeekByte contextData peek_tag1 1 	    if {$peek_tag == 0x24 && $peek_tag1 == 0x80} { 		set contextVar1 $contextData 		::asn::asnGet24OctetString contextVar1 contextData 		puts "p7dParse=0x240x80" 	    } #################             set tag_len [asn::asnPeekTag contextData tag_var tag_type_var constr_var]             if {$tag_var == $OCTET_STRING_TAG} {               asn::asnGetOctetString contextData keyBags               dict set dres "keyBags" $keyBags             } else {               error "pfxTbsParse: Invalid key bags structure 1"             }           } else {             error "pfxTbsParse: Invalid key bags structure 2"           }      	  return $dres }  # tbs => dict: keyBags certsPbeData encrCerts proc ::GostPfx::pfxTbsParse {tbs} {   variable OCTET_STRING_TAG    variable OBJECT_IDENTIFIER_TAG    variable SEQUENCE_TAG    variable INTEGER_TAG   variable oid_pkcs7_data    variable oid_pkcs7_encrypted_data      set data $tbs   set dres [dict create] #  set dres [list]    set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $SEQUENCE_TAG} {     asn::asnGetSequence data seqValue1      set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]     if {$tag_var == $SEQUENCE_TAG} {       asn::asnGetSequence seqValue1 seqValue2       set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]       if {$tag_var == $OBJECT_IDENTIFIER_TAG} {         asn::asnGetObjectIdentifier seqValue2 oid2          if {$oid2 == $oid_pkcs7_encrypted_data} { 	  set dres [::GostPfx::p7mParse $seqValue2]         } elseif {$oid2 == $oid_pkcs7_data} { #    	    set gg [dict create]     	    set gg [list] 	    set gg [::GostPfx::p7dParse $seqValue2] 	    foreach {l l1} $gg { 		dict set dres $l $l1 	    }  	} else {           error "pfxTbsParse: Invalid encrypted certificates structure 9 oid2=$oid2, oid_pkcs7_encrypted_data=$oid_pkcs7_encrypted_data"         }               } else {         error "pfxTbsParse: Invalid encrypted certificates structure 10"       }     }     set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]     if {$tag_var == $SEQUENCE_TAG} {       asn::asnGetSequence seqValue1 seqValue2       set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]       if {$tag_var == $OBJECT_IDENTIFIER_TAG} {         asn::asnGetObjectIdentifier seqValue2 oid2         if {$oid2 == $oid_pkcs7_data} { #    	    set gg [dict create]     	    set gg [list] 	    set gg [::GostPfx::p7dParse $seqValue2] 	    foreach {l l1} $gg { 		dict set dres $l $l1 	    }          } elseif {$oid2 == $oid_pkcs7_encrypted_data} {     	    set gg [list] 	    set gg [::GostPfx::p7mParse $seqValue2] 	    foreach {l l1} $gg { 		dict set dres $l $l1 	    }         } else {           error "pfxTbsParse: Invalid key bags structure 3"         }       } else {         error "pfxTbsParse: Invalid key bags structure 4"       }     }   } else {     error "pfxTbsParse: Invalid TBS structure 5"   }   return $dres }  # oid_pkcs5PBES2, ... => dict: hmacKeySalt hmacKeyIter hmacAlg digestParamset cipherAlg cipherIV cipherParamset proc ::GostPfx::pfxPbeDataParse {pbeData} {   variable OCTET_STRING_TAG    variable OBJECT_IDENTIFIER_TAG    variable SEQUENCE_TAG    variable INTEGER_TAG   variable oid_pkcs5PBES2    variable oid_pBKDF2      set data $pbeData   set dres [dict create]   set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $OBJECT_IDENTIFIER_TAG} {     asn::asnGetObjectIdentifier data oid1     if {$oid1 == $oid_pkcs5PBES2} {       set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]       if {$tag_var == $SEQUENCE_TAG} {         asn::asnGetSequence data seqValue1         set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]         if {$tag_var == $SEQUENCE_TAG} {           asn::asnGetSequence seqValue1 seqValue2           set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]           if {$tag_var == $OBJECT_IDENTIFIER_TAG} {             asn::asnGetObjectIdentifier seqValue2 oid2             if {$oid2 == $oid_pBKDF2} {               set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]               if {$tag_var == $SEQUENCE_TAG} {                 asn::asnGetSequence seqValue2 seqValue3                 set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]                  if {$tag_var == $OCTET_STRING_TAG} {                   asn::asnGetOctetString seqValue3 hmacKeySalt                   dict set dres "hmacKeySalt" $hmacKeySalt                   set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]                   if {$tag_var == $INTEGER_TAG} {                     asn::asnGetInteger seqValue3 hmacKeyIter                     dict set dres "hmacKeyIter" $hmacKeyIter                     set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]                     if {$tag_var == $SEQUENCE_TAG} {                       asn::asnGetSequence seqValue3 seqValue4                       set tag_len [asn::asnPeekTag seqValue4 tag_var tag_type_var constr_var]                       if {$tag_var == $OBJECT_IDENTIFIER_TAG} {                         asn::asnGetObjectIdentifier seqValue4 hmacAlg                         dict set dres "hmacAlg" $hmacAlg                         set tag_len [asn::asnPeekTag seqValue4 tag_var tag_type_var constr_var]                         if {$tag_var == $OBJECT_IDENTIFIER_TAG} {                           asn::asnGetObjectIdentifier seqValue4 digestParamset                           dict set dres "digestParamset" $digestParamset                         }                        } else {                         error "pfxPbeDataParse: Invalid PBKDF2 HMAC algorithm structure"                       }                     } else {                       error "pfxPbeDataParse: Invalid PBKDF2 HMAC algorithm structure"                     }                                       } else {                     error "pfxPbeDataParse: Invalid PBKDF2 iteration count structure"                   }                 } else {                   error "pfxPbeDataParse: Invalid PBKDF2 salt structure"                 }               } else {                 error "pfxPbeDataParse: Invalid PBKDF2 structure"               }                           } else {               error "pfxPbeDataParse: Invalid PBKDF2 structure"             }           } else {             error "pfxPbeDataParse: Invalid PBKDF2 structure"           }           }         set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]         if {$tag_var == $SEQUENCE_TAG} {           asn::asnGetSequence seqValue1 seqValue2           set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]           if {$tag_var == $OBJECT_IDENTIFIER_TAG} {             asn::asnGetObjectIdentifier seqValue2 cipherAlg             dict set dres "cipherAlg" $cipherAlg             set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]             if {$tag_var == $SEQUENCE_TAG} {               asn::asnGetSequence seqValue2 seqValue3               set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]               if {$tag_var == $OCTET_STRING_TAG} {                 asn::asnGetOctetString seqValue3 cipherIV                 dict set dres "cipherIV" $cipherIV                 set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]                 if {$tag_var == $OBJECT_IDENTIFIER_TAG} {                   asn::asnGetObjectIdentifier seqValue3 cipherParamset                   dict set dres "cipherParamset" $cipherParamset                 }               } else {                 error "pfxPbeDataParse: Invalid PBE cipher IV structure"               }             } else {               error "pfxPbeDataParse: Invalid PBE cipher params structure"             }           } else {             error "pfxPbeDataParse: Invalid PBE cipher algorithm structure"           }         } else {           error "pfxPbeDataParse: Invalid PBE cipher algorithm structure"         }               } else {         error "pfxPbeDataParse: Invalid PBE structure"        }     } else {       error "pfxPbeDataParse: Invalid PBE structure"      }   } else {     error "pfxPbeDataParse: Invalid PBE structure"    }   return $dres }  # generates cipher key proc ::GostPfx::pfxPbeKey {password hmacAlg hmacKeySalt hmacKeyIter} {   variable oid_HMACgostR3411_94    variable oid_tc26_hmac_gost_3411_2012_512      set key ""   if {$hmacAlg == $oid_HMACgostR3411_94} {     set key [lcc_gost3411_94_pkcs5 $password $hmacKeySalt $hmacKeyIter 32]   } else {     if {$hmacAlg == $oid_tc26_hmac_gost_3411_2012_512} {       set key [lcc_gost3411_2012_pkcs5 $password $hmacKeySalt $hmacKeyIter 32]     } else {       error "pfxPbeKey: Unsupported PKCS5 HMAC algorithm $hmacAlg"     }   }   return $key }  # encrData => decrData proc ::GostPfx::pfxDataDecrypt {data alg paramset key iv} {   variable oid_gost28147_89 puts "pfxDataDecrypt: paramset=$paramset"    set out ""   if {$alg == $oid_gost28147_89} {     set dotted_paramset [string map {" " "."} $paramset]     set par [lcc_gost28147_getParamsByOid $dotted_paramset]     if {$par > 0} {       set ctx [lcc_gost28147_cfb_ctx_create $par 0 $key $iv]       if {$ctx > 0} {         set out [lcc_gost28147_cfb_ctx_update $ctx $data]         lcc_gost28147_cfb_ctx_delete $ctx       } else {         error "pfxDataDecrypt: Invalid cipher key or IV"       }       lcc_handle_free $par     } else {       error "pfxDataDecrypt: Unsupported cipher paramset $paramset"     }   } else {     error "pfxDataDecrypt: Unsupported cipher algorithm $alg"     }   return $out }   # plain data => encrData proc ::GostPfx::pfxDataEncrypt {data alg paramset key iv} {   variable oid_gost28147_89      set out ""   if {$alg == $oid_gost28147_89} {     set dotted_paramset [string map {" " "."} $paramset]     set par [lcc_gost28147_getParamsByOid $dotted_paramset]     if {$par > 0} {       set ctx [lcc_gost28147_cfb_ctx_create $par 1 $key $iv]       if {$ctx > 0} {         set out [lcc_gost28147_cfb_ctx_update $ctx $data]         lcc_gost28147_cfb_ctx_delete $ctx       } else {         error "pfxDataEncrypt: Invalid cipher key or IV"       }       lcc_handle_free $par     } else {       error "pfxDataEncrypt: Unsupported cipher paramset $paramset"     }   } else {     error "pfxDataEncrypt: Unsupported cipher algorithm $alg"     }   return $out }   # SET => dict: friendlyName localKeyID proc ::GostPfx::pfxIdentityDataParse {identityData} {   variable SEQUENCE_TAG    variable OBJECT_IDENTIFIER_TAG    variable OCTET_STRING_TAG    variable SET_TAG    variable BMP_STRING_TAG   variable oid_friendlyName    variable oid_localKeyID      set data $identityData   set dres [dict create]   set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $SET_TAG} {     asn::asnGetSet data set1     while {1} {       set tag_len [asn::asnPeekTag set1 tag_var tag_type_var constr_var]       if {$tag_var != $SEQUENCE_TAG} {         break       }       asn::asnGetSequence set1 seqValue3       set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]       if {$tag_var == $OBJECT_IDENTIFIER_TAG} {         asn::asnGetObjectIdentifier seqValue3 oid3         set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]         if {$tag_var == $SET_TAG} {           asn::asnGetSet seqValue3 set2           set tag_len [asn::asnPeekTag set2 tag_var tag_type_var constr_var]           if {$oid3 == $oid_friendlyName} {             if {$tag_var == $BMP_STRING_TAG} {               asn::asnGetString set2 friendlyName               dict set dres "friendlyName" $friendlyName             } else {               error "pfxIdentityDataParse: friendlyName is not BMP STRING"             }           } else {             if {$oid3 == $oid_localKeyID} {               if {$tag_var == $OCTET_STRING_TAG} {                 asn::asnGetOctetString set2 localKeyID                 dict set dres "localKeyID" $localKeyID               }             } else {               error "pfxIdentityDataParse: localKeyID is not OCTET STRING"             }           }         } else {           error "pfxIdentityDataParse: Invalid structure"         }                       } else {         error "pfxIdentityDataParse: Invalid structure"       }     }   } else {     error "pfxIdentityDataParse: Invalid structure"   }   return $dres }  # certBags => { {dict: certificat  friendlyName localKeyID} ...} proc ::GostPfx::pfxCertBagsParse {certBags} {   variable SEQUENCE_TAG    variable OBJECT_IDENTIFIER_TAG    variable OCTET_STRING_TAG    variable SET_TAG    variable BMP_STRING_TAG   variable oid_PKCS12CertificateBag    variable oid_PKCS9x509Certificate    variable oid_friendlyName    variable oid_localKeyID      set data $certBags   set lres {}   set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $SEQUENCE_TAG} {     asn::asnGetSequence data seqValue1     set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]     while {$tag_var == $SEQUENCE_TAG} {       asn::asnGetSequence seqValue1 seqValue2       set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]       if {$tag_var == $OBJECT_IDENTIFIER_TAG} {         asn::asnGetObjectIdentifier seqValue2 oid1         if {$oid1 == $oid_PKCS12CertificateBag} {           set dcert [dict create]           set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]           if {$tag_type_var == "CONTEXT"} {             asn::asnGetContext seqValue2 contextNumber context1 encoding_type             set tag_len [asn::asnPeekTag context1 tag_var tag_type_var constr_var]             if {$tag_var == $SEQUENCE_TAG} {               asn::asnGetSequence context1 seqValue3               set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]               if {$tag_var == $OBJECT_IDENTIFIER_TAG} {                 asn::asnGetObjectIdentifier seqValue3 oid2                 if {$oid2 == $oid_PKCS9x509Certificate} {                   set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]                   if {$tag_type_var == "CONTEXT"} {                     asn::asnGetContext seqValue3 contextNumber context2 encoding_type                     set tag_len [asn::asnPeekTag context2 tag_var tag_type_var constr_var]                     if {$tag_var == $OCTET_STRING_TAG} {                       asn::asnGetOctetString context2 certificate             	      dict set dcert "certificate" $certificate                     } else {                       error "pfxCertBagsParse: Invalid structure 0"                     }                                       } else {                     error "pfxCertBagsParse: Invalid structure 1"                   }                 } else {                   error "pfxCertBagsParse: Invalid structure 2"                 }               } else {                 error "pfxCertBagsParse: Invalid structure 3"               }             } else {               error "pfxCertBagsParse: Invalid structure 4"             }                      } else {             error "pfxCertBagsParse: Invalid structure 5"           }           set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]                      set did [pfxIdentityDataParse $seqValue2]           if {[dict exists $did friendlyName]} {             dict set dcert "friendlyName" [dict get $did friendlyName]           }           if {[dict exists $did localKeyID]} {             dict set dcert "localKeyID" [dict get $did localKeyID]           }         } else {           error "pfxCertBagsParse: Invalid structure 6"         }       } else {         error "pfxCertBagsParse: Invalid structure 7"       }       lappend lres $dcert                    set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]     }       } else {     error "pfxCertBagsParse: Invalid structure 8"   }    return [reverse $lres] #  return $lres }  # SEQUENCE, {SEQUENCE, oid_pkcs8ShroudedKeyBag, ...} => { {dict: keyBag friendlyName localKeyID} ... } proc ::GostPfx::pfxKeyBagsParse {keyBags} {   variable SEQUENCE_TAG    variable OBJECT_IDENTIFIER_TAG    variable OCTET_STRING_TAG    variable SET_TAG   variable oid_pkcs8ShroudedKeyBag       set data $keyBags   set lres {}   set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $SEQUENCE_TAG} {     asn::asnGetSequence data seqValue2     while {1} {       set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]       if {$tag_var != $SEQUENCE_TAG} {         break       }       asn::asnGetSequence seqValue2 seqValue3       set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]       if {$tag_var == $OBJECT_IDENTIFIER_TAG} {         asn::asnGetObjectIdentifier seqValue3 oid2         if {$oid2 == $oid_pkcs8ShroudedKeyBag} {            set dbag [dict create]           set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]           if {$tag_type_var == "CONTEXT"} {             asn::asnGetContext seqValue3 contextNumber context2 encoding_type             set tag_len [asn::asnPeekTag context2 tag_var tag_type_var constr_var]             if {$tag_var == $SEQUENCE_TAG} {               asn::asnGetSequence context2 keyBag               dict set dbag keyBag $keyBag                                    } else {               error "pfxKeyBagsParse: Invalid structure"             }                                     } else {             error "pfxKeyBagsParse: Invalid structure"           }               set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]           if {$tag_var == $SET_TAG} {             set did [pfxIdentityDataParse $seqValue3]             if {[dict exists $did friendlyName]} {               dict set dbag friendlyName [dict get $did friendlyName]             }             if {[dict exists $did localKeyID]} {               dict set dbag localKeyID [dict get $did localKeyID]             }           }                              lappend lres $dbag         } else {           error "pfxKeyBagsParse: Invalid structure"         }           } else {         error "pfxKeyBagsParse: Invalid structure"       }         }   } else {     error "pfxKeyBagsParse: Invalid structure"   }               return $lres }  # keyBag password => decrKey  proc ::GostPfx::pfxKeyBagDecrypt {keyBag password} {   variable SEQUENCE_TAG   variable OCTET_STRING_TAG      set decrKey ""   set data $keyBag   set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $SEQUENCE_TAG} {     asn::asnGetSequence data pbeData     set dpbe [pfxPbeDataParse $pbeData]     if {[dict exists $dpbe hmacKeySalt]} {       set hmacKeySalt [dict get $dpbe hmacKeySalt]     }     if {[dict exists $dpbe hmacKeyIter]} {       set hmacKeyIter [dict get $dpbe hmacKeyIter]     }     if {[dict exists $dpbe hmacAlg]} {       set hmacAlg [dict get $dpbe hmacAlg]     }     if {[dict exists $dpbe digestParamset]} {       set digestParamset [dict get $dpbe digestParamset]     }     if {[dict exists $dpbe cipherAlg]} {       set cipherAlg [dict get $dpbe cipherAlg]     }     if {[dict exists $dpbe cipherIV]} {       set cipherIV [dict get $dpbe cipherIV]     }     if {[dict exists $dpbe cipherParamset]} {       set cipherParamset [dict get $dpbe cipherParamset]     }          set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]     if {$tag_var == $OCTET_STRING_TAG} {       asn::asnGetOctetString data encrKey      } else {       error "pfxKeyBagDecrypt: Invalid structure"     }     set cipherKey [pfxPbeKey $password $hmacAlg $hmacKeySalt $hmacKeyIter]     set decrKey [pfxDataDecrypt $encrKey $cipherAlg $cipherParamset $cipherKey $cipherIV]   } else {     error "pfxKeyBagDecrypt: Invalid structure"   }          return $decrKey }  # decrKey => dict: version keyAlg gost3410Paramset gost3411Paramset keyValue proc ::GostPfx::pfxKeyDataParse {decrKey} {   variable SEQUENCE_TAG    variable INTEGER_TAG    variable OBJECT_IDENTIFIER_TAG    variable OCTET_STRING_TAG    set dKey [dict create]   set data $decrKey   set tag_len [asn::asnPeekTag data tag_var tag_type_var constr_var]   if {$tag_var == $SEQUENCE_TAG} {     asn::asnGetSequence data seqValue1     set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]     if {$tag_var == $INTEGER_TAG} {       asn::asnGetInteger seqValue1 version       dict set dKey version $version       set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]       if {$tag_var == $SEQUENCE_TAG} {         asn::asnGetSequence seqValue1 seqValue2         set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]         if {$tag_var == $OBJECT_IDENTIFIER_TAG} {           asn::asnGetObjectIdentifier seqValue2 keyAlg           dict set dKey keyAlg $keyAlg           set tag_len [asn::asnPeekTag seqValue2 tag_var tag_type_var constr_var]           if {$tag_var == $SEQUENCE_TAG} {             asn::asnGetSequence seqValue2 seqValue3             set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]             if {$tag_var == $OBJECT_IDENTIFIER_TAG} {               asn::asnGetObjectIdentifier seqValue3 gost3410Paramset               dict set dKey gost3410Paramset $gost3410Paramset               set tag_len [asn::asnPeekTag seqValue3 tag_var tag_type_var constr_var]               if {$tag_var == $OBJECT_IDENTIFIER_TAG} {                 asn::asnGetObjectIdentifier seqValue3 gost3411Paramset                 dict set dKey gost3411Paramset $gost3411Paramset               }             } else {               error "pfxKeyDataParse: Invalid key paramset structure"             }              } else {             error "pfxKeyDataParse: Invalid key paramset structure"           }                     } else {           error "pfxKeyDataParse: Invalid key algorithm structure"         }                 } else {         error "pfxKeyDataParse: Invalid key algorithm structure"       }           set tag_len [asn::asnPeekTag seqValue1 tag_var tag_type_var constr_var]       if {$tag_var == $OCTET_STRING_TAG} {         asn::asnGetOctetString seqValue1 octets1         set tag_len [asn::asnPeekTag octets1 tag_var tag_type_var constr_var]         if {$tag_var == $OCTET_STRING_TAG} {           asn::asnGetOctetString octets1 keyValue           dict set dKey keyValue $keyValue         } else {           error "pfxKeyDataParse: Invalid key value structure"         }       } else {         error "pfxKeyDataParse: Invalid key value structure"       }           } else {       error "pfxKeyDataParse: Invalid key info structure"     }      } else {     error "pfxKeyDataParse: Invalid key info structure"   }         return $dKey }  # => dict:  proc ::GostPfx::pfxGetSingleCertKey {indata password} {      set dres [dict create]      #puts "pfxParse"   set dpfx [pfxParse $indata]   if {[dict exists $dpfx authSafe]} {     set authSafe [dict get $dpfx authSafe]   }   if {[dict exists $dpfx macData]} {     set macData [dict get $dpfx macData]   }      #puts "pfxMacDataParse"   set dhmac [pfxMacDataParse $macData]    if {[dict exists $dhmac salt]} {     set hmacKeySalt [dict get $dhmac salt]   }   if {[dict exists $dhmac iter]} {     set hmacKeyIter [dict get $dhmac iter]   }   if {[dict exists $dhmac digestAlg]} {     set hmacDigestAlg [dict get $dhmac digestAlg]   }   if {[dict exists $dhmac hmac]} {     set hmac [dict get $dhmac hmac]   }    #puts "pfxGetAuthSafeTbs"   set tbs [pfxGetAuthSafeTbs $authSafe]    #puts "pfxHmacVerify"   set hmac_ok [pfxHmacVerify $tbs $hmac $password $hmacDigestAlg $hmacKeySalt $hmacKeyIter]   if {$hmac_ok != 1} {     error "pfxGetSingleKeyCert: Check integrity: Invalid password or container corrupted!"   }    #puts "pfxTbsParse"   set dKeysCertsData [pfxTbsParse $tbs]   if {[dict exists $dKeysCertsData keyBags]} {     set keyBags [dict get $dKeysCertsData keyBags]   }   if {[dict exists $dKeysCertsData certsPbeData]} {     set certsPbeData [dict get $dKeysCertsData certsPbeData]   }   if {[dict exists $dKeysCertsData encrCerts]} {     set encrCerts [dict get $dKeysCertsData encrCerts]   }    #puts "pfxPbeDataParse"   set dCertsPbe [pfxPbeDataParse $certsPbeData]   if {[dict exists $dCertsPbe hmacKeySalt]} {     set hmacKeySalt [dict get $dCertsPbe hmacKeySalt]   }   if {[dict exists $dCertsPbe hmacKeyIter]} {     set hmacKeyIter [dict get $dCertsPbe hmacKeyIter]   }   if {[dict exists $dCertsPbe hmacAlg]} {     set hmacAlg [dict get $dCertsPbe hmacAlg]   }   if {[dict exists $dCertsPbe digestParamset]} {     set digestParamset [dict get $dCertsPbe digestParamset]   }   if {[dict exists $dCertsPbe cipherAlg]} {     set cipherAlg [dict get $dCertsPbe cipherAlg]   }   if {[dict exists $dCertsPbe cipherIV]} {     set cipherIV [dict get $dCertsPbe cipherIV]   }   if {[dict exists $dCertsPbe cipherParamset]} {     set cipherParamset [dict get $dCertsPbe cipherParamset]   }    #puts "pfxPbeKey"   set cipherKey [pfxPbeKey $password $hmacAlg $hmacKeySalt $hmacKeyIter]    #puts "pfxDataDecrypt"   set certBags [pfxDataDecrypt $encrCerts $cipherAlg $cipherParamset $cipherKey $cipherIV]    #puts "pfxCertBagsParse"   set lcerts [pfxCertBagsParse $certBags]    set dcert [lindex $lcerts 0]   if {[dict exists $dcert "certificate"]} {     set cert [dict get $dcert "certificate"]     dict set dRes certificate $cert   }   if {[dict exists $dcert "friendlyName"]} {     set cert_friendlyName [dict get $dcert "friendlyName"]   }   if {[dict exists $dcert "localKeyID"]} {     set cert_localKeyID [dict get $dcert "localKeyID"]   }    #puts "pfxKeyBagsParse"    set lKeyBags [pfxKeyBagsParse $keyBags]   set dKeyBag [lindex $lKeyBags 0]   if {[dict exists $dKeyBag keyBag]} {     set keyBag [dict get $dKeyBag keyBag]   }   if {[dict exists $dKeyBag friendlyName]} {     set key_friendlyName [dict get $dKeyBag friendlyName]     dict set dRes friendlyName $key_friendlyName     if {[info exists cert_friendlyName] && ($cert_friendlyName != $key_friendlyName)} {       puts "pfxGetSingleKeyCert: Warning: certificate friendlyName: $cert_friendlyName is not equal to key friendlyName: $key_friendlyName"     }   }   if {[dict exists $dKeyBag localKeyID]} {     set key_localKeyID [dict get $dKeyBag localKeyID]     dict set dRes localKeyID $key_localKeyID     if {[info exists cert_localKeyID] && ($cert_localKeyID != $key_localKeyID)} {       puts "pfxGetSingleKeyCert: Warning: certificate localKeyID is not equal to key localKeyID"     }   }    #puts "pfxKeyBagDecrypt"   set decrKey [pfxKeyBagDecrypt $keyBag $password]    #puts "pfxKeyDataParse"   set dKey [pfxKeyDataParse $decrKey]   if {[dict exists $dKey version]} {     set version [dict get $dKey version]   }   if {[dict exists $dKey keyAlg]} {     set keyAlg [dict get $dKey keyAlg]     dict set dRes keyAlg $keyAlg   }   if {[dict exists $dKey gost3410Paramset]} {     set gost3410Paramset [dict get $dKey gost3410Paramset]     dict set dRes gost3410Paramset $gost3410Paramset   }   if {[dict exists $dKey gost3411Paramset]} {     set gost3411Paramset [dict get $dKey gost3411Paramset]     dict set dRes gost3411Paramset $gost3411Paramset   }   if {[dict exists $dKey keyValue]} {     set keyValue [dict get $dKey keyValue]     dict set dRes keyValue $keyValue   }      return $dRes }  proc ::GostPfx::pfxCreateSingleCertKey {password certificate keyValue keyAlg gost3410Paramset gost3411Paramset friendlyName localKeyID} {   variable oid_Gost3411_2012_512    variable oid_gost28147_89    variable oid_tc26_gost_28147_89_param_A    variable oid_tc26_hmac_gost_3411_2012_512   variable oid_pkcs5PBES2    variable oid_pBKDF2    variable oid_friendlyName    variable oid_localKeyID    variable oid_pkcs8ShroudedKeyBag    variable oid_pkcs7_data    variable oid_pkcs7_encrypted_data   variable oid_PKCS12CertificateBag    variable oid_PKCS9x509Certificate      set pfx ""   set ctx [lrnd_random_ctx_create ""]    # create identityData   set friendlyNameData [asn::asnSequence [asn::asnObjectIdentifier $oid_friendlyName] [asn::asnSet [asn::asnBMPString $friendlyName]]]   set localKeyIDData [asn::asnSequence [asn::asnObjectIdentifier $oid_localKeyID] [asn::asnSet [asn::asnOctetString $localKeyID]]]   set identityData [asn::asnSet $friendlyNameData $localKeyIDData]      # create private key data   set oid3410Paramset [asn::asnObjectIdentifier $gost3410Paramset]   set oid3411Paramset [asn::asnObjectIdentifier $gost3411Paramset]   set paramsetSequence [asn::asnSequence $oid3410Paramset $oid3411Paramset]   set oidKeyAlg [asn::asnObjectIdentifier $keyAlg]   set keyAlgSequence [asn::asnSequence $oidKeyAlg $paramsetSequence]   set keyOctets [asn::asnOctetString [asn::asnOctetString $keyValue]]   set version [asn::asnInteger 0]   set keyData [asn::asnSequence $version $keyAlgSequence $keyOctets]      # create private key cipherKey   set keySalt [lrnd_random_ctx_get_bytes $ctx 32]   set cipherKeyIter 2048   set hmacAlg $oid_tc26_hmac_gost_3411_2012_512   set keyCipherKey [pfxPbeKey $password $hmacAlg $keySalt $cipherKeyIter]   # create cipher algorithm and params   set cipherAlg $oid_gost28147_89   set cipherParamset $oid_tc26_gost_28147_89_param_A   set keyCipherIV [lrnd_random_ctx_get_bytes $ctx 8]   # encrypt private key data   set encrKey [pfxDataEncrypt $keyData $cipherAlg $cipherParamset $keyCipherKey $keyCipherIV]    # create private key PbeData   set keyCipherParams [asn::asnSequence [asn::asnOctetString $keyCipherIV] [asn::asnObjectIdentifier $cipherParamset]]   set keyCipherData [asn::asnSequence [asn::asnObjectIdentifier $cipherAlg] $keyCipherParams]   set hmacAlgData [asn::asnSequence [asn::asnObjectIdentifier $oid_tc26_hmac_gost_3411_2012_512] [asn::asnNull]]   set keyHmacData [asn::asnSequence [asn::asnOctetString $keySalt] [asn::asnInteger $cipherKeyIter] $hmacAlgData]   set keyPbkdf2Data [asn::asnSequence [asn::asnObjectIdentifier $oid_pBKDF2] $keyHmacData]   set keyPbeData [asn::asnSequence [asn::asnObjectIdentifier $oid_pkcs5PBES2] [asn::asnSequence $keyPbkdf2Data $keyCipherData]]      # create keyBag   set keyBagContext [asn::asnContextConstr 0 [asn::asnSequence $keyPbeData [asn::asnOctetString $encrKey]]]   set keyBag [asn::asnSequence [asn::asnObjectIdentifier $oid_pkcs8ShroudedKeyBag] $keyBagContext $identityData]   # create keyBags   set keyBags [asn::asnOctetString [asn::asnSequence $keyBag]]   set keysData [asn::asnSequence [asn::asnObjectIdentifier $oid_pkcs7_data] [asn::asnContextConstr 0 $keyBags]]      # create certificate cipherKey   set certSalt [lrnd_random_ctx_get_bytes $ctx 32]   set certCipherKey [pfxPbeKey $password $hmacAlg $certSalt $cipherKeyIter]   # create cipher algorithm and params   set certCipherIV [lrnd_random_ctx_get_bytes $ctx 8]   # create certPbeData   set certCipherParams [asn::asnSequence [asn::asnOctetString $certCipherIV] [asn::asnObjectIdentifier $cipherParamset]]   set certCipherData [asn::asnSequence [asn::asnObjectIdentifier $cipherAlg] $certCipherParams]   set certHmacData [asn::asnSequence [asn::asnOctetString $certSalt] [asn::asnInteger $cipherKeyIter] $hmacAlgData]   set certPbkdf2Data [asn::asnSequence [asn::asnObjectIdentifier $oid_pBKDF2] $certHmacData]   set certPbeData [asn::asnSequence [asn::asnObjectIdentifier $oid_pkcs5PBES2] [asn::asnSequence $certPbkdf2Data $certCipherData]]   # create certData   set certData [asn::asnSequence [asn::asnObjectIdentifier $oid_PKCS9x509Certificate] [asn::asnContextConstr 0 [asn::asnOctetString $certificate]]]   set certBag [asn::asnSequence [asn::asnObjectIdentifier $oid_PKCS12CertificateBag] [asn::asnContextConstr 0 $certData] $identityData]   # create certBags   set certBags [asn::asnSequence $certBag]   # encrypt certBags   set encrCertBags [pfxDataEncrypt $certBags $cipherAlg $cipherParamset $certCipherKey $certCipherIV]     # create certsInfo (non-constructed context here!)    set certsContent [asn::asnSequence [asn::asnObjectIdentifier $oid_pkcs7_data] $certPbeData [asn::asnContext 0 $encrCertBags]]   set certsEncrData [asn::asnSequence [asn::asnInteger 0] $certsContent]   set certsData [asn::asnSequence [asn::asnObjectIdentifier $oid_pkcs7_encrypted_data] [asn::asnContextConstr 0 $certsEncrData]]   # create tbs   set tbs [asn::asnSequence $certsData $keysData]   # create hmacKey   set hmacKeySalt [lrnd_random_ctx_get_bytes $ctx 32]   set hmacPbaKey [string range [lcc_gost3411_2012_pkcs5 $password $hmacKeySalt $cipherKeyIter 96] 64 95]     # create hmac   set hmacPba [lcc_gost3411_2012_hmac 64 $tbs $hmacPbaKey]   # create macData   set digestParams [asn::asnSequence [asn::asnObjectIdentifier $oid_Gost3411_2012_512] [asn::asnNull]]   set hmacData [asn::asnSequence $digestParams [asn::asnOctetString $hmacPba]]   set macData [asn::asnSequence $hmacData [asn::asnOctetString $hmacKeySalt] [asn::asnInteger $cipherKeyIter]]   # create pfx   set pfxData [asn::asnSequence [asn::asnObjectIdentifier $oid_pkcs7_data] [asn::asnContextConstr 0 [asn::asnOctetString $tbs]]]   set pfx [asn::asnSequence [asn::asnInteger 3] $pfxData $macData]    lrnd_random_ctx_delete $ctx   return $pfx } proc ::GostPfx::pfxCreateLocalKeyID {keyAlg gost3410Paramset privKeyValue} {   variable oid_GostR3410_2001   variable oid_GostR3411_94_with_GostR3410_2001   variable oid_tc26_gost3410_2012_256   variable oid_tc26_signwithdigest_gost3410_2012_256   variable oid_tc26_gost3410_2012_512   variable oid_tc26_signwithdigest_gost3410_2012_512      set localKeyID ""     if {($keyAlg == $oid_GostR3410_2001) || ($keyAlg == $oid_GostR3411_94_with_GostR3410_2001) || \   ($keyAlg == $oid_tc26_gost3410_2012_256) || ($keyAlg == $oid_tc26_signwithdigest_gost3410_2012_256)} {     set par_id [string map {" " "."} $gost3410Paramset]     set group [lcc_gost3410_2012_256_getGroupByOid $par_id]     if {$group > 0} {       set pubKey [lcc_gost3410_2012_256_createPublicKey $group $privKeyValue]     } else {       error "pfxCreateLocalKeyID: Unsupported paramset $gost3410Paramset"     }   } else {     if {($keyAlg == $oid_tc26_gost3410_2012_512) || ($keyAlg == $oid_tc26_signwithdigest_gost3410_2012_512)} {       set par_id [string map { } {.} $gost3410Paramset]       set group [lcc_gost3410_2012_512_getGroupByOid $par_id]       if {$group > 0} {         set pubKey [lcc_gost3410_2012_512_createPublicKey $group $privKeyValue]       } else {         error "pfxCreateLocalKeyID: Unsupported paramset $gost3410Paramset"       }     } else {       error "pfxCreateLocalKeyID: Unsupported algorithm: $keyAlg"     }   }   set octets [asn::asnOctetString $pubKey]   set localKeyID [lcc_sha1 $octets]       return $localKeyID } package provide GostPfx 1.0.0

Итак, мы запустили утилиту crytoarmpkcs, загрузили контейнер PKCS#12 с сертификатом всесильного Хабра, полученным ранее, и просматриваем сертификат:

Теперь можно подписывать документы не задействуя никакого стороннего СКЗИ, главное, чтобы была связь с интернетом, для получения цепочки сертификатов, штампов времени, списков отозванных сертификатов, ответов OCSP.
Процесс подписания ничем не отличается от описанного ранее:

Полученную подпись также можно смело проверять на сайте Госуслуг или другом сервисе.
Как уже отмечалось, сертификат из контейнера можно экспортировать как в файл, так и на токен. При экспорте сертификата на токен будет проверена его подпись:

Также можно импортировать закрытый ключ на токен:

Надо иметь ввиду, что не все токены позволяют импортировать на них закрытые ключи. С нашей точки зрения, это функция прежде всего необходима при хранении личного сертификата в облачном токене PKCS#11.
А о том, как работать с токенами PKCS#11, было рассказано в предыдущей статье.
И осталось самая малость (у нас есть еще кнопка «Резерв»), — это шифрование документов на сертификате получателя (на открытом ключе). Но об этом расскажем в следующий раз.


ссылка на оригинал статьи https://habr.com/ru/post/460205/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *