From 5ce934287f609aeaefa73ce5b9fb328f08124444 Mon Sep 17 00:00:00 2001 From: alashkova Date: Thu, 14 Nov 2024 17:59:16 +0300 Subject: [PATCH] =?UTF-8?q?SUPPORT-8703.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=86=D0=B5=D0=BF=D0=BE=D1=87=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=81=D0=B5=D1=80=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/capi.c | 3 + src/utils/capi.h | 28 ++++++++ src/utils/cryptopro.c | 129 +++++++++++++++++++++++++++++++++++++ Инструкция по установке.md | 25 ++++++- 4 files changed, 183 insertions(+), 2 deletions(-) diff --git a/src/utils/capi.c b/src/utils/capi.c index 8cba246..c6458e7 100644 --- a/src/utils/capi.c +++ b/src/utils/capi.c @@ -28,6 +28,9 @@ capi_function_list_init(library_t *lib, capi_function_list_t *fl) LIBRARY_RESOLVE(fl->GetLastError, lib, "GetLastError"); LIBRARY_RESOLVE(fl->CryptImportPublicKeyInfo, lib, "CryptImportPublicKeyInfo"); LIBRARY_RESOLVE(fl->CryptDestroyKey, lib, "CryptDestroyKey"); + LIBRARY_RESOLVE(fl->CertGetCertificateChain, lib, "CertGetCertificateChain"); + LIBRARY_RESOLVE(fl->CertVerifyCertificateChainPolicy, lib, "CertVerifyCertificateChainPolicy"); + LIBRARY_RESOLVE(fl->CertFreeCertificateChain, lib, "CertFreeCertificateChain"); #ifdef UNICODE LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashW"); diff --git a/src/utils/capi.h b/src/utils/capi.h index 555ffb9..71fe5c4 100644 --- a/src/utils/capi.h +++ b/src/utils/capi.h @@ -175,6 +175,31 @@ DECLARE_FN(WINADVAPI, CRYPT_DESTROY_KEY, (HCRYPTKEY hKey)); +DECLARE_FN(WINADVAPI, + BOOL, + CERT_GET_CERTIFICATE_CHAIN, + (HCERTCHAINENGINE hChainEngine, + PCCERT_CONTEXT pCertContext, + LPFILETIME pTime, + HCERTSTORE hAdditionalStore, + PCERT_CHAIN_PARA pChainPara, + DWORD dwFlags, + LPVOID pvReserved, + PCCERT_CHAIN_CONTEXT* ppChainContext)); + +DECLARE_FN(WINADVAPI, + BOOL, + CERT_VERIFY_CERTIFICATE_CHAIN_POLICY, + (LPCSTR pszPolicyOID, + PCCERT_CHAIN_CONTEXT pChainContext, + PCERT_CHAIN_POLICY_PARA pPolicyPara, + PCERT_CHAIN_POLICY_STATUS pPolicyStatus)); + +DECLARE_FN(WINADVAPI, + VOID, + CERT_FREE_CERTIFICATE_CHAIN, + (PCCERT_CHAIN_CONTEXT pChainContext)); + #ifdef UNICODE #define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_W_FN #define CRYPT_VERIFY_SIGNATURE_FN CRYPT_VERIFY_SIGNATURE_W_FN @@ -205,6 +230,9 @@ typedef struct { CRYPT_ACQUIRE_CONTEXT_FN CryptAcquireContext; CRYPT_IMPORT_PUBLIC_KEY_INFO_FN CryptImportPublicKeyInfo; CRYPT_DESTROY_KEY_FN CryptDestroyKey; + CERT_GET_CERTIFICATE_CHAIN_FN CertGetCertificateChain; + CERT_VERIFY_CERTIFICATE_CHAIN_POLICY_FN CertVerifyCertificateChainPolicy; + CERT_FREE_CERTIFICATE_CHAIN_FN CertFreeCertificateChain; } capi_function_list_t; diff --git a/src/utils/cryptopro.c b/src/utils/cryptopro.c index b93b91f..11f667d 100644 --- a/src/utils/cryptopro.c +++ b/src/utils/cryptopro.c @@ -79,6 +79,127 @@ error: return -1; } +static PCCERT_CHAIN_CONTEXT +get_cert_chain(PCCERT_CONTEXT certificate) +{ + LOG_TRACE("get_cert_chain enter"); + + CERT_CHAIN_PARA chain_para = {0}; + chain_para.cbSize = sizeof(CERT_CHAIN_PARA); + + PCCERT_CHAIN_CONTEXT chain_ctx = NULL; + + + if (!cp_function_list.CertGetCertificateChain(NULL, + certificate, + NULL, + NULL, + &chain_para, + (CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT), + NULL, + &chain_ctx)) { + LOG_ERROR("CertGetCertificateChain() failed"); + LOG_ERROR("get_cert_chain exit with error"); + return NULL; + } + + LOG_TRACE("get_cert_chain exit"); + return chain_ctx; +} + +static const char* +get_cert_chain_policy_status_error_desc(DWORD err) +{ + // TODO-8703 + switch(err) { + case CERT_E_UNTRUSTEDROOT: + return "CERT_E_UNTRUSTEDROOT"; + case CERT_E_CHAINING: + return "CERT_E_CHAINING"; + default: + break; + } + return "Unknown error"; +} + +static bool +check_cert_chain_policy(PCCERT_CHAIN_CONTEXT chain_ctx) +{ + LOG_TRACE("check_cert_chain_policy enter"); + + CERT_CHAIN_POLICY_PARA policy_para = {0}; + policy_para.cbSize = sizeof(policy_para); + + CERT_CHAIN_POLICY_STATUS status = {0}; + status.cbSize = sizeof(status); + + CPSIGNATURE_EXTRA_CERT_CHAIN_POLICY_STATUS extraStatus = {0}; + extraStatus.cbSize = sizeof(extraStatus); + + status.pvExtraPolicyStatus = &extraStatus; + + if (!cp_function_list.CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_BASE, + // TODO-8703: CPCERT_CHAIN_POLICY_SIGNATURE + chain_ctx, + &policy_para, + &status)) { + LOG_ERROR("CertVerifyCertificateChainPolicy() failed"); + LOG_ERROR("check_cert_chain_policy exit with error"); + return false; + } + + if (status.dwError != 0) { + LOG_WARN("The certificate chain cannot be validated. " + "CertVerifyCertificateChainPolicy status: %s('0x%08x')", + get_cert_chain_policy_status_error_desc(status.dwError), status.dwError); + LOG_TRACE("check_cert_chain_policy exit"); + return false; + } + + LOG_TRACE("check_cert_chain_policy exit"); + return true; +} + +static void +free_cert_chain(PCCERT_CHAIN_CONTEXT chain_ctx) +{ + LOG_TRACE("free_cert_chain enter"); + + if (chain_ctx) { + cp_function_list.CertFreeCertificateChain(chain_ctx); + } + + LOG_TRACE("free_cert_chain exit"); +} + +static bool +verify_cert_chain(PCCERT_CONTEXT certificate) +{ + bool is_verified = false; + + LOG_TRACE("verify_cert_chain enter"); + + PCCERT_CHAIN_CONTEXT chain_ctx = get_cert_chain(certificate); + if (chain_ctx == NULL) { + goto exit; + } + + if (!check_cert_chain_policy(chain_ctx)) { + goto exit; + } + + is_verified = true; + +exit: + free_cert_chain(chain_ctx); + + LOG_DEBUG("cert_chain: %s", is_verified ? "verified" : "failed"); + + LOG_DEBUG("verify_cert_chain exit"); + return is_verified; +} + static int sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign) { @@ -101,6 +222,10 @@ sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t goto exit; } + if (!verify_cert_chain(pSignerCert)) { + goto exit; + } + if (!cp_function_list.CryptAcquireCertificatePrivateKey( pSignerCert, CRYPT_ACQUIRE_SILENT_FLAG, @@ -425,6 +550,10 @@ cryptopro_verify(const str_t* cert_thumbprint, const str_t* alg, const str_t* da goto exit; } + if (!verify_cert_chain(certificate)) { + goto exit; + } + if (!cp_function_list.CryptAcquireContext(&hCryptProv, NULL, CP_KC1_GR3410_2001_PROV, PROV_GOST_2001_DH, CRYPT_VERIFYCONTEXT)) { LOG_ERROR("CryptAcquireContext() failed"); diff --git a/Инструкция по установке.md b/Инструкция по установке.md index 2f1391d..a090705 100644 --- a/Инструкция по установке.md +++ b/Инструкция по установке.md @@ -58,11 +58,32 @@ chown -R ervu:ervu /var/opt/cprocsp/keys/ervu/key_cont.000/ Получить следующие значения: - Поле "SHA1 отпечаток": это значение нужно будет указать в конфигурационном файле модуля подписания в качестве настройки \ - Поле "Ссылка на ключ": убедиться, что значение равно "Есть" (иначе необходимо проверить выполнение предыдущих шагов) +- Поля "URL сертификата УЦ": по ссылкам, указанным в этом значении, скачать сертификаты УЦ и установить их (описано далее) +- Поля "URL списка отзыва": по ссылкам, указанным в этом значении, скачать CRL и установить их (описано далее) -4. (опционально) Сделать тестовую подпись с помощью установленного контейнера и полученных значений \ и \: +4. Установить сертификаты УЦ (поля "URL сертификата УЦ" в свойствах сертификата). Для каждого сертификата выполнить команду от имени **root**: +``` bash +/opt/cprocsp/bin/amd64/certmgr -install -store uRoot -file .cer +``` + +5. Установить CRL (поле "URL списка отзыва" в свойствах сертификата). Для каждого CRL выполнить команду от имени **ervu**: +``` bash +/opt/cprocsp/bin/amd64/certmgr -install -store uCA -crl -file .crl +``` + +6. Проверить цепочку сертификатов (команда выполняется от имени **ervu**): +``` bash +/opt/cprocsp/bin/amd64/certmgr -list -thumbprint -chain +``` + +Результатом команды должна быть строка "Цепочка сертификатов: Успешно проверена." + +Подробнее о проверке цепочки сертификатов здесь: https://www.altlinux.org/%D0%9A%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%9F%D1%80%D0%BE#%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0_%D1%86%D0%B5%D0%BF%D0%BE%D1%87%D0%BA%D0%B8_%D1%81%D0%B5%D1%80%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D0%BE%D0%B2 + +7. (опционально) Сделать тестовую подпись с помощью установленного контейнера и полученных значений \ и \: ``` bash touch test.txt -/opt/cprocsp/bin/amd64/cryptcp -signf -thumbprint ./test.txt +/opt/cprocsp/bin/amd64/cryptcp -signf -thumbprint -errchain ./test.txt rm -f test.txt test.txt.sgn ```