diff --git a/CMakeLists.txt b/CMakeLists.txt index 57982a4..f5da34b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ CMAKE_MINIMUM_REQUIRED (VERSION 3.0) SET (CMAKE_C_COMPILER "gcc") -PROJECT (ervu-sign-module VERSION 1.4.1 LANGUAGES C) +PROJECT (ervu-sign-module VERSION 1.4.2 LANGUAGES C) IF (CMAKE_VERBOSE) SET (CMAKE_VERBOSE_MAKEFILE 1) diff --git a/README.md b/README.md index af1c4ab..8d91462 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ $ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/msg/verify_detached \ | `CERT_TRUST_REVOCATION_STATUS_UNKNOWN` | Неизвестен статус отзыва сертификата или одного из сертификатов в цепочке (проблема с CRL) | | `CERT_TRUST_IS_UNTRUSTED_ROOT` | Сертификат или цепочка сертификатов основана на ненадежном корневом сертификате | | `CERT_TRUST_IS_NOT_TIME_VALID` | Этот сертификат или один из сертификатов в цепочке сертификатов является недопустимым по времени | +| `CERT_TRUST_IS_PARTIAL_CHAIN` | Цепочка сертификатов не полная | ## Сборка из исходников diff --git a/src/utils/capi.c b/src/utils/capi.c index aa0c7e6..a351229 100644 --- a/src/utils/capi.c +++ b/src/utils/capi.c @@ -37,6 +37,7 @@ capi_function_list_init(library_t *lib, capi_function_list_t *fl) LIBRARY_RESOLVE(fl->CertFindExtension, lib, "CertFindExtension"); LIBRARY_RESOLVE(fl->CryptDecodeObjectEx, lib, "CryptDecodeObjectEx"); LIBRARY_RESOLVE(fl->LocalFree, lib, "LocalFree"); + LIBRARY_RESOLVE(fl->FileTimeToSystemTime, lib, "FileTimeToSystemTime"); #ifdef UNICODE LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashW"); diff --git a/src/utils/capi.h b/src/utils/capi.h index d0012df..56b33d1 100644 --- a/src/utils/capi.h +++ b/src/utils/capi.h @@ -266,6 +266,12 @@ DECLARE_FN(WINADVAPI, LOCAL_FREE, (HLOCAL hMem)); +DECLARE_FN(WINADVAPI, + BOOL, + FILE_TIME_TO_SYSTEM_TIME, + (CONST FILETIME *lpFileTime, + LPSYSTEMTIME lpSystemTime)); + #ifdef UNICODE #define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_W_FN #define CRYPT_VERIFY_SIGNATURE_FN CRYPT_VERIFY_SIGNATURE_W_FN @@ -308,6 +314,7 @@ typedef struct { CERT_FIND_EXTENSION_FN CertFindExtension; CRYPT_DECODE_OBJECT_EX_FN CryptDecodeObjectEx; LOCAL_FREE_FN LocalFree; + FILE_TIME_TO_SYSTEM_TIME_FN FileTimeToSystemTime; } capi_function_list_t; diff --git a/src/utils/cryptopro.c b/src/utils/cryptopro.c index eafe541..34d1f2f 100644 --- a/src/utils/cryptopro.c +++ b/src/utils/cryptopro.c @@ -5,9 +5,12 @@ #include "library.h" #include "logger.h" +#define SHA1_SIZE 20 + static const char* CERT_TRUST_IS_UNTRUSTED_ROOT_ERR_CODE = "CERT_TRUST_IS_UNTRUSTED_ROOT"; static const char* CERT_TRUST_REVOCATION_STATUS_UNKNOWN_ERR_CODE = "CERT_TRUST_REVOCATION_STATUS_UNKNOWN"; static const char* CERT_TRUST_IS_NOT_TIME_VALID_ERR_CODE = "CERT_TRUST_IS_NOT_TIME_VALID"; +static const char* CERT_TRUST_IS_PARTIAL_CHAIN_ERR_CODE = "CERT_TRUST_IS_PARTIAL_CHAIN"; static capi_function_list_t cp_function_list; static library_t libcapi; @@ -24,6 +27,10 @@ static PCCERT_CONTEXT get_signer_cert(const cryptopro_context_t *ctx, HCERTSTORE static int set_password(PCCERT_CONTEXT cert_ctx, const str_t *password); +static bool get_subject_name(PCCERT_CONTEXT cert, /*out*/ char **subject_name); + +static bool get_issuer_name(PCCERT_CONTEXT cert, /*out*/ char **issuer_name); + bool cryptopro_init(const char* cp_file) { @@ -128,7 +135,7 @@ print_crl_urls_from_certificate(PCCERT_CONTEXT cert) &crl_dist_points, &info_size )) { - LOG_ERROR("CryptDecodeObjectEx failed. Desc: 0x%x", cp_function_list.GetLastError()); + LOG_ERROR("CryptDecodeObjectEx failed. Error code: 0x%08x", cp_function_list.GetLastError()); goto error; } @@ -160,6 +167,169 @@ error: return; } +static void +print_crt_urls_from_certificate(PCCERT_CONTEXT cert) +{ + LOG_TRACE("print_crt_urls_from_certificate enter"); + + PCERT_EXTENSION extension = cp_function_list.CertFindExtension( + szOID_AUTHORITY_INFO_ACCESS, + cert->pCertInfo->cExtension, + cert->pCertInfo->rgExtension + ); + + if (!extension) { + LOG_WARN("Authority Info Access extension not found"); + goto error; + } + + DWORD info_size = 0; + PCERT_AUTHORITY_INFO_ACCESS info_access = NULL; + + if (!cp_function_list.CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + X509_AUTHORITY_INFO_ACCESS, + extension->Value.pbData, + extension->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + NULL, + &info_access, + &info_size + )) { + LOG_ERROR("CryptDecodeObjectEx failed. Error code: 0x%08x", cp_function_list.GetLastError()); + goto error; + } + + if (info_access && info_access->cAccDescr > 0) { + for (DWORD i = 0; i < info_access->cAccDescr; i++) { + if (strcmp(info_access->rgAccDescr[i].pszAccessMethod, szOID_PKIX_CA_ISSUERS) == 0) { + if (info_access->rgAccDescr[i].AccessLocation.dwAltNameChoice == CERT_ALT_NAME_URL) { + LOG_INFO("CA Issuers URL: %ls", info_access->rgAccDescr[i].AccessLocation._empty_union_.pwszURL); + } + } + } + } + + if (info_access) { + cp_function_list.LocalFree(info_access); + } + + LOG_TRACE("print_crt_urls_from_certificate exit"); + return; + +error: + LOG_ERROR("print_crt_urls_from_certificate exit with error"); + return; +} + +static char* +bin_to_hex(const BYTE *bytes, DWORD len) +{ + char* hex = NULL; + + hex = malloc(len * 2 + 1); + if (hex == NULL) { + LOG_ERROR("bin_to_hex exit with error: could not allocate memory for hex (%d bytes)", len * 2 + 1); + return NULL; + } + + for (DWORD i = 0; i < len; i++) { + sprintf(hex + i * 2, "%02x", bytes[i]); + } + + return hex; +} + +static bool +get_thumbprint(PCCERT_CONTEXT cert, /*out*/ char** thumbprint) +{ + LOG_TRACE("get_thumbprint enter"); + + BYTE sha1_hash[SHA1_SIZE]; + DWORD sha1_hash_size = sizeof(sha1_hash); + + if (!cp_function_list.CertGetCertificateContextProperty( + cert, + CERT_SHA1_HASH_PROP_ID, + sha1_hash, + &sha1_hash_size)) { + LOG_ERROR("CertGetCertificateContextProperty(CERT_SHA1_HASH_PROP_ID) failed. Error code: 0x%08x", + cp_function_list.GetLastError()); + goto error; + } + + *thumbprint = bin_to_hex(sha1_hash, sha1_hash_size); + if (*thumbprint == NULL) { + goto error; + } + + LOG_DEBUG("thumbprint: '%s'", *thumbprint); + + LOG_TRACE("get_thumbprint exit"); + return true; + +error: + LOG_ERROR("get_thumbprint exit with error"); + return false; +} + +static void +print_cert_time_validity(PCCERT_CONTEXT cert) +{ + SYSTEMTIME st; + + LOG_TRACE("print_cert_time_validity enter"); + + if (cp_function_list.FileTimeToSystemTime(&cert->pCertInfo->NotBefore, &st)) { + LOG_INFO("Not valid before: %02d.%02d.%04d", st.wDay, st.wMonth, st.wYear); + } + + if (cp_function_list.FileTimeToSystemTime(&cert->pCertInfo->NotAfter, &st)) { + LOG_INFO("Not valid after: %02d.%02d.%04d", st.wDay, st.wMonth, st.wYear); + } + + LOG_TRACE("print_cert_time_validity exit"); +} + +static void +print_common_cert_info(PCCERT_CONTEXT cert) +{ + char* subject = NULL; + char* issuer = NULL; + char* thumbprint = NULL; + + LOG_TRACE("print_common_cert_info enter"); + + if (get_thumbprint(cert, &thumbprint)) { + LOG_INFO("SHA1 Hash: %s", thumbprint); + } + + if (get_subject_name(cert, &subject)) { + LOG_INFO("Subject: %s", subject); + } + + if (get_issuer_name(cert, &issuer)) { + LOG_INFO("Issuer: %s", issuer); + } + + free(subject); + free(issuer); + free(thumbprint); + + LOG_TRACE("print_common_cert_info exit"); +} + +static void +print_cert_info(PCCERT_CONTEXT cert) +{ + LOG_INFO("=========== Certificate Info: ==========="); + print_common_cert_info(cert); + print_cert_time_validity(cert); + print_crt_urls_from_certificate(cert); + print_crl_urls_from_certificate(cert); + LOG_INFO("========================================="); +} + static void process_trust_status_error(PCCERT_CONTEXT cert, DWORD status, /*out*/ const char **error_code) { @@ -168,7 +338,6 @@ process_trust_status_error(PCCERT_CONTEXT cert, DWORD status, /*out*/ const char switch (status) { case CERT_TRUST_REVOCATION_STATUS_UNKNOWN: *error_code = CERT_TRUST_REVOCATION_STATUS_UNKNOWN_ERR_CODE; - print_crl_urls_from_certificate(cert); break; case CERT_TRUST_IS_UNTRUSTED_ROOT: *error_code = CERT_TRUST_IS_UNTRUSTED_ROOT_ERR_CODE; @@ -176,6 +345,9 @@ process_trust_status_error(PCCERT_CONTEXT cert, DWORD status, /*out*/ const char case CERT_TRUST_IS_NOT_TIME_VALID: *error_code = CERT_TRUST_IS_NOT_TIME_VALID_ERR_CODE; break; + case CERT_TRUST_IS_PARTIAL_CHAIN: + *error_code = CERT_TRUST_IS_PARTIAL_CHAIN_ERR_CODE; + break; default: *error_code = NULL; break; @@ -188,6 +360,8 @@ process_trust_status_error(PCCERT_CONTEXT cert, DWORD status, /*out*/ const char LOG_WARN("The certificate is not trusted. CERT_TRUST_STATUS: '0x%08x'", status); } + print_cert_info(cert); + LOG_TRACE("process_trust_status_error exit"); } diff --git a/Инструкция по установке.md b/Инструкция по установке.md index 62da791..95e6cb2 100644 --- a/Инструкция по установке.md +++ b/Инструкция по установке.md @@ -99,12 +99,12 @@ chown -R ervu:ervu /var/opt/cprocsp/keys/ervu/key_cont.000/ | Среда \ Алгоритм подписания | GOST3410_2012_256 | | ----------------------------- | ----------------------------------------------------- | | | TESIA GOST 2012 new.cer | -| Тестовая | Отпечаток: e654f6114dc19bae84c5748794a4fd7797726e71 | -| | Срок действия: по 24.04.2025 | +| Тестовая | Отпечаток: b3a7890493bdd95d47c6871627b475e276962bf1 | +| | Срок действия: по 21.04.2026 | | ----------------------------- | ----------------------------------------------------- | -| | ГОСТ+ПРОД+24-25.cer | -| Продуктивная | Отпечаток: 20ad21785b9161ef46f075d4dc23c34e1e349228 | -| | Срок действия: по 08.06.2025 | +| | ГОСТ_PROD_25_26.cer | +| Продуктивная | Отпечаток: 2583cca4e2aad8d8170ef73fecc592ccac46821d | +| | Срок действия: по 17.06.2026 | -------------------------------------------------------------------------------------