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

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

Информационная безопасность *Криптография *Графические оболочки *Разработка под Linux *Разработка под Windows *
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, было рассказано в предыдущей статье.
И осталось самая малость (у нас есть еще кнопка «Резерв»), — это шифрование документов на сертификате получателя (на открытом ключе). Но об этом расскажем в следующий раз.
Теги:
Хабы:
Всего голосов 7: ↑6 и ↓1 +5
Просмотры 3.9K
Комментарии Комментарии 6