А достаточно ли штампа?
Простая проштамповка подписи сразу после ее формирования – необходимое, но недостаточное условие того, чтобы доказать ее действительность через длительное время. При проверке нужно будет убедиться, что в зафиксированный момент подписания сертификат был не отозван.
На эти и другие вопросы я отвечу во второй части статьи, где разберу на составляющие электронную подпись и различные ее форматы.
См. также Обзор форматов стандарта CAdES (pdf)
Действительность подписи
В соответствии с 63-ФЗ (Статья 11) документ с УКЭП обладает юридической силой, если:
● квалифицированный сертификат был действителен на момент подписания либо в момент проверки подписи;
● сертификат соответствует сформированной подписи;
● в подписанный документ не вносили изменения.
С технической точки зрения, с помощью открытого ключа проверяется соответствие значения ЭП подписанным данным (соответствие предоставленного открытого ключа закрытому, с помощью которого производились «криптографические преобразования»). Также устанавливается действительность сертификата либо на день проверки, либо на момент подписания документа. Если срок действия квалифицированного сертификата истек, появляется необходимость достоверно установить время подписания.
Криптографический арм на базе контейнера pkcs#12. создание электронной подписи cades-x long type 1. часть 3
Прошло время и утилита, начатая как
просмотрщик сертификатов
, дополненная функциями работы с криптографическими
токенами PKCS#11
и создания запросов (PKCS#10) на квалифицированный сертификат, пополнилась, как и было заявлено, функциями работы с контейнерами PKCS#12.
Загружаем, запускаем утилиту cryptoarmpkcs и нажимаем кнопку «PKCS12»:
Скриншот наглядно демонстрирует, что позволяет делать утилита, имея на руках контейнер PKCS#12:
- просмотреть сертификат владельца, для чего достаточно будет кликнуть на иконку, находящуюся справа от поля «friendlyName»;
- сформировать одну из трех типов электроноой подписи CAdes:
- сохранить сертификат из контейнера в файле;
- сохранить сертификат на токене;
- сохранить закрытый ключ владельца на токене.
Две последние операции возможны только при подключенном токене (выбрана библиотека PKCS#11 и подключен токен). Отметим также, что не все токены позволяют импортировать закрытые ключи. Но эта операция важна, если мы хотим работать
с облачными токенами PKCS#11
.
В отличие от криптографического токена PKCS#11, защищенный контейнер PKCS#12 не является криптографической машиной, т.е. он только хранит в защищенном виде (зашифрованном на пароле) сертификат и закрытый ключ к нему и не выполняет никаких криптографических операций. Требования к защищенному контейнеру на базе российской криптографии сформулированы ТК-26 в документе «Р 50.1.112-2021. Транспортный ключевой контейнер.», который утвержден и введен в действие Приказом Федерального агентства по техническому регулированию и метрологии от 23 ноября 2021 г. No 1753-ст.
Как получить контейнер PKCS#12 из криптопровайдера MS CSP хорошо описано в одной из статей на Хабр.
Для работы с PKCS#12 пришлось разработать два новых пакета tcl, помимо ранее созданного TclPKCS11. Первый пакет Lcc, для поддержки российской криптографии с учетом рекомендаций ТК-26.
// Digest commands
// gost3411_2021
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021", gost3411_2021_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_ctx_create", gost3411_2021_ctx_create_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_ctx_update", gost3411_2021_ctx_update_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_ctx_final", gost3411_2021_ctx_final_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_ctx_delete", gost3411_2021_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_2021_hmac", gost3411_2021_hmac_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_hmac_ctx_create", gost3411_2021_hmac_ctx_create_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_hmac_ctx_update", gost3411_2021_hmac_ctx_update_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_hmac_ctx_final", gost3411_2021_hmac_ctx_final_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_hmac_ctx_delete", gost3411_2021_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_2021_pkcs5", gost3411_2021_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_2021 KDF
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_256_kdf", gost3411_2021_256_kdf_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_256_kdf_tree", gost3411_2021_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_2021_256_prf_tls", gost3411_2021_256_prf_tls_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3411_2021_512_prf_tls", gost3411_2021_512_prf_tls_Cmd, NULL, NULL);
// gost3410-2021 commands
// 256 bits
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_256_getGroupByOid", gost3410_2021_256_getGroupByOid_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_256_getGroupByDerOid", gost3410_2021_256_getGroupByDerOid_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_256_getGroupById", gost3410_2021_256_getGroupById_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_256_createPrivateKey", gost3410_2021_256_createPrivateKey_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_256_createPublicKey", gost3410_2021_256_createPublicKey_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_256_sign", gost3410_2021_256_sign_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_256_verify", gost3410_2021_256_verify_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_256_vko", gost3410_2021_256_vko_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_256_keg", gost3410_2021_256_keg_Cmd, NULL, NULL);
// 512 bits
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_512_getGroupByOid", gost3410_2021_512_getGroupByOid_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_512_getGroupByDerOid", gost3410_2021_512_getGroupByDerOid_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_512_getGroupById", gost3410_2021_512_getGroupById_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_512_createPrivateKey", gost3410_2021_512_createPrivateKey_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_512_createPublicKey", gost3410_2021_512_createPublicKey_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_512_sign", gost3410_2021_512_sign_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_512_verify", gost3410_2021_512_verify_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_512_vko", gost3410_2021_512_vko_Cmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "lcc_gost3410_2021_512_keg", gost3410_2021_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-2021 и ГОСТ Р 34.11-2021, но и поддержка алгоритмов шифрования Кузнечик и Магма.
Второй пакет 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-и неопределенной длины.
Итак, мы запустили утилиту crytoarmpkcs, загрузили контейнер PKCS#12 с сертификатом всесильного Хабра,
полученным ранее
, и просматриваем сертификат:
Теперь можно подписывать документы не задействуя никакого стороннего СКЗИ, главное, чтобы была связь с интернетом, для получения цепочки сертификатов, штампов времени, списков отозванных сертификатов, ответов OCSP.
Процесс подписания ничем не отличается от описанного ранее:
Полученную подпись также можно смело проверять на сайте Госуслуг или другом сервисе.
Как уже отмечалось, сертификат из контейнера можно экспортировать как в файл, так и на токен. При экспорте сертификата на токен будет проверена его подпись:
Также можно импортировать закрытый ключ на токен:
Надо иметь ввиду, что не все токены позволяют импортировать на них закрытые ключи. С нашей точки зрения, это функция прежде всего необходима при хранении личного сертификата в облачном токене PKCS#11.
А о том, как работать с токенами PKCS#11, было рассказано в предыдущей статье.
И осталось самая малость (у нас есть еще кнопка «Резерв»), — это шифрование документов на сертификате получателя (на открытом ключе). Но об этом расскажем в следующий раз.
Сертификаты
Мы уже выяснили, что квалифицированные сертификаты выдают удостоверяющие центры, аккредитованные головным УЦ (Минкомсвязь России). Квалифицированный сертификат хранит открытые сведения: данные владельца закрытого ключа, открытый ключ (создается в пару к закрытому), срок действия сертификата, уникальный номер, ограничения по его использованию и др.
Эту информацию подписывает удостоверяющий центр, которому в свою очередь тоже был выдан квалифицированный сертификат, подписанный другим вышестоящим УЦ. Таким образом, формируется цепочка сертификатов, приводящая к головному УЦ. Он априори является доверенной стороной для всех остальных участников процесса.
В ряде случаев удостоверяющий центр может отозвать выпущенный ранее сертификат до завершения его срока действия. Такое может произойти при компрометации закрытого ключа пользователя. Информацию по отзыву конкретного сертификата можно узнать, отправив запрос и получив OCSP-ответ со статусом действительности данного конкретного экземпляра или запросив от УЦ список отозванных сертификатов за все время его существования.
Служба штампов времени
Вернемся к проблеме того, как определить время подписания документа. Если ЭП проверяют после истечения срока действия сертификата, то необходимо установить момент подписания. Технически возможно указать его в структуре самой подписи, но доверять этому значению мы не можем, так как оно формируется на стороне подписанта.
Штампом времени называют подписанный ответ TSA с фиксацией момента, в котором существовала штампуемая информация. Данные, а строго говоря их хэш, отправляются запросом по протоколу TSP (Time stamp protocol). Служба штампов формирует ответ, в котором в определенном формате фиксируется точное время существования полученного в запросе хэша.
После формирования подписи, чтобы зафиксировать момент ее создания, устанавливается штамп времени. Если в будущем сертификат подписанта истечет или закрытый ключ будет скомпрометирован, документ не потеряет юридическую значимость (при условии, что в указанное время сертификат подписанта был действителен).
Цифровая подпись cades-bes для xml средствами 1с с помощью криптопро
Для работы с ГИС ЖКХ из 1С через API понадобилось выполнять цифровую подпись. Поначалу использовался OpenSSL Python вот отсюда. Там сама подпись и контрольные суммы генерируются с помощью OpenSSL, а скрипт вставляет в исходный XML необходимые узлы.
Во-первых, все это работает через консоль, что не очень быстро. Во-вторых, OpenSSL принимает закрытый ключ в открытом PEM формате, что само по себе безобразие. В-третьих, с переходом на ГОСТ 2021 все это стало невозможно, поскольку инструментов извлечения закрытого ключа в формате PEM из сертификата, выполненного по ГОСТ 2021 не нашлось.
Штатный менеджер криптографии 1С помочь не может, так как подписывать XML не умеет, может только вернуть подпись для потока данных в формате CMS (базируется на PKCS#7), а необходима только сигнатура, длиной в 64 байта.
В итоге пришлось написать обработку, которая выполняет то же самое, что выполнял скрипт на Python.
Обработка подписывает стандартный SOAP-конверт <envelope>, для примера в ней есть тестовый запрос. Разумеется, для подписи в системе должен стоять КриптоПро, к нему должен быть подцеплен сертификат. Пробную версию КриптоПро можно скачать и установить бесплатно на официальном сайте. Тестовый сертификат можно выпустить там же, в тестовом удостоверяющем центре.
Для получения сигнатуры и хеша используется COM-объект “CAdESCOM”, поставляемый вместе с КриптоПро. Менеджер криптографии 1С используется для получения данных сертификата – издателя и открытого ключа. Можно было бы обойтись без него, но с ним удобнее. Кроме того, используется COM-объект “System.Text.UTF8Encoding” для конвертации строки в массив байт COMSafeArray в кодировке UTF8. К сожалению, на выходе у XML полностью теряется форматирование. Избежать этого не получилось, так как оно уничтожается при каноникализации средствами 1С. Обратно возвращать форматирование уже нельзя, подпись становится невалидной.
Методика не привязана к конкретному прикладному решению и работает как на сервере, так и на клиенте. Тестировалось на релизе 8.3.13.1513. На более ранних будет работать вплоть до релиза, где есть МенеджерКриптографии, ДокументDOM, ДвоичныеДанные, ПреобразованиеККаноническомуXML и функции для работы с Base64.
P.S. Буду очень благодарен, если кто-нибудь сможет ответить на пару вопросов:
1. Так как используется COM, работать все это будет только на Windows. Если конвертацию в UTF8 можно переписать на 1С, то аналога CAdESCOM в Linux нет и не будет. Может, кто знает, как можно получить хеш и сигнатуру из КриптоПро в консоли на Linux.
2. Элемент подписи создается с помощью ДокументDOM фактически вручную, то есть поэтапным созданием каждого узла. Дописать в ДокументDOM кусок получилось из объекта ФабрикаXDTO, но началась путаница с пространствами имен и каноникализацией. Возможно, все-таки перепишу с использованием фабрики и макета. А вот вставить в один ДокументDOM кусок другого ДокументDOM, созданного из макета, у меня не получилось, хотя в СП такая возможность вроде как декларируется. Если кто подскажет, как – скажу спасибо.
P.P.S. Не на тему ЭЦП, но около. Если кто-нибудь озадачится вопросом создания туннеля по стандарту ГОСТ 2021, как этого требует ГИС ЖКХ и не только он, при наличии КриптоПро с этим отлично справляется их форк STunnel – stunnel-msspi. В него уже встроены необходимые алгоритмы, при этом нет необходимости вытаскивать ключ из КриптоПро и хранить его в открытом виде, как требуется в случае использования оригинального STunnel. Достаточно только указать открытый ключ сертификата или имя хранилища в КриптоПро.
Электронная подпись: простая и усиленная
Обратимся к Федеральному закону от 06.04.2021 № 63-ФЗ «Об электронной подписи» (далее – 63-ФЗ). Исходя из него, ЭП – это сущность, которая:
● является информацией;
● связана с подписываемой информацией;
● позволяет определить подписанта.
Признаки усиленной квалифицированной электронной подписи (далее – УКЭП):
● является информацией;
● связана с подписываемой информацией;
● предоставляет возможность определить подписанта;
● получена с помощью ключа ЭП (закрытого) в результате криптографического преобразования информации;
● позволяет установить факт внесения изменений в подписываемые данные;
● ключ проверки ЭП (открытый) указан в квалифицированном сертификате;
● для создания и проверки подписи используются сертифицированные средства ЭП.
Всё вышеперечисленное накладывает дополнительные ограничения, в том числе и на техническую часть. В процессе работы с электронной подписью должны присутствовать открытый и закрытый ключи, квалицированный сертификат, средства создания и проверки ЭП.
Удостоверяющие центры (далее – УЦ), аккредитованные Минкомсвязью России, по запросам пользователей выпускают для них сертификаты. Требования к форме последних, средствам УЦ и средствам электронной подписи устанавливает ФСБ России (Приказ ФСБ РФ от 27 декабря 2021 г.
№ 795 «Об утверждении Требований к форме квалифицированного сертификата ключа проверки электронной подписи»; Приказ ФСБ РФ от 27 декабря 2021 г. № 796 «Об утверждении Требований к средствам электронной подписи и Требований к средствам удостоверяющего центра»). Примером сертифицированных ФСБ средств ЭП служит средство криптографической защиты «КриптоПро CSP».