
Итак, для работы с контейнерами 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, но и поддержка алгоритмов шифрования Кузнечик и Магма.

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, было рассказано в предыдущей статье.
И осталось самая малость (у нас есть еще кнопка «Резерв»), — это шифрование документов на сертификате получателя (на открытом ключе). Но об этом расскажем в следующий раз.