From f53d4f86a58b6cfe7aa963abc2a5745cdfd481ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B0=D0=B8=D0=BB=D1=8F=20=D0=90=D0=BB=D0=B0=D1=88?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=D0=B0?= Date: Tue, 19 Aug 2025 11:40:27 +0300 Subject: [PATCH 01/10] =?UTF-8?q?SUPPORT-9332.=20=D0=94=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=81=D0=BF=D0=B5=D1=86=D0=B8?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=BF=D1=80=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BA=D0=B5=20=D1=86=D0=B5=D0=BF=D0=BE=D1=87=D0=BA=D0=B8?= =?UTF-8?q?=20=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 --- CMakeLists.txt | 1 + README.md | 30 ++++++++--- src/fcgisrv/fcgi_utils.c | 30 +++++++++++ src/fcgisrv/fcgi_utils.h | 12 ++--- src/modules/service_sign.c | 89 +++++++------------------------ src/modules/service_verify.c | 43 +++++++-------- src/utils/cryptopro.c | 60 ++++++++++++++++----- src/utils/cryptopro.h | 8 +-- src/utils/response_builder.c | 100 +++++++++++++++++++++++++++++++++++ src/utils/response_builder.h | 14 +++++ src/utils/timer.c | 4 +- 11 files changed, 266 insertions(+), 125 deletions(-) create mode 100644 src/utils/response_builder.c create mode 100644 src/utils/response_builder.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d598d3c..0d58f77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,7 @@ ADD_EXECUTABLE (${PROJECT_NAME} ${UTILS_DIR}/jwt.c ${UTILS_DIR}/library.c ${UTILS_DIR}/logger.c + ${UTILS_DIR}/response_builder.c ${UTILS_DIR}/str_t.c ${UTILS_DIR}/timer.c ${UTILS_DIR}/uuid.c diff --git a/README.md b/README.md index 7b5c8a7..1d303c7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ - подпись данных - проверка подписи маркера доступа -### Подпись данных +## Подпись данных Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain). C помощью ДСЧ генерирует state - набор случайных символов, генерируется по стандарту UUID. @@ -36,14 +36,14 @@ $ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/sign -H "Content-Type: text/pl } ``` -### Проверка подписи маркера доступа +## Проверка подписи маркера доступа Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain). Проверяет подпись маркера доступа, полученного в теле запроса. В ответе возвращает один из следующих статус-кодов: -- 200 OK - подпись валидна -- 401 Unauthorized - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера) -- 500 Internal Server Error - внутренняя ошибка сервера +- `200 OK`- подпись валидна +- `401 Unauthorized` - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера) +- `500 Internal Server Error` - внутренняя ошибка сервера (подробнее см. в `Обработка ошибок`) Пример выполнения запроса: ``` @@ -65,6 +65,24 @@ $ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/verify -H "Content-Type: text/ < ``` +## Обработка ошибок + +В случае ошибки сервер возвращает ответ со статус-кодом `500 Internal Server Error` и телом в формате JSON, содержащим поле `error_code`. +Пример ответа: +```json +{ + "error_code": "CERT_TRUST_REVOCATION_STATUS_UNKNOWN" +} +``` + +### Возможные коды ошибок (`error_code`) + +| Код | Описание | +|-----------------------------------------|----------| +| `CERT_TRUST_REVOCATION_STATUS_UNKNOWN` | Неизвестен статус отзыва сертификата или одного из сертификатов в цепочке (проблема с CRL) | +| `CERT_TRUST_IS_UNTRUSTED_ROOT` | Сертификат или цепочка сертификатов основана на ненадежном корневом сертификате | +| `CERT_TRUST_IS_NOT_TIME_VALID` | Этот сертификат или один из сертификатов в цепочке сертификатов является недопустимым по времени | + ## Сборка из исходников Инструкции по сборке из исходников см. "Инструкция по сборке.md" @@ -116,5 +134,3 @@ export SIGN_LOG_TIME=on ``` unset SIGN_LOG_TIME ``` - - diff --git a/src/fcgisrv/fcgi_utils.c b/src/fcgisrv/fcgi_utils.c index 64ab5b3..95a1654 100644 --- a/src/fcgisrv/fcgi_utils.c +++ b/src/fcgisrv/fcgi_utils.c @@ -1,9 +1,11 @@ #include "fcgi_server_internal.h" #include "utils/logger.h" +#include "utils/response_builder.h" #include + int fcgi_get_content_length(const FCGX_Request* request) { @@ -161,3 +163,31 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va return HANDLER_SUCCESS; } + +fcgi_handler_status_t +fcgi_500_internal_server_error_handler(const FCGX_Request* request, const char *error_code) +{ + LOG_TRACE("fcgi_500_internal_server_error_handler"); + + char *response = build_response_from_error_code(error_code); + if (response == NULL) { + goto error; + } + + LOG_DEBUG("response status: " FCGI_500_RESPONSE_FORMAT, response); + + if (FCGX_FPrintF(request->out, FCGI_500_RESPONSE_FORMAT, response) < 0) { + LOG_ERROR("FCGX_FPrintF() failed"); + goto error; + } + + fcgi_print_log(request, "500 Internal Server Error"); + + free(response); + return HANDLER_SUCCESS; + +error: + free(response); + LOG_ERROR("fcgi_500_internal_server_error_handler exit with error"); + return HANDLER_ERROR; +} diff --git a/src/fcgisrv/fcgi_utils.h b/src/fcgisrv/fcgi_utils.h index 080531b..a7e195f 100644 --- a/src/fcgisrv/fcgi_utils.h +++ b/src/fcgisrv/fcgi_utils.h @@ -131,9 +131,9 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va #define FCGI_500_RESPONSE_FORMAT \ "Status: 500 Internal Server Error" CRLF\ - "Content-type: text/plain" CRLF\ + "Content-type: application/json" CRLF\ CRLF\ - "500 Internal Server Error" CRLF + "%s" CRLF @@ -213,12 +213,8 @@ fcgi_413_request_entity_too_large_handler(const FCGX_Request* request, void* ctx } -static inline fcgi_handler_status_t -fcgi_500_internal_server_error_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) -{ - fcgi_print_log(request, "500 Internal Server Error"); - return FCGI_CHECK_PRINTF_STATUS(request, FCGI_500_RESPONSE_FORMAT, 500); -} +fcgi_handler_status_t +fcgi_500_internal_server_error_handler(const FCGX_Request* request, const char *error_code); /******************************************************************************/ diff --git a/src/modules/service_sign.c b/src/modules/service_sign.c index e887d4c..32e8ea9 100644 --- a/src/modules/service_sign.c +++ b/src/modules/service_sign.c @@ -3,9 +3,9 @@ #include "fcgisrv/fcgi_utils.h" #include "utils/cryptopro.h" -#include "utils/json_writer.h" #include "utils/timer.h" #include "utils/uuid.h" +#include "utils/response_builder.h" #define SIGN_CONF_SECTION "sign" #define SIGN_CONF_KEY_LOCATION "location" @@ -17,6 +17,7 @@ CRLF\ "%s" CRLF + static const str_t SIGN_CONF_DEFAULT_LOCATION = str_t_const("/sign"); static const int CLIENT_MAX_BODY_SIZE = 4096; @@ -36,12 +37,15 @@ typedef struct fcgi_sign_request_s { int content_length; char *response; + const char *error_code; } fcgi_sign_request_t; static void fcgi_sign_request_clear(fcgi_sign_request_t *req_info); -static fcgi_request_handler_pt fcgi_request_finalize_handler(fcgi_handler_status_t status); +static fcgi_handler_status_t fcgi_request_finalize(const FCGX_Request* request, + fcgi_sign_request_t *req_info, + fcgi_handler_status_t status); static int sign_content_with_state(const sign_service_t *hsign, fcgi_sign_request_t *req_info); @@ -212,7 +216,7 @@ fcgi_sign_handler(FCGX_Request* request, void* ctx) // status = HANDLER_SUCCESS; exit: - status = fcgi_request_finalize_handler(status)(request, &req_info); + status = fcgi_request_finalize(request, &req_info, status); fcgi_sign_request_clear(&req_info); @@ -235,12 +239,10 @@ fcgi_sign_request_clear(fcgi_sign_request_t *req_info) } static fcgi_handler_status_t -fcgi_ok_handler(const FCGX_Request* request, void *ctx) +fcgi_ok_handler(const FCGX_Request* request, const fcgi_sign_request_t *req_info) { LOG_TRACE("fcgi_ok_handler"); - const fcgi_sign_request_t *req_info = (fcgi_sign_request_t*) ctx; - LOG_DEBUG("response status: " FCGI_OK_RESPONSE_FORMAT, req_info->response); if (FCGX_FPrintF(request->out, FCGI_OK_RESPONSE_FORMAT, req_info->response) < 0) { @@ -251,39 +253,32 @@ fcgi_ok_handler(const FCGX_Request* request, void *ctx) return HANDLER_SUCCESS; } -static fcgi_request_handler_pt -fcgi_request_finalize_handler(fcgi_handler_status_t status) +static fcgi_handler_status_t +fcgi_request_finalize(const FCGX_Request* request, fcgi_sign_request_t *req_info, + fcgi_handler_status_t status) { - fcgi_request_handler_pt handler; - switch (status) { case HANDLER_SUCCESS: - handler = fcgi_ok_handler; - break; + return fcgi_ok_handler(request, req_info); case HANDLER_HTTP_OK: - handler = fcgi_200_ok_handler; - break; + return fcgi_200_ok_handler(request, req_info); case HANDLER_HTTP_BAD_REQUEST: - handler = fcgi_400_bad_request_handler; - break; + return fcgi_400_bad_request_handler(request, req_info); case HANDLER_HTTP_NOT_ACCEPTABLE: - handler = fcgi_406_not_acceptable_handler; - break; + return fcgi_406_not_acceptable_handler(request, req_info); case HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE: - handler = fcgi_413_request_entity_too_large_handler; - break; + return fcgi_413_request_entity_too_large_handler(request, req_info); case HANDLER_ERROR: default: - handler = fcgi_500_internal_server_error_handler; break; } - return handler; + return fcgi_500_internal_server_error_handler(request, req_info->error_code); } static int @@ -318,52 +313,7 @@ error: return -1; } -static char * -generate_response(const char *signature, const char *state) -{ - JsonBuilder *jbuilder; - char *response; - LOG_TRACE("generate_response enter"); - - jbuilder = json_builder_new(); - if (jbuilder == NULL) { - LOG_ERROR("json_builder_new failed"); - goto error; - } - - if (json_builder_begin_object(jbuilder) == NULL) { - LOG_ERROR("json_builder_begin_object failed"); - goto error; - } - - if (json_write_member_string(jbuilder, "signature", signature)) { - goto error; - } - - if (json_write_member_string(jbuilder, "state", state)) { - goto error; - } - - if (json_builder_end_object(jbuilder) == NULL) { - LOG_ERROR("json_builder_end_object failed"); - goto error; - } - - response = json_write_to_str(jbuilder); - - g_object_unref(jbuilder); - - LOG_TRACE("generate_response exit"); - return response; - -error: - if (jbuilder != NULL) { - g_object_unref(jbuilder); - } - LOG_ERROR("generate_response exit with error"); - return NULL; -} static int sign_content_with_state(const sign_service_t *hsign, fcgi_sign_request_t *req_info) @@ -383,13 +333,14 @@ sign_content_with_state(const sign_service_t *hsign, fcgi_sign_request_t *req_in goto error; } - if (cryptopro_sign(&hsign->cryptopro_ctx, &content_state, &signature)) { + if (cryptopro_sign(&hsign->cryptopro_ctx, &content_state, &signature, &req_info->error_code)) { goto error; } assert(str_t_is_null_terminated(signature)); + assert(req_info->error_code == NULL); - req_info->response = generate_response(signature.data, state); + req_info->response = build_response_from_signature(signature.data, state); if (req_info->response == NULL) { goto error; diff --git a/src/modules/service_verify.c b/src/modules/service_verify.c index 3316dc1..1e242f4 100644 --- a/src/modules/service_verify.c +++ b/src/modules/service_verify.c @@ -37,12 +37,15 @@ typedef struct fcgi_verify_request_s { char *content; int content_length; - char *verify_error; + char *verify_error; // клиентская ошибка (подпись невалидна) - статус 401 + const char *error_code; // серверная ошибка - статус 500 } fcgi_verify_request_t; -static fcgi_request_handler_pt fcgi_request_finalize_handler(fcgi_handler_status_t status); +static fcgi_handler_status_t fcgi_request_finalize(const FCGX_Request* request, + fcgi_verify_request_t *req_info, + fcgi_handler_status_t status); static void fcgi_verify_request_clear(fcgi_verify_request_t *req_info); static fcgi_handler_status_t verify_jwt_sign(fcgi_verify_request_t* req_info, verify_service_t *ctx); @@ -180,7 +183,7 @@ fcgi_verify_handler(FCGX_Request* request, void* ctx) status = verify_jwt_sign(&req_info, ctx); exit: - status = fcgi_request_finalize_handler(status)(request, &req_info); + status = fcgi_request_finalize(request, &req_info, status); fcgi_verify_request_clear(&req_info); @@ -211,40 +214,33 @@ fcgi_401_bad_signature_handler(const FCGX_Request* request, void *ctx) return HANDLER_SUCCESS; } -static fcgi_request_handler_pt -fcgi_request_finalize_handler(fcgi_handler_status_t status) +static fcgi_handler_status_t +fcgi_request_finalize(const FCGX_Request* request, fcgi_verify_request_t *req_info, + fcgi_handler_status_t status) { - fcgi_request_handler_pt handler; - switch (status) { case HANDLER_SUCCESS: case HANDLER_HTTP_OK: - handler = fcgi_200_ok_handler; - break; + return fcgi_200_ok_handler(request, req_info); case HANDLER_HTTP_BAD_REQUEST: - handler = fcgi_400_bad_request_handler; - break; + return fcgi_400_bad_request_handler(request, req_info); case HANDLER_HTTP_UNAUTHORIZED: - handler = fcgi_401_bad_signature_handler; - break; + return fcgi_401_bad_signature_handler(request, req_info); case HANDLER_HTTP_NOT_ACCEPTABLE: - handler = fcgi_406_not_acceptable_handler; - break; + return fcgi_406_not_acceptable_handler(request, req_info); case HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE: - handler = fcgi_413_request_entity_too_large_handler; - break; + return fcgi_413_request_entity_too_large_handler(request, req_info); case HANDLER_ERROR: default: - handler = fcgi_500_internal_server_error_handler; break; } - return handler; + return fcgi_500_internal_server_error_handler(request, req_info->error_code); } static void @@ -259,9 +255,10 @@ fcgi_verify_request_clear(fcgi_verify_request_t *req_info) } static int -verify_sign_using_thumbprint_list(cryptopro_context_t *ctx, const string_list_t* thumbprint_list, +verify_sign_using_thumbprint_list(cryptopro_context_t* ctx, const string_list_t* thumbprint_list, const str_t* alg, const str_t* header_payload, - const str_t* sign, bool* is_verified, char** verify_error) + const str_t* sign, bool* is_verified, char** verify_error, + const char** error_code) { int rc = -1; @@ -278,7 +275,7 @@ verify_sign_using_thumbprint_list(cryptopro_context_t *ctx, const string_list_t* ctx->cert_thumbprint = &thumbprint; - rc = cryptopro_verify(ctx, alg, header_payload, sign, is_verified, verify_error); + rc = cryptopro_verify(ctx, alg, header_payload, sign, is_verified, verify_error, error_code); if (rc) { LOG_ERROR("cryptopro_verify() failed for cert with thumbprint '%s'", cert_thumbprint); } @@ -347,7 +344,7 @@ verify_jwt_sign(fcgi_verify_request_t* req_info, verify_service_t *ctx) if (verify_sign_using_thumbprint_list(&ctx->cryptopro_ctx, &ctx->conf->esia_cert_thumbprint_list, &alg, &header_payload, &sign, &is_verified, - &req_info->verify_error)) { + &req_info->verify_error, &req_info->error_code)) { goto error; } diff --git a/src/utils/cryptopro.c b/src/utils/cryptopro.c index 5d183fd..cfbba5d 100644 --- a/src/utils/cryptopro.c +++ b/src/utils/cryptopro.c @@ -5,11 +5,15 @@ #include "library.h" #include "logger.h" +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 capi_function_list_t cp_function_list; static library_t libcapi; -static int sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign); +static int sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, + /*out*/ str_t *sign, /*out*/ const char **error_code); static int reverse_sign(const str_t *sign, /*out*/ str_t *sign_reversed); @@ -38,7 +42,8 @@ cryptopro_init(const char* cp_file) } int -cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign) +cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, + /*out*/ str_t *sign, /*out*/ const char **error_code) { str_t signed_data = str_t_null; str_t sign_reversed = str_t_null; @@ -51,7 +56,7 @@ cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t assert(data != NULL && !str_t_is_null(*data)); assert(sign != NULL); - if (sign_hash_data(ctx, data, &signed_data)) { + if (sign_hash_data(ctx, data, &signed_data, error_code)) { goto error; } @@ -94,8 +99,38 @@ free_cert_chain(PCCERT_CHAIN_CONTEXT chain_ctx) LOG_TRACE("free_cert_chain exit"); } +static void +process_trust_status_error(DWORD status, /*out*/ const char **error_code) +{ + LOG_TRACE("process_trust_status_error enter"); + + switch (status) { + case CERT_TRUST_REVOCATION_STATUS_UNKNOWN: + *error_code = CERT_TRUST_REVOCATION_STATUS_UNKNOWN_ERR_CODE; + break; + case CERT_TRUST_IS_UNTRUSTED_ROOT: + *error_code = CERT_TRUST_IS_UNTRUSTED_ROOT_ERR_CODE; + break; + case CERT_TRUST_IS_NOT_TIME_VALID: + *error_code = CERT_TRUST_IS_NOT_TIME_VALID_ERR_CODE; + break; + default: + *error_code = NULL; + break; + } + + if (*error_code) { + LOG_WARN("The certificate is not trusted. CERT_TRUST_STATUS: '0x%08x' (%s)", + status, *error_code); + } else { + LOG_WARN("The certificate is not trusted. CERT_TRUST_STATUS: '0x%08x'", status); + } + + LOG_TRACE("process_trust_status_error exit"); +} + static PCCERT_CHAIN_CONTEXT -get_cert_chain(PCCERT_CONTEXT certificate) +get_cert_chain(PCCERT_CONTEXT certificate, /*out*/ const char **error_code) { LOG_TRACE("get_cert_chain enter"); @@ -118,8 +153,7 @@ get_cert_chain(PCCERT_CONTEXT certificate) } if (chain_ctx->TrustStatus.dwErrorStatus) { - LOG_WARN("The certificate is not trusted. CERT_TRUST_STATUS: '0x%08x'", - chain_ctx->TrustStatus.dwErrorStatus); + process_trust_status_error(chain_ctx->TrustStatus.dwErrorStatus, error_code); goto error; } @@ -180,7 +214,7 @@ exit: } static bool -verify_cert_chain(PCCERT_CONTEXT certificate, timer_context_t *timer_ctx) +verify_cert_chain(PCCERT_CONTEXT certificate, timer_context_t *timer_ctx, /*out*/ const char **error_code) { bool is_verified = false; @@ -188,7 +222,7 @@ verify_cert_chain(PCCERT_CONTEXT certificate, timer_context_t *timer_ctx) timer_on_get_cert_chain_enter(timer_ctx); - PCCERT_CHAIN_CONTEXT chain_ctx = get_cert_chain(certificate); + PCCERT_CHAIN_CONTEXT chain_ctx = get_cert_chain(certificate, error_code); if (chain_ctx == NULL) { goto exit; } @@ -310,7 +344,7 @@ log_sign_hash_data_last_error() } static int -sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign) +sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign, /*out*/ const char **error_code) { int rc = -1; BOOL bReleaseContext = FALSE; @@ -321,7 +355,7 @@ sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t timer_on_verify_cert_chain_enter(ctx->timer_ctx); - if (!verify_cert_chain(ctx->signer_cert, ctx->timer_ctx)) { + if (!verify_cert_chain(ctx->signer_cert, ctx->timer_ctx, error_code)) { goto exit; } @@ -673,8 +707,8 @@ get_verify_error(char** verify_error) } int -cryptopro_verify(cryptopro_context_t *ctx, const str_t* alg, const str_t* data, - const str_t* sign, bool* is_verified, char** verify_error) +cryptopro_verify(cryptopro_context_t* ctx, const str_t* alg, const str_t* data, + const str_t* sign, bool* is_verified, char** verify_error, const char** error_code) { int rc = -1; HCERTSTORE hStoreHandle = NULL; @@ -711,7 +745,7 @@ cryptopro_verify(cryptopro_context_t *ctx, const str_t* alg, const str_t* data, timer_on_verify_cert_chain_enter(ctx->timer_ctx); - if (!verify_cert_chain(certificate, ctx->timer_ctx)) { + if (!verify_cert_chain(certificate, ctx->timer_ctx, error_code)) { goto exit; } diff --git a/src/utils/cryptopro.h b/src/utils/cryptopro.h index 6a318c4..2921f54 100644 --- a/src/utils/cryptopro.h +++ b/src/utils/cryptopro.h @@ -41,10 +41,12 @@ bool cryptopro_init(const char* cp_file); int open_signer_cert(cryptopro_context_t *ctx); void close_signer_cert(cryptopro_context_t *ctx); -int cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign); +int cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign, + /*out*/ const char **error_code); -int cryptopro_verify(cryptopro_context_t *сtx, const str_t* alg, const str_t *data, - const str_t *sign, bool* is_verified, char** verify_error); +int cryptopro_verify(cryptopro_context_t* ctx, const str_t* alg, const str_t* data, + const str_t* sign, bool* is_verified, char** verify_error, + const char** error_code); int cryptopro_gen_random(const cryptopro_context_t *ctx, unsigned char* data, size_t len); diff --git a/src/utils/response_builder.c b/src/utils/response_builder.c new file mode 100644 index 0000000..1a932b9 --- /dev/null +++ b/src/utils/response_builder.c @@ -0,0 +1,100 @@ +#include "response_builder.h" + +#include "utils/json_writer.h" +#include "utils/logger.h" + +static const char* DEFAULT_ERROR_CODE = "INTERNAL_ERROR"; + +char * +build_response_from_error_code(const char *error_code) +{ + JsonBuilder *jbuilder; + char *response; + + LOG_TRACE("build_response_from_error_code enter"); + + if (error_code == NULL) { + error_code = DEFAULT_ERROR_CODE; + } + + jbuilder = json_builder_new(); + if (jbuilder == NULL) { + LOG_ERROR("build_response_from_error_code. json_builder_new failed"); + goto error; + } + + if (json_builder_begin_object(jbuilder) == NULL) { + LOG_ERROR("build_response_from_error_code. json_builder_begin_object failed"); + goto error; + } + + if (json_write_member_string(jbuilder, "error_code", error_code)) { + goto error; + } + + if (json_builder_end_object(jbuilder) == NULL) { + LOG_ERROR("build_response_from_error_code. json_builder_end_object failed"); + goto error; + } + + response = json_write_to_str(jbuilder); + + g_object_unref(jbuilder); + + LOG_TRACE("build_response_from_error_code exit"); + return response; + +error: + if (jbuilder != NULL) { + g_object_unref(jbuilder); + } + LOG_ERROR("build_response_from_error_code exit with error"); + return NULL; +} + +char * +build_response_from_signature(const char *signature, const char *state) +{ + JsonBuilder *jbuilder; + char *response; + + LOG_TRACE("build_response_from_signature enter"); + + jbuilder = json_builder_new(); + if (jbuilder == NULL) { + LOG_ERROR("json_builder_new failed"); + goto error; + } + + if (json_builder_begin_object(jbuilder) == NULL) { + LOG_ERROR("json_builder_begin_object failed"); + goto error; + } + + if (json_write_member_string(jbuilder, "signature", signature)) { + goto error; + } + + if (json_write_member_string(jbuilder, "state", state)) { + goto error; + } + + if (json_builder_end_object(jbuilder) == NULL) { + LOG_ERROR("json_builder_end_object failed"); + goto error; + } + + response = json_write_to_str(jbuilder); + + g_object_unref(jbuilder); + + LOG_TRACE("build_response_from_signature exit"); + return response; + +error: + if (jbuilder != NULL) { + g_object_unref(jbuilder); + } + LOG_ERROR("build_response_from_signature exit with error"); + return NULL; +} diff --git a/src/utils/response_builder.h b/src/utils/response_builder.h new file mode 100644 index 0000000..1dd8dc1 --- /dev/null +++ b/src/utils/response_builder.h @@ -0,0 +1,14 @@ +#ifndef RESPONSE_BUILDER_H_INCLUDED +#define RESPONSE_BUILDER_H_INCLUDED + + +/* common functions */ + +char * build_response_from_error_code(const char *error_code); + +/* functions for sign service */ + +char * build_response_from_signature(const char *signature, const char *state); + + +#endif // RESPONSE_BUILDER_H_INCLUDED diff --git a/src/utils/timer.c b/src/utils/timer.c index c2fb2bc..667d785 100644 --- a/src/utils/timer.c +++ b/src/utils/timer.c @@ -11,10 +11,10 @@ init_timers(timer_context_t *ctx) { const char* slt = getenv(ENV_SIGN_LOG_TIME); if (slt != NULL) { - LOG_INFO("environment variable '" ENV_SIGN_LOG_TIME "'='%s' -> timings logging: ON", slt); + LOG_DEBUG("environment variable '" ENV_SIGN_LOG_TIME "'='%s' -> timings logging: ON", slt); ctx->is_timer_on = true; } else { - LOG_INFO("environment variable '" ENV_SIGN_LOG_TIME "' does not exist " + LOG_DEBUG("environment variable '" ENV_SIGN_LOG_TIME "' does not exist " "-> timings logging: OFF"); ctx->is_timer_on = false; } From 9d2f2be25a533b9f3afade34f37c37658cc07de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B0=D0=B8=D0=BB=D1=8F=20=D0=90=D0=BB=D0=B0=D1=88?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=D0=B0?= Date: Tue, 19 Aug 2025 11:41:52 +0300 Subject: [PATCH 02/10] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=201.?= =?UTF-8?q?3.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d58f77..0d9d8b7 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.3.3 LANGUAGES C) +PROJECT (ervu-sign-module VERSION 1.3.4 LANGUAGES C) IF (CMAKE_VERBOSE) SET (CMAKE_VERBOSE_MAKEFILE 1) From ac08303c908de25ebf555bd97bc2fb7ddfed5653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B0=D0=B8=D0=BB=D1=8F=20=D0=90=D0=BB=D0=B0=D1=88?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=D0=B0?= Date: Fri, 29 Aug 2025 13:03:06 +0300 Subject: [PATCH 03/10] =?UTF-8?q?SUPPORT-9333.=20=D0=9F=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=BA=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=81=D0=BE=D0=BE=D0=B1?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D1=8F,=20=D1=81=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=B0=D1=89=D0=B5=D0=B3=D0=BE=20=D0=BE=D1=82=D1=81?= =?UTF-8?q?=D0=BE=D0=B5=D0=B4=D0=B8=D0=BD=D1=91=D0=BD=D0=BD=D1=83=D1=8E=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 3 + README.md | 43 +- conf/ervu-sign-module.conf.example | 3 + conf/nginx-docker.conf | 1 + conf/nginx.conf | 1 + src/fcgisrv/fcgi_utils.c | 60 +- src/fcgisrv/fcgi_utils.h | 29 +- src/modules/service_sign.c | 29 +- src/modules/service_verify.c | 53 +- src/modules/service_verify_detached.c | 275 +++++++ src/modules/service_verify_detached.h | 29 + src/service_manager.c | 21 + src/service_manager.h | 13 +- src/utils/base64.c | 28 + src/utils/base64.h | 2 + src/utils/capi.c | 7 + src/utils/capi.h | 69 ++ src/utils/cryptopro.c | 303 +++++++- src/utils/cryptopro.h | 20 + src/utils/detached_sign_payload_parser.c | 279 ++++++++ src/utils/detached_sign_payload_parser.h | 13 + src/utils/json_parser.c | 7 +- src/utils/multipart_parser.c | 677 ++++++++++++++++++ src/utils/multipart_parser.h | 65 ++ src/utils/response_builder.c | 47 ++ src/utils/response_builder.h | 5 + src/utils/timer.c | 41 ++ src/utils/timer.h | 13 + tests/CMakeLists.txt | 6 + tests/main.c | 28 + .../utils/test_detached_sign_payload_parser.c | 169 +++++ .../utils/test_detached_sign_payload_parser.h | 11 + tests/utils/test_multipart_parser.c | 149 ++++ tests/utils/test_multipart_parser.h | 18 + 34 files changed, 2403 insertions(+), 114 deletions(-) create mode 100644 src/modules/service_verify_detached.c create mode 100644 src/modules/service_verify_detached.h create mode 100644 src/utils/detached_sign_payload_parser.c create mode 100644 src/utils/detached_sign_payload_parser.h create mode 100644 src/utils/multipart_parser.c create mode 100644 src/utils/multipart_parser.h create mode 100644 tests/utils/test_detached_sign_payload_parser.c create mode 100644 tests/utils/test_detached_sign_payload_parser.h create mode 100644 tests/utils/test_multipart_parser.c create mode 100644 tests/utils/test_multipart_parser.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d9d8b7..29b0889 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,7 @@ ADD_EXECUTABLE (${PROJECT_NAME} ${UTILS_DIR}/capi.c ${UTILS_DIR}/conf_file_context.c ${UTILS_DIR}/cryptopro.c + ${UTILS_DIR}/detached_sign_payload_parser.c ${UTILS_DIR}/gconf_file.c ${UTILS_DIR}/glib_utils.c ${UTILS_DIR}/json_parser.c @@ -125,6 +126,7 @@ ADD_EXECUTABLE (${PROJECT_NAME} ${UTILS_DIR}/jwt.c ${UTILS_DIR}/library.c ${UTILS_DIR}/logger.c + ${UTILS_DIR}/multipart_parser.c ${UTILS_DIR}/response_builder.c ${UTILS_DIR}/str_t.c ${UTILS_DIR}/timer.c @@ -135,6 +137,7 @@ ADD_EXECUTABLE (${PROJECT_NAME} ${FCGISRV_DIR}/fcgi_utils.c ${FCGISRV_DIR}/fcgi_worker.c ${MODULES_DIR}/service_sign.c + ${MODULES_DIR}/service_verify_detached.c ${MODULES_DIR}/service_verify.c ${MODULES_DIR}/service_version.c ) diff --git a/README.md b/README.md index 1d303c7..af1c4ab 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ В модуле реализованы следующие функции: - подпись данных - проверка подписи маркера доступа +- проверка подписанного сообщения, содержащего отсоединённую (detached) подпись ## Подпись данных @@ -41,7 +42,7 @@ $ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/sign -H "Content-Type: text/pl Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain). Проверяет подпись маркера доступа, полученного в теле запроса. В ответе возвращает один из следующих статус-кодов: -- `200 OK`- подпись валидна +- `200 OK` - подпись валидна - `401 Unauthorized` - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера) - `500 Internal Server Error` - внутренняя ошибка сервера (подробнее см. в `Обработка ошибок`) @@ -65,6 +66,43 @@ $ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/verify -H "Content-Type: text/ < ``` +### Проверка подписанного сообщения, содержащего отсоединённую подпись + +Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: multipart/form-data). +Проверяет подписанное сообщение, содержащее отсоединённую (detached) подпись. +В ответе возвращает один из следующих статус-кодов: +- `200 OK` - подпись валидна, в ответе возвращает поля "Subject" и "Issuer" из свойств сертификата, которым было подписано сообщение +- `401 Unauthorized` - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера) +- `500 Internal Server Error` - внутренняя ошибка сервера (подробнее см. в `Обработка ошибок`) + +Пример выполнения запроса: +``` +$ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/msg/verify_detached \ + -F "data=@data.csv;type=application/octet-stream" \ + -F "sign=@data.csv.sig;type=application/octet-stream" +* Trying 127.0.0.1:80... +* TCP_NODELAY set +* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0) +> POST /msg/verify_detached HTTP/1.1 +> Host: 127.0.0.1 +> User-Agent: curl/7.65.3-DEV +> Accept: */* +> Content-Length: 2770 +> Content-Type: multipart/form-data; boundary=------------------------d4c9f889765b7342 +> +< HTTP/1.1 200 OK +< Server: nginx/1.24.0 +< Date: Tue, 26 Aug 2025 08:58:18 GMT +< Content-Type: application/json +< Transfer-Encoding: chunked +< Connection: keep-alive +< +{ + "signer_subject":"C=RU, O=Организация, OU=Отдел тестирования отсоединенной подписи, CN=Фамилия Имя Отчество", + "issuer_name": "C=RU, O=УЦ, CN=GOST CA Root" +} +``` + ## Обработка ошибок В случае ошибки сервер возвращает ответ со статус-кодом `500 Internal Server Error` и телом в формате JSON, содержащим поле `error_code`. @@ -121,6 +159,9 @@ sign_cert_password = \*\*\*\* *\# пароль от контейнера* location = /verify *\# значение по умолчанию: /verify* esia_cert_thumbprint = sha1_thumbprint_of_esia_cert0,sha1_thumbprint_of_esia_cert1 *\# список SHA1 отпечатков сертификатов ЕСИА, указанных через запятую (без пробелов)* +- В секции **\[verify_detached\]** задать настройки проверки подписанного сообщения, содержащего отсоединённую подпись: +location = /msg/verify_detached *\# значение по умолчанию: /msg/verify_detached* + ## Логирование времени обработки запросов ***Для включения*** логирования времени обработки запросов необходимо задать переменную окружения SIGN_LOG_TIME: diff --git a/conf/ervu-sign-module.conf.example b/conf/ervu-sign-module.conf.example index 0fa7d13..8c7fe44 100644 --- a/conf/ervu-sign-module.conf.example +++ b/conf/ervu-sign-module.conf.example @@ -17,3 +17,6 @@ sign_cert_password = **** [verify] #location = /verify esia_cert_thumbprint = + +#[verify_detached] +#location = /msg/verify_detached diff --git a/conf/nginx-docker.conf b/conf/nginx-docker.conf index 7916831..d38d24f 100644 --- a/conf/nginx-docker.conf +++ b/conf/nginx-docker.conf @@ -1,6 +1,7 @@ server { listen 80; server_name localhost; + client_max_body_size 10M; location / { fastcgi_pass ervu-sign-module:9009; diff --git a/conf/nginx.conf b/conf/nginx.conf index a91ef19..092571a 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -21,6 +21,7 @@ http { proxy_temp_path /var/spool/nginx/tmp/proxy; fastcgi_temp_path /var/spool/nginx/tmp/fastcgi; client_body_temp_path /var/spool/nginx/tmp/client; + client_max_body_size 10M; include /etc/nginx/mime.types; default_type application/octet-stream; diff --git a/src/fcgisrv/fcgi_utils.c b/src/fcgisrv/fcgi_utils.c index 95a1654..f3958d9 100644 --- a/src/fcgisrv/fcgi_utils.c +++ b/src/fcgisrv/fcgi_utils.c @@ -1,6 +1,5 @@ #include "fcgi_server_internal.h" -#include "utils/logger.h" #include "utils/response_builder.h" #include @@ -48,7 +47,7 @@ check_content_length(int content_length, int client_max_body_size) fcgi_handler_status_t -check_content_type(const FCGX_Request* request, const char* acceptable_content_type) +check_content_type(const FCGX_Request* request, const str_t* acceptable_content_type, const char** actual_content_type) { LOG_TRACE("check_content_type"); @@ -58,13 +57,23 @@ check_content_type(const FCGX_Request* request, const char* acceptable_content_t return HANDLER_HTTP_BAD_REQUEST; } - if (strcmp(content_type, acceptable_content_type) == 0) { + if (actual_content_type) { + *actual_content_type = content_type; + } + + /* + * Проверяем, начинается ли заголовок Content-Type с допустимого префикса + * Сравнение производится только для начальной части строки + * (игнорируя дополнительные параметры после точки с запятой) + * Пример: "multipart/form-data;boundary=--123" является допустимым для "multipart/form-data" + */ + if (strncmp(content_type, acceptable_content_type->data, acceptable_content_type->len) == 0) { LOG_DEBUG("Content-Type is ok"); return HANDLER_SUCCESS; } - LOG_WARN("Content-Type is not acceptable, '%s' (!= '%s')", - content_type, acceptable_content_type); + LOG_WARN("Content-Type is not acceptable, '%s' (!= '%.*s')", + content_type, (int) acceptable_content_type->len, acceptable_content_type->data); return HANDLER_HTTP_NOT_ACCEPTABLE; } @@ -164,6 +173,47 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va return HANDLER_SUCCESS; } +fcgi_handler_status_t +fcgi_200_ok_handler(const FCGX_Request* request, const char* response) +{ + LOG_TRACE("fcgi_200_ok_handler"); + + const char *format = response ? FCGI_200_JSON_RESPONSE_FORMAT : FCGI_200_EMPTY_RESPONSE_FORMAT; + + if (response) { + LOG_DEBUG("response:\n" FCGI_200_JSON_RESPONSE_FORMAT, response); + } else { + LOG_DEBUG("response:\n" FCGI_200_EMPTY_RESPONSE_FORMAT); + } + + if (FCGX_FPrintF(request->out, format, response) < 0) { + LOG_ERROR("FCGX_FPrintF() failed"); + LOG_ERROR("fcgi_200_ok_handler exit with error"); + return HANDLER_ERROR; + } + + return HANDLER_SUCCESS; +} + +fcgi_handler_status_t +fcgi_401_bad_signature_handler(const FCGX_Request* request, const char* verify_error) +{ + LOG_TRACE("fcgi_401_bad_signature_handler"); + + assert(verify_error != NULL); + + LOG_DEBUG("response status: " FCGI_401_RESPONSE_FORMAT, verify_error); + + if (FCGX_FPrintF(request->out, FCGI_401_RESPONSE_FORMAT, verify_error) < 0) { + LOG_ERROR("FCGX_FPrintF() failed"); + return HANDLER_ERROR; + } + + fcgi_print_log(request, "401 Unauthorized"); + + return HANDLER_SUCCESS; +} + fcgi_handler_status_t fcgi_500_internal_server_error_handler(const FCGX_Request* request, const char *error_code) { diff --git a/src/fcgisrv/fcgi_utils.h b/src/fcgisrv/fcgi_utils.h index a7e195f..ab0de74 100644 --- a/src/fcgisrv/fcgi_utils.h +++ b/src/fcgisrv/fcgi_utils.h @@ -4,6 +4,7 @@ #include "fcgi_server.h" #include "utils/logger.h" +#include "utils/str_t.h" #include "stdbool.h" @@ -67,7 +68,7 @@ check_content_length(int content_length, int client_max_body_size); * Returns: HANDLER_SUCCESS, HANDLER_HTTP_BAD_REQUEST, HANDLER_HTTP_NOT_ACCEPTABLE */ fcgi_handler_status_t -check_content_type(const FCGX_Request* request, const char* acceptable_content_type); +check_content_type(const FCGX_Request* request, const str_t* acceptable_content_type, const char** actual_content_type); /** * Returns: HANDLER_SUCCESS, HANDLER_HTTP_BAD_REQUEST, HANDLER_ERROR @@ -89,10 +90,16 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va #define CRLF "\r\n" -#define FCGI_200_RESPONSE_FORMAT \ +#define FCGI_200_EMPTY_RESPONSE_FORMAT \ "Status: 200 OK" CRLF\ CRLF +#define FCGI_200_JSON_RESPONSE_FORMAT \ + "Status: 200 OK" CRLF\ + "Content-type: application/json" CRLF\ + CRLF\ + "%s" CRLF + #define FCGI_400_RESPONSE_FORMAT \ "Status: 400 Bad Request" CRLF\ "Content-type: text/plain" CRLF\ @@ -103,7 +110,7 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va "Status: 401 Unauthorized" CRLF\ "Content-type: text/plain" CRLF\ CRLF\ - "401 Unauthorized" CRLF + "%s" CRLF #define FCGI_404_RESPONSE_FORMAT \ "Status: 404 Not Found" CRLF\ @@ -158,12 +165,8 @@ fcgi_print_log(const FCGX_Request* request, const char* status) LOG_INFO("%s %s, '%s'", method, uri, status); } -static inline fcgi_handler_status_t -fcgi_200_ok_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) -{ - LOG_DEBUG("response status: '200 OK'"); - return FCGI_CHECK_PRINTF_STATUS(request, FCGI_200_RESPONSE_FORMAT, 200); -} +fcgi_handler_status_t +fcgi_200_ok_handler(const FCGX_Request* request, const char* response); static inline fcgi_handler_status_t fcgi_400_bad_request_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) @@ -173,12 +176,8 @@ fcgi_400_bad_request_handler(const FCGX_Request* request, void* ctx __attribute_ } -static inline fcgi_handler_status_t -fcgi_401_unauthorized_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) -{ - fcgi_print_log(request, "401 Unauthorized"); - return FCGI_CHECK_PRINTF_STATUS(request, FCGI_401_RESPONSE_FORMAT, 401); -} +fcgi_handler_status_t +fcgi_401_bad_signature_handler(const FCGX_Request* request, const char* verify_error); static inline fcgi_handler_status_t diff --git a/src/modules/service_sign.c b/src/modules/service_sign.c index 32e8ea9..5efd446 100644 --- a/src/modules/service_sign.c +++ b/src/modules/service_sign.c @@ -12,16 +12,10 @@ #define SIGN_CONF_KEY_SIGN_CERT_THUMBPRINT "sign_cert_thumbprint" #define SIGN_CONF_KEY_SIGN_CERT_PASSWORD "sign_cert_password" -#define FCGI_OK_RESPONSE_FORMAT \ - "Content-type: application/json" CRLF\ - CRLF\ - "%s" CRLF - - static const str_t SIGN_CONF_DEFAULT_LOCATION = str_t_const("/sign"); static const int CLIENT_MAX_BODY_SIZE = 4096; -static const char* ACCEPTABLE_CONTENT_TYPE = "text/plain"; +static const str_t ACCEPTABLE_CONTENT_TYPE = str_t_const("text/plain"); typedef struct sign_service_s { const sign_conf_t *conf; @@ -194,7 +188,7 @@ fcgi_sign_handler(FCGX_Request* request, void* ctx) goto exit; } - status = check_content_type(request, ACCEPTABLE_CONTENT_TYPE); + status = check_content_type(request, &ACCEPTABLE_CONTENT_TYPE, NULL); if (status != HANDLER_SUCCESS) { goto exit; } @@ -238,31 +232,14 @@ fcgi_sign_request_clear(fcgi_sign_request_t *req_info) memset(req_info, 0, sizeof(fcgi_sign_request_t)); } -static fcgi_handler_status_t -fcgi_ok_handler(const FCGX_Request* request, const fcgi_sign_request_t *req_info) -{ - LOG_TRACE("fcgi_ok_handler"); - - LOG_DEBUG("response status: " FCGI_OK_RESPONSE_FORMAT, req_info->response); - - if (FCGX_FPrintF(request->out, FCGI_OK_RESPONSE_FORMAT, req_info->response) < 0) { - LOG_ERROR("FCGX_FPrintF() failed"); - return HANDLER_ERROR; - } - - return HANDLER_SUCCESS; -} - static fcgi_handler_status_t fcgi_request_finalize(const FCGX_Request* request, fcgi_sign_request_t *req_info, fcgi_handler_status_t status) { switch (status) { case HANDLER_SUCCESS: - return fcgi_ok_handler(request, req_info); - case HANDLER_HTTP_OK: - return fcgi_200_ok_handler(request, req_info); + return fcgi_200_ok_handler(request, req_info->response); case HANDLER_HTTP_BAD_REQUEST: return fcgi_400_bad_request_handler(request, req_info); diff --git a/src/modules/service_verify.c b/src/modules/service_verify.c index 1e242f4..df406ca 100644 --- a/src/modules/service_verify.c +++ b/src/modules/service_verify.c @@ -11,16 +11,10 @@ #define VERIFY_CONF_KEY_LOCATION "location" #define VERIFY_CONF_KEY_THUMBPRINT "esia_cert_thumbprint" -#define FCGI_401_BAD_SIGNATURE_RESPONSE_FORMAT \ - "Status: 401 Unauthorized" CRLF\ - "Content-type: text/plain" CRLF\ - CRLF\ - "%s" CRLF - static const str_t VERIFY_CONF_DEFAULT_LOCATION = str_t_const("/verify"); static const int CLIENT_MAX_BODY_SIZE = 8192; -static const char* ACCEPTABLE_CONTENT_TYPE = "text/plain"; +static const str_t ACCEPTABLE_CONTENT_TYPE = str_t_const("text/plain"); typedef struct verify_service_s { @@ -170,7 +164,7 @@ fcgi_verify_handler(FCGX_Request* request, void* ctx) goto exit; } - status = check_content_type(request, ACCEPTABLE_CONTENT_TYPE); + status = check_content_type(request, &ACCEPTABLE_CONTENT_TYPE, NULL); if (status != HANDLER_SUCCESS) { goto exit; } @@ -191,29 +185,6 @@ exit: return status; } -static fcgi_handler_status_t -fcgi_401_bad_signature_handler(const FCGX_Request* request, void *ctx) -{ - LOG_TRACE("fcgi_401_bad_signature_handler"); - - const fcgi_verify_request_t *req_info = (fcgi_verify_request_t*) ctx; - - assert(req_info->verify_error != NULL); - - LOG_DEBUG("response status: " FCGI_401_BAD_SIGNATURE_RESPONSE_FORMAT, - req_info->verify_error); - - if (FCGX_FPrintF(request->out, FCGI_401_BAD_SIGNATURE_RESPONSE_FORMAT, - req_info->verify_error) < 0) { - LOG_ERROR("FCGX_FPrintF() failed"); - return HANDLER_ERROR; - } - - fcgi_print_log(request, "401 Unauthorized"); - - return HANDLER_SUCCESS; -} - static fcgi_handler_status_t fcgi_request_finalize(const FCGX_Request* request, fcgi_verify_request_t *req_info, fcgi_handler_status_t status) @@ -221,13 +192,13 @@ fcgi_request_finalize(const FCGX_Request* request, fcgi_verify_request_t *req_in switch (status) { case HANDLER_SUCCESS: case HANDLER_HTTP_OK: - return fcgi_200_ok_handler(request, req_info); + return fcgi_200_ok_handler(request, NULL); case HANDLER_HTTP_BAD_REQUEST: return fcgi_400_bad_request_handler(request, req_info); case HANDLER_HTTP_UNAUTHORIZED: - return fcgi_401_bad_signature_handler(request, req_info); + return fcgi_401_bad_signature_handler(request, req_info->verify_error); case HANDLER_HTTP_NOT_ACCEPTABLE: return fcgi_406_not_acceptable_handler(request, req_info); @@ -315,24 +286,12 @@ verify_jwt_sign(fcgi_verify_request_t* req_info, verify_service_t *ctx) goto error; } - sign.len = base64_decoded_length(sign_base64.len); - sign.data = calloc(1, sign.len + 1); - if (sign.data == NULL) { - LOG_ERROR("Could not allocate memory for decoded sign (%zd bytes)", sign.len + 1); - goto error; - } - if (decode_base64_url(&sign, &sign_base64)) { + if (decode_base64_url_with_alloc(&sign, &sign_base64)) { LOG_ERROR("Could not decode jwt sign"); goto error; } - header.len = base64_decoded_length(header_base64.len); - header.data = calloc(1, header.len + 1); - if (header.data == NULL) { - LOG_ERROR("Could not allocate memory for decoded header (%zd bytes)", header.len + 1); - goto error; - } - if (decode_base64_url(&header, &header_base64)) { + if (decode_base64_url_with_alloc(&header, &header_base64)) { LOG_ERROR("Could not decode jwt header"); goto error; } diff --git a/src/modules/service_verify_detached.c b/src/modules/service_verify_detached.c new file mode 100644 index 0000000..e5a8903 --- /dev/null +++ b/src/modules/service_verify_detached.c @@ -0,0 +1,275 @@ +#include "service_verify_detached.h" + +#include "fcgisrv/fcgi_utils.h" + +#include "utils/cryptopro.h" +#include "utils/detached_sign_payload_parser.h" +#include "utils/logger.h" +#include "utils/response_builder.h" + + +#define VERIFY_DETACHED_CONF_SECTION "verify_detached" +#define VERIFY_DETACHED_CONF_KEY_LOCATION "location" + +static const str_t VERIFY_DETACHED_CONF_DEFAULT_LOCATION = str_t_const("/msg/verify_detached"); + +static const int CLIENT_MAX_BODY_SIZE = 10 * 1024 * 1024; // 10 MB +static const str_t ACCEPTABLE_CONTENT_TYPE = str_t_const("multipart/form-data"); + + +typedef struct verify_detached_service_s { + const verify_detached_conf_t *conf; + + cryptopro_context_t cryptopro_ctx; + + timer_context_t timer_ctx; + +} verify_detached_service_t; + + +typedef struct fcgi_verify_detached_request_s { + char *content; + int content_length; + const char *content_type; + str_t data; + str_t sign; + + char *response; + cert_info_t signer_cert; + char *verify_error; // клиентская ошибка (подпись невалидна) - статус 401 + const char *error_code; // серверная ошибка - статус 500 + +} fcgi_verify_detached_request_t; + + +static fcgi_handler_status_t fcgi_request_parse_content(fcgi_verify_detached_request_t *req_info); +static void fcgi_verify_detached_request_clear(fcgi_verify_detached_request_t *req_info); +static fcgi_handler_status_t verify_detached(fcgi_verify_detached_request_t* req_info, verify_detached_service_t *ctx); +static fcgi_handler_status_t fcgi_request_finalize(const FCGX_Request* request, + fcgi_verify_detached_request_t *req_info, + fcgi_handler_status_t status); + + +int +verify_detached_conf_load(verify_detached_conf_t *conf, const conf_file_context_t conf_file) +{ + LOG_TRACE("verify_detached_conf_load enter"); + + memset(conf, 0, sizeof(verify_detached_conf_t)); + + conf_file_field_t fields[] = { + { + VERIFY_DETACHED_CONF_SECTION, + VERIFY_DETACHED_CONF_KEY_LOCATION, + &conf->location, + CONF_FILE_VALUE_STRT, + CONF_FILE_VALUE_LOCATION, + &VERIFY_DETACHED_CONF_DEFAULT_LOCATION + } + }; + + if (conf_file_load_values(conf_file, fields, sizeof(fields) / sizeof(conf_file_field_t))) { + goto error; + } + + LOG_TRACE("verify_detached_conf_load exit"); + return 0; + +error: + verify_detached_conf_clear(conf); + LOG_ERROR("verify_detached_conf_load exit with error"); + return -1; +} + +void +verify_detached_conf_clear(verify_detached_conf_t *conf) +{ + LOG_TRACE("verify_detached_conf_clear"); + + if (conf == NULL) return; + + str_t_clear(&conf->location); + + memset(conf, 0, sizeof(verify_detached_conf_t)); +} + +HVerifyDetached +verify_detached_service_create(const verify_detached_conf_t *conf, const main_conf_t *main_conf) +{ + verify_detached_service_t *hverify; + LOG_TRACE("verify_detached_service_create enter"); + + hverify = (verify_detached_service_t*) calloc(1, sizeof(verify_detached_service_t)); + if (hverify == NULL) { + LOG_ERROR("Could not allocate memory for HVerifyDetached"); + goto error; + } + + hverify->conf = conf; + + init_timers(&hverify->timer_ctx); + + cryptopro_context_set(&hverify->cryptopro_ctx, + NULL, + NULL, + main_conf->cp_name, + main_conf->cp_type, + &hverify->timer_ctx); + + LOG_TRACE("verify_detached_service_create exit"); + return (HVerifyDetached)hverify; + +error: + verify_detached_service_free((HVerifyDetached)hverify); + LOG_ERROR("verify_detached_service_create exit with error"); + return NULL; +} + +void +verify_detached_service_free(HVerifyDetached hverify) +{ + LOG_TRACE("verify_detached_service_free"); + + verify_detached_service_t *ctx = (verify_detached_service_t *) hverify; + + if (ctx == NULL) return; + + free(ctx); +} + +fcgi_handler_status_t +fcgi_verify_detached_handler(FCGX_Request* request, void* ctx) +{ + fcgi_handler_status_t status = HANDLER_ERROR; + fcgi_verify_detached_request_t req_info = {0}; + + LOG_TRACE("fcgi_verify_detached_handler enter"); + + if (request == NULL) { + LOG_ERROR("fcgi_verify_detached_handler exit with error. Desc: request is NULL"); + return HANDLER_ERROR; + } + + req_info.content_length = fcgi_get_content_length(request); + + status = check_content_length(req_info.content_length, CLIENT_MAX_BODY_SIZE); + if (status != HANDLER_SUCCESS) { + goto exit; + } + + status = check_content_type(request, &ACCEPTABLE_CONTENT_TYPE, &req_info.content_type); + if (status != HANDLER_SUCCESS) { + goto exit; + } + + // Проверяем, что функция check_content_type() вернула req_info.content_type + assert(req_info.content_type != NULL); + + status = fcgi_request_load_data(request, req_info.content_length, &req_info.content); + if (status != HANDLER_SUCCESS) { + goto exit; + } + + status = fcgi_request_parse_content(&req_info); + if (status != HANDLER_SUCCESS) { + goto exit; + } + + status = verify_detached(&req_info, ctx); + if (status != HANDLER_SUCCESS) { + goto exit; + } + + req_info.response = build_response_from_signer_cert(&req_info.signer_cert); + if (req_info.response == NULL) { + goto exit; + } + + // status = HANDLER_SUCCESS; + +exit: + status = fcgi_request_finalize(request, &req_info, status); + + fcgi_verify_detached_request_clear(&req_info); + + LOG_TRACE("fcgi_verify_detached_handler exit"); + return status; +} + +static fcgi_handler_status_t +fcgi_request_parse_content(fcgi_verify_detached_request_t *req_info) +{ + LOG_TRACE("fcgi_request_parse_content enter"); + + if (parse_detached_sign_payload(req_info->content_type, req_info->content, req_info->content_length, + &req_info->data, &req_info->sign)) { + LOG_ERROR("fcgi_request_parse_content exit with error"); + return HANDLER_ERROR; + } + + LOG_TRACE("fcgi_request_parse_content exit"); + return HANDLER_SUCCESS; +} + +static void +fcgi_verify_detached_request_clear(fcgi_verify_detached_request_t *req_info) +{ + LOG_TRACE("fcgi_verify_detached_request_clear"); + + free(req_info->content); + free(req_info->response); + free(req_info->verify_error); + + cert_info_clear(&req_info->signer_cert); + + memset(req_info, 0, sizeof(fcgi_verify_detached_request_t)); +} + +static fcgi_handler_status_t +verify_detached(fcgi_verify_detached_request_t* req_info, verify_detached_service_t *ctx) +{ + LOG_TRACE("verify_detached enter"); + + bool is_verified = false; + + if (cryptopro_verify_detached(&ctx->cryptopro_ctx, &req_info->data, &req_info->sign, &is_verified, + &req_info->signer_cert, &req_info->verify_error, &req_info->error_code)) { + goto error; + } + + LOG_TRACE("verify_detached exit"); + return is_verified ? HANDLER_SUCCESS : HANDLER_HTTP_UNAUTHORIZED; + +error: + LOG_ERROR("verify_detached exit with error"); + return HANDLER_ERROR; +} + +static fcgi_handler_status_t +fcgi_request_finalize(const FCGX_Request* request, fcgi_verify_detached_request_t *req_info, + fcgi_handler_status_t status) +{ + switch (status) { + case HANDLER_SUCCESS: + case HANDLER_HTTP_OK: + return fcgi_200_ok_handler(request, req_info->response); + + case HANDLER_HTTP_BAD_REQUEST: + return fcgi_400_bad_request_handler(request, req_info); + + case HANDLER_HTTP_UNAUTHORIZED: + return fcgi_401_bad_signature_handler(request, req_info->verify_error); + + case HANDLER_HTTP_NOT_ACCEPTABLE: + return fcgi_406_not_acceptable_handler(request, req_info); + + case HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE: + return fcgi_413_request_entity_too_large_handler(request, req_info); + + case HANDLER_ERROR: + default: + break; + } + + return fcgi_500_internal_server_error_handler(request, req_info->error_code); +} diff --git a/src/modules/service_verify_detached.h b/src/modules/service_verify_detached.h new file mode 100644 index 0000000..89ed636 --- /dev/null +++ b/src/modules/service_verify_detached.h @@ -0,0 +1,29 @@ +#ifndef SERVICE_VERIFY_DETACHED_H_INCLUDED +#define SERVICE_VERIFY_DETACHED_H_INCLUDED + + +#include "main_conf.h" + +#include "fcgisrv/fcgi_server.h" + +#include "utils/str_t.h" + + +typedef struct verify_detached_service_t* HVerifyDetached; + +typedef struct verify_detached_conf_s { + str_t location; + +} verify_detached_conf_t; + + +int verify_detached_conf_load(verify_detached_conf_t *conf, const conf_file_context_t conf_file); +void verify_detached_conf_clear(verify_detached_conf_t *conf); + +HVerifyDetached verify_detached_service_create(const verify_detached_conf_t *conf, const main_conf_t *main_conf); +void verify_detached_service_free(HVerifyDetached hverify); + +fcgi_handler_status_t fcgi_verify_detached_handler(FCGX_Request* request, void* ctx); + + +#endif // SERVICE_VERIFY_DETACHED_H_INCLUDED \ No newline at end of file diff --git a/src/service_manager.c b/src/service_manager.c index b432990..4f47dca 100644 --- a/src/service_manager.c +++ b/src/service_manager.c @@ -166,6 +166,11 @@ service_manager_load_conf(const char* config_file_name, service_manager_conf_t* goto error; } + if (verify_detached_conf_load(&services_cf->verify_detached_cf, conf_file)) { + LOG_ERROR("Verify detached message signature service configuraton loading failed"); + goto error; + } + if (version_conf_load(&services_cf->version_cf)) { LOG_ERROR("Show version service configuraton loading failed"); goto error; @@ -193,6 +198,7 @@ service_manager_clear_conf(service_manager_conf_t* services_cf) version_conf_clear(&services_cf->version_cf); sign_conf_clear(&services_cf->sign_cf); verify_conf_clear(&services_cf->verify_cf); + verify_detached_conf_clear(&services_cf->verify_detached_cf); fcgi_clear_conf(&services_cf->fcgi_cf); main_conf_clear(&services_cf->main_cf); } @@ -212,6 +218,8 @@ deinit_services(service_manager_t* services) verify_service_free(services->hverify); + verify_detached_service_free(services->hverify_detached); + LOG_TRACE("deinit_services exit"); return 0; @@ -262,6 +270,19 @@ init_services(service_manager_t* services, const service_manager_conf_t* service goto error; } + /* verify detached message signature service */ + services->hverify_detached = verify_detached_service_create(&services_cf->verify_detached_cf, + &services_cf->main_cf); + if (services->hverify_detached == NULL) { + goto error; + } + + if (register_service(services, services_cf, fcgi_verify_detached_handler, services->hverify_detached, + &services_cf->verify_detached_cf.location)) { + LOG_ERROR("Could not register 'verify detached message signature service'"); + goto error; + } + /* show version service */ if (register_service(services, services_cf, fcgi_version_handler, NULL, services_cf->version_cf.location)) { diff --git a/src/service_manager.h b/src/service_manager.h index 7c30f88..8710b37 100644 --- a/src/service_manager.h +++ b/src/service_manager.h @@ -9,6 +9,7 @@ #include "modules/service_sign.h" #include "modules/service_verify.h" +#include "modules/service_verify_detached.h" #include "modules/service_version.h" struct service_s; @@ -45,11 +46,12 @@ struct service_s { typedef struct service_manager_conf_s { - fcgi_conf_t fcgi_cf; - main_conf_t main_cf; - sign_conf_t sign_cf; - verify_conf_t verify_cf; - version_conf_t version_cf; + fcgi_conf_t fcgi_cf; + main_conf_t main_cf; + sign_conf_t sign_cf; + verify_conf_t verify_cf; + verify_detached_conf_t verify_detached_cf; + version_conf_t version_cf; } service_manager_conf_t; @@ -57,6 +59,7 @@ typedef struct service_manager_s { HFcgi hfcgi; HSign hsign; HVerify hverify; + HVerifyDetached hverify_detached; } service_manager_t; diff --git a/src/utils/base64.c b/src/utils/base64.c index 4c9381e..5794ee2 100644 --- a/src/utils/base64.c +++ b/src/utils/base64.c @@ -167,3 +167,31 @@ error: LOG_ERROR("decode_base64_url exit with error"); return -1; } + +int +decode_base64_url_with_alloc(str_t *dst, const str_t *src) +{ + LOG_TRACE("decode_base64_url_with_alloc enter"); + + dst->len = base64_decoded_length(src->len); + + dst->data = calloc(1, dst->len + 1); + if (dst->data == NULL) { + LOG_ERROR("Could not allocate memory for decoded data (%zd bytes)", dst->len + 1); + goto error; + } + + if (decode_base64_url(dst, src)) { + LOG_ERROR("Could not decode data"); + goto error; + } + + LOG_TRACE("decode_base64_url_with_alloc exit"); + return 0; + +error: + str_t_clear(dst); + LOG_ERROR("decode_base64_url_with_alloc exit with error"); + return -1; + +} diff --git a/src/utils/base64.h b/src/utils/base64.h index 6ec70e4..43bd210 100644 --- a/src/utils/base64.h +++ b/src/utils/base64.h @@ -9,4 +9,6 @@ void encode_base64_url(str_t *dst, const str_t *src); int decode_base64_url(str_t *dst, const str_t *src); +int decode_base64_url_with_alloc(str_t *dst, const str_t *src); + #endif // BASE64_H \ No newline at end of file diff --git a/src/utils/capi.c b/src/utils/capi.c index 83f8356..aa0c7e6 100644 --- a/src/utils/capi.c +++ b/src/utils/capi.c @@ -32,15 +32,22 @@ capi_function_list_init(library_t *lib, capi_function_list_t *fl) LIBRARY_RESOLVE(fl->CertFreeCertificateChain, lib, "CertFreeCertificateChain"); LIBRARY_RESOLVE(fl->CryptGenRandom, lib, "CryptGenRandom"); LIBRARY_RESOLVE(fl->CertGetIntendedKeyUsage, lib, "CertGetIntendedKeyUsage"); + LIBRARY_RESOLVE(fl->CryptVerifyDetachedMessageSignature, lib, "CryptVerifyDetachedMessageSignature"); + LIBRARY_RESOLVE(fl->CryptGetMessageSignerCount, lib, "CryptGetMessageSignerCount"); + LIBRARY_RESOLVE(fl->CertFindExtension, lib, "CertFindExtension"); + LIBRARY_RESOLVE(fl->CryptDecodeObjectEx, lib, "CryptDecodeObjectEx"); + LIBRARY_RESOLVE(fl->LocalFree, lib, "LocalFree"); #ifdef UNICODE LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashW"); LIBRARY_RESOLVE(fl->CryptVerifySignature, lib, "CryptVerifySignatureW"); LIBRARY_RESOLVE(fl->CryptAcquireContext, lib, "CryptAcquireContextW"); + LIBRARY_RESOLVE(fl->CertNameToStr, lib, "CertNameToStrW"); #else LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashA"); LIBRARY_RESOLVE(fl->CryptVerifySignature, lib, "CryptVerifySignatureA"); LIBRARY_RESOLVE(fl->CryptAcquireContext, lib, "CryptAcquireContextA"); + LIBRARY_RESOLVE(fl->CertNameToStr, lib, "CertNameToStrA"); #endif // !UNICODE return true; diff --git a/src/utils/capi.h b/src/utils/capi.h index 4ea512b..d0012df 100644 --- a/src/utils/capi.h +++ b/src/utils/capi.h @@ -134,6 +134,18 @@ DECLARE_FN(WINADVAPI, LPCWSTR szDescription, DWORD dwFlags)); +DECLARE_FN(WINADVAPI, + BOOL, + CRYPT_VERIFY_DETACHED_MESSAGE_SIGNATURE, + (PCRYPT_VERIFY_MESSAGE_PARA pVerifyPara, + DWORD dwSignerIndex, + const BYTE *pbDetachedSignBlob, + DWORD cbDetachedSignBlob, + DWORD cToBeSigned, + const BYTE *rgpbToBeSigned[], + DWORD rgcbToBeSigned[], + PCCERT_CONTEXT *ppSignerCert)); + DECLARE_FN(WINADVAPI, BOOL, CRYPT_ACQUIRE_CONTEXT_A, @@ -205,14 +217,65 @@ DECLARE_FN(WINADVAPI, BYTE *pbKeyUsage, IN DWORD cbKeyUsage)); +DECLARE_FN(WINADVAPI, + DWORD, + CERT_NAME_TO_STR_A, + (DWORD dwCertEncodingType, + PCERT_NAME_BLOB pName, + DWORD dwStrType, + LPSTR psz, + DWORD csz)); + +DECLARE_FN(WINADVAPI, + DWORD, + CERT_NAME_TO_STR_W, + (DWORD dwCertEncodingType, + PCERT_NAME_BLOB pName, + DWORD dwStrType, + LPWSTR psz, + DWORD csz)); + +DECLARE_FN(WINADVAPI, + LONG, + CRYPT_GET_MESSAGE_SIGNER_COUNT, + (DWORD dwMsgEncodingType, + const BYTE *pbSignedBlob, + DWORD cbSignedBlob)); + +DECLARE_FN(WINADVAPI, + PCERT_EXTENSION, + CERT_FIND_EXTENSION, + (LPCSTR pszObjId, + DWORD cExtensions, + CERT_EXTENSION rgExtensions[])); + +DECLARE_FN(WINADVAPI, + BOOL, + CRYPT_DECODE_OBJECT_EX, + (DWORD dwCertEncodingType, + LPCSTR lpszStructType, + const BYTE *pbEncoded, + DWORD cbEncoded, + DWORD dwFlags, + PCRYPT_DECODE_PARA pDecodePara, + void *pvStructInfo, + DWORD *pcbStructInfo)); + +DECLARE_FN(WINADVAPI, + HLOCAL, + LOCAL_FREE, + (HLOCAL hMem)); + #ifdef UNICODE #define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_W_FN #define CRYPT_VERIFY_SIGNATURE_FN CRYPT_VERIFY_SIGNATURE_W_FN #define CRYPT_ACQUIRE_CONTEXT_FN CRYPT_ACQUIRE_CONTEXT_W_FN +#define CERT_NAME_TO_STR_FN CERT_NAME_TO_STR_W_FN #else #define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_A_FN #define CRYPT_VERIFY_SIGNATURE_FN CRYPT_VERIFY_SIGNATURE_A_FN #define CRYPT_ACQUIRE_CONTEXT_FN CRYPT_ACQUIRE_CONTEXT_A_FN +#define CERT_NAME_TO_STR_FN CERT_NAME_TO_STR_A_FN #endif // !UNICODE @@ -231,6 +294,7 @@ typedef struct { CERT_GET_CERTIFICATE_CONTEXT_PROPERTY_FN CertGetCertificateContextProperty; CERT_SET_CERTIFICATE_CONTEXT_PROPERTY_FN CertSetCertificateContextProperty; CRYPT_VERIFY_SIGNATURE_FN CryptVerifySignature; + CRYPT_VERIFY_DETACHED_MESSAGE_SIGNATURE_FN CryptVerifyDetachedMessageSignature; CRYPT_ACQUIRE_CONTEXT_FN CryptAcquireContext; CRYPT_IMPORT_PUBLIC_KEY_INFO_FN CryptImportPublicKeyInfo; CRYPT_DESTROY_KEY_FN CryptDestroyKey; @@ -239,6 +303,11 @@ typedef struct { CERT_FREE_CERTIFICATE_CHAIN_FN CertFreeCertificateChain; CRYPT_GEN_RANDOM_FN CryptGenRandom; CERT_GET_INTENDED_KEY_USAGE_FN CertGetIntendedKeyUsage; + CERT_NAME_TO_STR_FN CertNameToStr; + CRYPT_GET_MESSAGE_SIGNER_COUNT_FN CryptGetMessageSignerCount; + CERT_FIND_EXTENSION_FN CertFindExtension; + CRYPT_DECODE_OBJECT_EX_FN CryptDecodeObjectEx; + LOCAL_FREE_FN LocalFree; } capi_function_list_t; diff --git a/src/utils/cryptopro.c b/src/utils/cryptopro.c index cfbba5d..6c38b1f 100644 --- a/src/utils/cryptopro.c +++ b/src/utils/cryptopro.c @@ -100,13 +100,75 @@ free_cert_chain(PCCERT_CHAIN_CONTEXT chain_ctx) } static void -process_trust_status_error(DWORD status, /*out*/ const char **error_code) +print_crl_urls_from_certificate(PCCERT_CONTEXT cert) +{ + LOG_TRACE("print_crl_urls_from_certificate enter"); + + PCERT_EXTENSION extension = cp_function_list.CertFindExtension( + szOID_CRL_DIST_POINTS, + cert->pCertInfo->cExtension, + cert->pCertInfo->rgExtension + ); + + if (!extension) { + LOG_WARN("CRL Distribution Points extension not found"); + goto error; + } + + DWORD info_size = 0; + CRL_DIST_POINTS_INFO *crl_dist_points = NULL; + + if (!cp_function_list.CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + X509_CRL_DIST_POINTS, + extension->Value.pbData, + extension->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + NULL, + &crl_dist_points, + &info_size + )) { + LOG_ERROR("CryptDecodeObjectEx failed. Desc: 0x%x", cp_function_list.GetLastError()); + goto error; + } + + if (crl_dist_points && crl_dist_points->cDistPoint > 0) { + for (DWORD i = 0; i < crl_dist_points->cDistPoint; i++) { + PCRL_DIST_POINT pDistPoint = &crl_dist_points->rgDistPoint[i]; + + if (pDistPoint->DistPointName.dwDistPointNameChoice == CRL_DIST_POINT_FULL_NAME) { + PCERT_ALT_NAME_ENTRY pAltNameEntry = pDistPoint->DistPointName._empty_union_.FullName.rgAltEntry; + + for (DWORD j = 0; j < pDistPoint->DistPointName._empty_union_.FullName.cAltEntry; j++) { + if (pAltNameEntry[j].dwAltNameChoice == CERT_ALT_NAME_URL) { + LOG_INFO("CRL URL: %ls", pAltNameEntry[j]._empty_union_.pwszURL); + } + } + } + } + } + + if (crl_dist_points) { + cp_function_list.LocalFree(crl_dist_points); + } + + LOG_TRACE("print_crl_urls_from_certificate exit"); + return; + +error: + LOG_ERROR("print_crl_urls_from_certificate exit with error"); + return; +} + +static void +process_trust_status_error(PCCERT_CONTEXT cert, DWORD status, /*out*/ const char **error_code) { LOG_TRACE("process_trust_status_error enter"); 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; @@ -153,7 +215,7 @@ get_cert_chain(PCCERT_CONTEXT certificate, /*out*/ const char **error_code) } if (chain_ctx->TrustStatus.dwErrorStatus) { - process_trust_status_error(chain_ctx->TrustStatus.dwErrorStatus, error_code); + process_trust_status_error(certificate, chain_ctx->TrustStatus.dwErrorStatus, error_code); goto error; } @@ -220,6 +282,8 @@ verify_cert_chain(PCCERT_CONTEXT certificate, timer_context_t *timer_ctx, /*out* LOG_TRACE("verify_cert_chain enter"); + timer_on_verify_cert_chain_enter(timer_ctx); + timer_on_get_cert_chain_enter(timer_ctx); PCCERT_CHAIN_CONTEXT chain_ctx = get_cert_chain(certificate, error_code); @@ -240,6 +304,8 @@ exit: LOG_DEBUG("cert_chain: %s", is_verified ? "verified" : "failed"); + timer_on_verify_cert_chain_exit(timer_ctx); + LOG_DEBUG("verify_cert_chain exit"); return is_verified; } @@ -293,7 +359,6 @@ open_signer_cert(cryptopro_context_t *ctx) goto error; } - LOG_TRACE("open_signer_cert exit"); return 0; @@ -353,14 +418,10 @@ sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t BYTE *pbSignedMessageBlob; DWORD cbSignedMessageBlob; - timer_on_verify_cert_chain_enter(ctx->timer_ctx); - if (!verify_cert_chain(ctx->signer_cert, ctx->timer_ctx, error_code)) { goto exit; } - timer_on_verify_cert_chain_exit(ctx->timer_ctx); - timer_on_acquire_private_key_enter(ctx->timer_ctx); if (!cp_function_list.CryptAcquireCertificatePrivateKey( @@ -394,6 +455,10 @@ sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t } pbSignedMessageBlob = malloc(cbSignedMessageBlob * sizeof(BYTE)); + if (pbSignedMessageBlob == NULL) { + LOG_ERROR("Could not allocate memory for pbSignedMessageBlob (%zd bytes)", cbSignedMessageBlob * sizeof(BYTE)); + goto exit; + } if (!cp_function_list.CryptSignHash(hash, AT_KEYEXCHANGE, NULL, 0, pbSignedMessageBlob, &cbSignedMessageBlob)) { LOG_ERROR("CryptSignHash() failed"); @@ -703,6 +768,8 @@ get_verify_error(char** verify_error) return; } + LOG_DEBUG("verify error: %s", *verify_error); + LOG_TRACE("get_verify_error exit"); } @@ -743,14 +810,10 @@ cryptopro_verify(cryptopro_context_t* ctx, const str_t* alg, const str_t* data, goto exit; } - timer_on_verify_cert_chain_enter(ctx->timer_ctx); - if (!verify_cert_chain(certificate, ctx->timer_ctx, error_code)) { goto exit; } - timer_on_verify_cert_chain_exit(ctx->timer_ctx); - LOG_DEBUG("provider: '%s', prov_type: %u", ctx->provider, ctx->prov_type); if (!cp_function_list.CryptAcquireContext(&hCryptProv, NULL, ctx->provider, ctx->prov_type, @@ -842,6 +905,224 @@ exit: return rc; } +static bool +get_subject_name(PCCERT_CONTEXT cert, /*out*/ char **subject_name) +{ + LOG_TRACE("get_subject_name enter"); + + DWORD len = cp_function_list.CertNameToStr( + cert->dwCertEncodingType, + &cert->pCertInfo->Subject, + CERT_X500_NAME_STR, + NULL, + 0 + ); + + *subject_name = malloc(len); + if (*subject_name == NULL) { + LOG_ERROR("Could not allocate memory for Subject Name str (%d bytes)", len); + goto error; + } + + cp_function_list.CertNameToStr( + cert->dwCertEncodingType, + &cert->pCertInfo->Subject, + CERT_X500_NAME_STR, + *subject_name, + len + ); + + LOG_DEBUG("Subject Name: %s", *subject_name); + + LOG_TRACE("get_subject_name exit"); + return true; + +error: + free(*subject_name); + LOG_ERROR("get_subject_name exit with error"); + return false; +} + +static bool +get_issuer_name(PCCERT_CONTEXT cert, /*out*/ char **issuer_name) +{ + LOG_TRACE("get_issuer_name enter"); + + DWORD len = cp_function_list.CertNameToStr( + cert->dwCertEncodingType, + &cert->pCertInfo->Issuer, + CERT_X500_NAME_STR, + NULL, + 0 + ); + + *issuer_name = malloc(len); + if (*issuer_name == NULL) { + LOG_ERROR("Could not allocate memory for Issuer Name str (%d bytes)", len); + goto error; + } + + cp_function_list.CertNameToStr( + cert->dwCertEncodingType, + &cert->pCertInfo->Issuer, + CERT_X500_NAME_STR, + *issuer_name, + len + ); + + LOG_DEBUG("Issuer Name: %s", *issuer_name); + + LOG_TRACE("get_issuer_name exit"); + return true; + +error: + free(*issuer_name); + LOG_ERROR("get_issuer_name exit with error"); + return false; +} + +static bool +get_signer_cert_info(PCCERT_CONTEXT signer_cert, cert_info_t *signer_cert_info) +{ + LOG_TRACE("get_signer_cert_info enter"); + + if (!get_subject_name(signer_cert, &signer_cert_info->subject)) { + goto error; + } + + if (!get_issuer_name(signer_cert, &signer_cert_info->issuer)) { + goto error; + } + + LOG_TRACE("get_signer_cert_info exit"); + return true; + +error: + LOG_ERROR("get_signer_cert_info exit with error"); + return false; +} + +/* + * Проверка количества подписантов сообщения + * Предполагается, что количество подписантов должно быть равно 1 + */ +static bool +check_message_signer_count(const str_t *sign) +{ + int signer_count = cp_function_list.CryptGetMessageSignerCount( + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + (const BYTE *) sign->data, + (DWORD) sign->len); + + LOG_DEBUG("signer count = %d", signer_count); + + if (signer_count == 1) { + LOG_DEBUG("signer count is ok"); + return true; + } + + if (signer_count < 0) { + LOG_ERROR("Could not get signer count. Last error code: 0x%08x", + cp_function_list.GetLastError()); + } else { + LOG_ERROR("Invalid signer count: %d", signer_count); + } + + return false; +} + +/* + * Проверка подписи и извлечение сертификата подписанта + * Предполагается, что количество подписантов проверено и равно 1 + */ +static bool +verify_detached_message_signature(const str_t *data, const str_t *sign, + timer_context_t *timer_ctx, + /*out*/ PCCERT_CONTEXT *signer_cert) +{ + LOG_TRACE("verify_detached_message_signature enter"); + + timer_on_verify_detached_message_signature_enter(timer_ctx); + + CRYPT_VERIFY_MESSAGE_PARA verifyParams = { + .cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA), + .dwMsgAndCertEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING + }; + + BOOL r = cp_function_list.CryptVerifyDetachedMessageSignature( + &verifyParams, + 0, // индекс проверяемой подписи + (CONST BYTE *) sign->data, + sign->len, + 1, // количество подписанных сообщений + (const BYTE **) &(data->data), + (DWORD*) &(data->len), + signer_cert + ); + + timer_on_verify_detached_message_signature_exit(timer_ctx); + + LOG_TRACE("verify_detached_message_signature exit"); + return r; +} + +int +cryptopro_verify_detached(cryptopro_context_t *ctx, const str_t *data, const str_t *sign, + bool *is_verified, /*out*/ cert_info_t *signer_cert_info, + /*out*/ char **verify_error, /*out*/ const char **error_code) +{ + LOG_TRACE("cryptopro_verify_detached enter"); + + timer_on_cryptopro_verify_detached_enter(ctx->timer_ctx); + + *is_verified = false; + PCCERT_CONTEXT signer_cert = NULL; + + if (!check_message_signer_count(sign)) { + goto error; + } + + if (verify_detached_message_signature(data, sign, ctx->timer_ctx, &signer_cert)) { + LOG_DEBUG("sign is valid"); + + if (!check_cert_key_usage(signer_cert)) { + goto error; + } + + if (!verify_cert_chain(signer_cert, ctx->timer_ctx, error_code)) { + goto error; + } + + if (!get_signer_cert_info(signer_cert, signer_cert_info)) { + goto error; + } + + *is_verified = true; + + } else { + LOG_DEBUG("sign is invalid"); + get_verify_error(verify_error); + } + + if (signer_cert != NULL) { + cp_function_list.CertFreeCertificateContext(signer_cert); + } + + timer_on_cryptopro_verify_detached_exit(ctx->timer_ctx); + + LOG_TRACE("cryptopro_verify_detached exit"); + return 0; + +error: + if (signer_cert != NULL) { + cp_function_list.CertFreeCertificateContext(signer_cert); + } + + LOG_ERROR("cryptopro_verify_detached exit with error. Last error code: 0x%08x", + cp_function_list.GetLastError()); + return -1; +} + int cryptopro_gen_random(const cryptopro_context_t *ctx, unsigned char* data, size_t len) { diff --git a/src/utils/cryptopro.h b/src/utils/cryptopro.h index 2921f54..1b14529 100644 --- a/src/utils/cryptopro.h +++ b/src/utils/cryptopro.h @@ -21,6 +21,11 @@ typedef struct cryptopro_context_s { } cryptopro_context_t; +typedef struct cert_info_s { + char *subject; + char *issuer; + +} cert_info_t; static inline void cryptopro_context_set(cryptopro_context_t *ctx, const str_t *cert_thumbprint, @@ -36,6 +41,17 @@ cryptopro_context_set(cryptopro_context_t *ctx, const str_t *cert_thumbprint, ctx->timer_ctx = timer_ctx; } +static inline void +cert_info_clear(cert_info_t *info) +{ + assert(info != NULL); + + free(info->subject); + free(info->issuer); + + memset(info, 0, sizeof(cert_info_t)); +} + bool cryptopro_init(const char* cp_file); int open_signer_cert(cryptopro_context_t *ctx); @@ -48,6 +64,10 @@ int cryptopro_verify(cryptopro_context_t* ctx, const str_t* alg, const str_t* da const str_t* sign, bool* is_verified, char** verify_error, const char** error_code); +int cryptopro_verify_detached(cryptopro_context_t *ctx, const str_t *data, const str_t *sign, + bool *is_verified, /*out*/ cert_info_t *signer_cert_info, + /*out*/ char **verify_error, /*out*/ const char **error_code); + int cryptopro_gen_random(const cryptopro_context_t *ctx, unsigned char* data, size_t len); #endif // CRYPTOPRO_H_INCLUDED diff --git a/src/utils/detached_sign_payload_parser.c b/src/utils/detached_sign_payload_parser.c new file mode 100644 index 0000000..8264447 --- /dev/null +++ b/src/utils/detached_sign_payload_parser.c @@ -0,0 +1,279 @@ +#include "detached_sign_payload_parser.h" + +#include "utils/logger.h" +#include "utils/multipart_parser.h" + + +#define FIELD_DATA "data" +#define FIELD_SIGN "sign" +#define ACCEPTABLE_CONTENT_TYPE "application/octet-stream" + + +/* + Helper structure for parsing multipart/form-data. + All fields in the structure are just references and don't need to be freed. +*/ +typedef struct multipart_data_s { + str_t *cur_header; + /* headers: */ + str_t content_disposition; + str_t content_type; + + str_t *cur_field; + /* fields: */ + str_t signed_data; + str_t sign; + +} multipart_data_t; + + +static int +on_header_field(multipart_parser* parser, const char *at, size_t length) +{ + LOG_TRACE("on_header_field. length: %zd, at: %.*s ", length, (int) length, at); + + multipart_data_t *data = multipart_parser_get_data(parser); + + if (strncmp(at, "Content-Disposition", length) == 0) { + data->cur_header = &data->content_disposition; + } else if (strncmp(at, "Content-Type", length) == 0) { + data->cur_header = &data->content_type; + } else { + LOG_WARN("Unknown header: '%.*s'", (int) length, at); + } + + return 0; +} + +static int +process_content_disposition(multipart_data_t *data) +{ + LOG_TRACE("process_content_disposition enter"); + + size_t name_len = 0; + const char *name = multipart_get_name(data->content_disposition.data, + data->content_disposition.len, + &name_len); + + if (name == NULL) { + LOG_ERROR("Could not get field name from Content-Disposition: '%.*s'", + (int) data->content_disposition.len, data->content_disposition.data); + goto error; + } + + LOG_DEBUG("field name: %.*s ", (int) name_len, name); + + if (strncmp(name, FIELD_DATA, name_len) == 0) { + data->cur_field = &data->signed_data; + } else if (strncmp(name, FIELD_SIGN, name_len) == 0) { + data->cur_field = &data->sign; + } else { + LOG_WARN("Unknown field: '%.*s'", (int) name_len, name); + goto error; + } + + LOG_TRACE("process_content_disposition exit"); + return 0; + +error: + LOG_ERROR("process_content_disposition exit with error"); + return -1; +} + +static int +check_content_type(const str_t *content_type) +{ + LOG_TRACE("check_content_type enter"); + + LOG_DEBUG("Content-Type: '%.*s'", (int) content_type->len, content_type->data); + + if (strncmp(content_type->data, ACCEPTABLE_CONTENT_TYPE, content_type->len) != 0) { + LOG_WARN("Content-Type is not acceptable ('%.*s' != '%s')", + (int) content_type->len, content_type->data, ACCEPTABLE_CONTENT_TYPE); + return -1; + } + + LOG_DEBUG("Content-Type is ok"); + + LOG_TRACE("check_content_type exit"); + return 0; +} + +static int +on_header_value(multipart_parser* parser, const char *at, size_t length) +{ + LOG_TRACE("on_header_value. length: %zd, at: %.*s ", length, (int) length, at); + + multipart_data_t *data = multipart_parser_get_data(parser); + + if (data->cur_header == &data->content_disposition) { + data->content_disposition.data = (char*) at; + data->content_disposition.len = length; + if (process_content_disposition(data)) { + goto error; + } + } else if (data->cur_header == &data->content_type) { + data->content_type.data = (char*) at; + data->content_type.len = length; + if (check_content_type(&data->content_type)) { + goto error; + } + } else { + LOG_ERROR("Unknown header value: '%.*s'", (int) length, at); + goto error; + } + + data->cur_header = NULL; + + return 0; + +error: + LOG_ERROR("on_header_value exit with error"); + return -1; +} + +static int +on_body(multipart_parser* parser, const char *at, size_t length) +{ + LOG_TRACE("on_body. length: %zd", length); + + multipart_data_t *data = multipart_parser_get_data(parser); + + if (data->cur_field != NULL) { + data->cur_field->data = (char*) at; + data->cur_field->len = length; + } else { + LOG_WARN("Unknown current field"); + return -1; + } + + data->cur_field = NULL; + + return 0; +} + +static int +on_boundary_begin(multipart_parser* parser) +{ + LOG_TRACE("on_boundary_begin"); + + multipart_data_t *data = multipart_parser_get_data(parser); + + if (data->cur_field != NULL) { + LOG_ERROR("on_boundary_begin. Current field is not set"); + goto error; + } + + if (data->cur_header != NULL) { + LOG_ERROR("on_boundary_begin. Current header is not set"); + goto error; + } + + return 0; + +error: + LOG_ERROR("on_boundary_begin exit with error"); + return -1; +} + +static int +on_headers_complete(multipart_parser* parser) +{ + LOG_TRACE("on_headers_complete"); + + multipart_data_t *data = multipart_parser_get_data(parser); + + if (data->cur_header != NULL) { + LOG_ERROR("on_headers_complete. Current header is not set"); + return -1; + } + + return 0; +} + +static int +on_body_parts_complete(multipart_parser* parser) +{ + LOG_TRACE("on_body_parts_complete"); + + multipart_data_t *data = multipart_parser_get_data(parser); + + if (data->cur_field != NULL) { + LOG_ERROR("on_body_parts_complete. Current field is not set"); + return -1; + } + + return 0; +} + +static int +parse_multipart(const char *content_type_header, const char* content, size_t content_len, + str_t *signed_data, str_t *sign) +{ + LOG_TRACE("parse_multipart enter"); + + const char *boundary = NULL; + size_t boundary_len = 0; + multipart_data_t data = {0}; + multipart_parser parser = {0}; + + multipart_parser_settings settings = { + .on_boundary_begin = on_boundary_begin, + .on_header_field = on_header_field, + .on_header_value = on_header_value, + .on_headers_complete = on_headers_complete, + .on_body = on_body, + .on_body_parts_complete = on_body_parts_complete, + }; + + boundary = multipart_get_boundary(content_type_header, &boundary_len); + if (boundary == NULL) { + goto error; + } + + multipart_parser_init(&parser, boundary, boundary_len, &data); + + if (multipart_parser_execute(&parser, &settings, content, content_len) != 0) { + goto error; + } + + *signed_data = data.signed_data; + *sign = data.sign; + + LOG_TRACE("parse_multipart exit"); + return 0; + +error: + LOG_ERROR("parse_multipart exit with error"); + return -1; +} + +int +parse_detached_sign_payload(const char *content_type_header, const char* content, + size_t content_len, str_t *data, str_t *sign) +{ + LOG_TRACE("parse_detached_sign_payload enter"); + + if (parse_multipart(content_type_header, content, content_len, data, sign)) { + goto error; + } + + if (str_t_is_null(*sign)) { + LOG_ERROR("Could not get sign from request"); + goto error; + } + + if (str_t_is_null(*data)) { + LOG_ERROR("Could not get signed data from request"); + goto error; + } + + LOG_TRACE("parse_detached_sign_payload exit"); + return 0; + +error: + str_t_nullify(data); + str_t_nullify(sign); + LOG_ERROR("parse_detached_sign_payload exit with error"); + return -1; +} \ No newline at end of file diff --git a/src/utils/detached_sign_payload_parser.h b/src/utils/detached_sign_payload_parser.h new file mode 100644 index 0000000..b362c02 --- /dev/null +++ b/src/utils/detached_sign_payload_parser.h @@ -0,0 +1,13 @@ +#ifndef DETACHED_SIGN_PAYLOAD_PARSER_H +#define DETACHED_SIGN_PAYLOAD_PARSER_H + +#include "utils/str_t.h" + +/* + * The @data and @sign fields do not need to be freed + */ +int parse_detached_sign_payload(const char *content_type_header, + const char* content, size_t content_len, + str_t *data, str_t *sign); + +#endif // DETACHED_SIGN_PAYLOAD_PARSER_H \ No newline at end of file diff --git a/src/utils/json_parser.c b/src/utils/json_parser.c index 8f2b797..2460a69 100644 --- a/src/utils/json_parser.c +++ b/src/utils/json_parser.c @@ -111,14 +111,13 @@ json_get_string_member(Hjson_parser hparser, const char* field_name, field_value->data); return 0; case 1: - LOG_ERROR("Could not retrieve '%.*s' from json object. " + LOG_ERROR("Could not retrieve '%s' from json object. " "Desc: field does not exist or it has not proper type. " "Expected: string member", - (int) field_value->len, field_value->data); + field_name); break; default: - LOG_ERROR("json_get_object_member_as_string failed, member: '%.*s'", - (int) field_value->len, field_value->data); + LOG_ERROR("json_get_object_member_as_string failed, member: '%s'", field_name); break; } diff --git a/src/utils/multipart_parser.c b/src/utils/multipart_parser.c new file mode 100644 index 0000000..060b044 --- /dev/null +++ b/src/utils/multipart_parser.c @@ -0,0 +1,677 @@ +#include "multipart_parser.h" +#include +#include +#include +#include +#include + +#include + +#include "utils/logger.h" + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifdef __GNUC__ +# define LIKELY(X) __builtin_expect(!!(X), 1) +# define UNLIKELY(X) __builtin_expect(!!(X), 0) +#else +# define LIKELY(X) (X) +# define UNLIKELY(X) (X) +#endif + +#ifndef UNREACHABLE +# ifdef _MSC_VER +# define UNREACHABLE __assume(0) +# else /* GCC, Clang & Intel C++ */ +# define UNREACHABLE __builtin_unreachable() +# endif +#endif + +#ifndef FALLTHROUGH +# if defined(__GNUC__) || defined(__clang__) +# define FALLTHROUGH __attribute__((fallthrough)) +# else +# define FALLTHROUGH ((void)0) +# endif +#endif + + +enum state { + s_start + , s_start_dash + , s_boundary + , s_boundary_cr + , s_boundary_almost_done + , s_header_field_start + , s_header_field + , s_header_value_discard_ws + , s_header_value + , s_header_value_lws + , s_header_almost_done + + , s_headers_almost_done + , s_headers_done + + , s_body_part_start + , s_body_part + + , s_body_part_boundary + , s_body_part_boundary_dash + , s_body_part_boundary_dash_dash + , s_body_part_boundary_compare +}; + +/* Macros for character classes */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) + + +void +multipart_parser_init(multipart_parser *parser, const char *boundary, size_t boundary_len, void *data) +{ + parser->state = s_start; + parser->boundary = boundary; + parser->boundary_len = boundary_len; + parser->data = data; +} + +void * +multipart_parser_get_data(multipart_parser *parser) +{ + return parser->data; +} + +int +multipart_parser_execute(multipart_parser *parser, + const multipart_parser_settings *settings, + const char *data, + size_t len) +{ + LOG_TRACE("multipart_parser_execute enter"); + + const char* buf_end = &data[len]; + const char* p = data; + + const char* body_start = data; + const char* body_end = buf_end; + + for (; p < buf_end; ++p) { + const char ch = *p; + + switch (parser->state) + { + case s_start: + if (LIKELY(ch == '-')) { + parser->state = s_start_dash; + } + continue; + + case s_start_dash: + if (LIKELY(ch == '-')) { + parser->nread = 0; + parser->state = s_boundary; + continue; + } + + LOG_ERROR("error on s_start_dash"); + goto error; + + case s_boundary: + if (LIKELY(parser->nread < parser->boundary_len)) { + if (LIKELY(ch == parser->boundary[parser->nread++])) { + continue; + } + } else { + if (LIKELY(ch == '\r')) { + parser->state = s_boundary_cr; + continue; + } else if (ch == '-') { + parser->state = s_boundary_almost_done; + continue; + } + } + LOG_ERROR("error on s_boundary"); + goto error; + + case s_boundary_cr: + if (LIKELY(ch == '\n')) { + if (LIKELY(settings->on_boundary_begin(parser) == 0)) { + parser->state = s_header_field_start; + continue; + } + } + LOG_ERROR("error on s_boundary_cr"); + goto error; + + case s_boundary_almost_done: + if (LIKELY(ch == '-')) { + if (settings->on_body_parts_complete(parser)) { + goto error; + } + goto exit; + } + LOG_ERROR("error on s_boundary_almost_done"); + goto error; + + case s_headers_almost_done: + if (ch == '\r') { + parser->state = s_headers_done; + continue; + } + FALLTHROUGH; + + case s_header_field_start: + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + parser->nread = 1; + parser->state = s_header_field; + continue; + } + LOG_ERROR("error on s_header_field_start"); + goto error; + + case s_header_field: + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '-') { + parser->nread++; + continue; + } else if (ch == ':') { + if (settings->on_header_field( + parser, + p - parser->nread, + parser->nread ) == 0) { + parser->state = s_header_value_discard_ws; + continue; + } + } + LOG_ERROR("error on s_header_field"); + goto error; + + case s_header_value_discard_ws: + if (ch > ' ') { + parser->nread = 1; + parser->state = s_header_value; + continue; + } if (ch == ' ') { + continue; + } + LOG_ERROR("error on s_header_value_discard_ws"); + goto error; + + case s_header_value: + if (ch != '\r') { + parser->nread++; + continue; + } + if (settings->on_header_value( + parser, + p - parser->nread, + parser->nread) == 0) { + parser->state = s_header_almost_done; + continue; + } + LOG_ERROR("error on s_header_value"); + goto error; + + case s_header_almost_done: + if (ch == '\n') { + parser->state = s_headers_almost_done; + continue; + } + LOG_ERROR("error on s_header_almost_done"); + goto error; + + case s_header_value_lws: + LOG_ERROR("error on s_header_value_lws"); + goto error; + + case s_headers_done: + if (ch == '\n') { + if (LIKELY( settings->on_headers_complete(parser) == 0)) { + parser->state = s_body_part_start; + continue; + } + } + LOG_ERROR("error on s_headers_done"); + goto error; + + case s_body_part_start: + body_start = p; + parser->state = s_body_part; + FALLTHROUGH; + + case s_body_part: + if (LIKELY(ch != '\r')) { + continue; + } + + body_end = p; + parser->state = s_body_part_boundary; + continue; + + case s_body_part_boundary: + if (ch == '\n') { + parser->state = s_body_part_boundary_dash; + continue; + } + + if (ch == '\r') { + body_end = p; + continue; + } + + parser->state = s_body_part; + continue; + + case s_body_part_boundary_dash: + if (ch == '-') { + parser->state = s_body_part_boundary_dash_dash; + continue; + } + + if (ch == '\r') { + body_end = p; + parser->state = s_body_part_boundary; + continue; + } + + parser->state = s_body_part; + continue; + + case s_body_part_boundary_dash_dash: + if (ch == '-') { + parser->nread = 0; + parser->state = s_body_part_boundary_compare; + continue; + } + + if (ch == '\r') { + body_end = p; + parser->state = s_body_part_boundary; + continue; + } + + parser->state = s_body_part; + continue; + + case s_body_part_boundary_compare: + if (LIKELY(parser->nread < parser->boundary_len)) { + if (LIKELY(ch == parser->boundary[parser->nread++])) { + continue; + } + + if (ch == '\r') { + body_end = p; + parser->state = s_body_part_boundary; + continue; + } + + parser->state = s_body_part; + continue; + } else { + if (settings->on_body(parser, body_start, + body_end - body_start) == 0) { + if (LIKELY(ch == '\r')) { + parser->state = s_boundary_cr; + continue; + } + + if (ch == '-') { + parser->state = s_boundary_almost_done; + continue; + } + } + } + LOG_ERROR("error on s_body_part_boundary_compare"); + goto error; + + default: + UNREACHABLE; + } + UNREACHABLE; + } + +exit: + LOG_TRACE("multipart_parser_execute exit"); + return 0; + +error: + LOG_ERROR("multipart_parser_execute exit with error"); + return -1; +} + +const char * +multipart_get_boundary(const char* content_type, size_t* boundary_len) +{ + LOG_TRACE("multipart_get_boundary enter"); + + const char boundary_tag[] = "boundary="; + size_t boundary_tag_len = sizeof(boundary_tag) - 1; + + const char* boundary_start = strstr(content_type, boundary_tag); + if (boundary_start == NULL) { + LOG_ERROR("Could not find boundary tag"); + goto error; + } + + boundary_start += boundary_tag_len; + + while (isspace((unsigned char)*boundary_start)) { + boundary_start++; + } + + int quoted = 0; + const char* value_start = boundary_start; + + if (*value_start == '"') { + quoted = 1; + value_start++; + } + + const char* value_end = value_start; + + if (quoted) { + while (*value_end != '\0' && *value_end != '"') { + value_end++; + } + } else { + while (*value_end != '\0' && *value_end != ';' && *value_end != ' ' && + *value_end != '\t' && *value_end != '\r' && *value_end != '\n') { + value_end++; + } + } + + if (value_end == value_start) { + LOG_ERROR("Boundary is empty"); + goto error; + } + + *boundary_len = value_end - value_start; + + LOG_DEBUG("boundary: '%.*s'", (int) *boundary_len, value_start); + + LOG_TRACE("multipart_get_boundary exit"); + return value_start; + +error: + LOG_ERROR("multipart_get_boundary exit with error"); + return NULL; +} + +const char* +multipart_get_name(const char* str, size_t len, size_t* value_len) +{ + LOG_TRACE("multipart_get_name enter"); + + const char* str_end = &str[len]; + const char* p = str; + + const char* value_start; + const char* name = NULL; + + typedef enum { + s_seek + , s_N + , s_NA + , s_NAM + , s_NAME + , s_NAME_EQ + , s_NAME_EQ_QUOT + , s_value_start + , s_value + , s_value_end + } e_state; + + for (e_state state = s_seek; p < str_end; ++p) { + const char ch = *p; + + switch (state) { + case s_seek: + _reset: + if (UNLIKELY(LOWER(ch) == 'n')) { + if (p != str && (*(p-1) == ' ' || *(p-1) == ';')) { + state = s_N; + } + } + continue; + + case s_N: + if (LIKELY(ch == 'a' || ch == 'A')) { + state = s_NA; + } else { + state = s_seek; + goto _reset; + } + continue; + + case s_NA: + if (LIKELY(ch == 'm' || ch == 'M')) { + state = s_NAM; + } else { + state = s_seek; + goto _reset; + } + continue; + + case s_NAM: + if (LIKELY(ch == 'e' || ch == 'E')) { + state = s_NAME; + } else { + state = s_seek; + goto _reset; + } + continue; + + case s_NAME: + if (LIKELY(ch == '=')) { + state = s_NAME_EQ; + } else { + if (ch == ' ') { /* Skip whitespace */ + continue; + } + + state = s_seek; + goto _reset; + } + continue; + + case s_NAME_EQ: + if (LIKELY(ch == '"')) { + state = s_value_start; + } else { + if (ch == ' ') { /* Skip whitespace */ + continue; + } + + state = s_seek; + goto _reset; + } + continue; + + case s_value_start: + value_start = p; + + if (LIKELY(ch != '"')) { + state = s_value; + } else { + *value_len = 0; /* detected an empty value */ + name = value_start; + goto exit; + } + continue; + + case s_value: + if (LIKELY(ch != '"')) { + continue; + } else { + *value_len = p - value_start; + name = value_start; + goto exit; + } + + default: + UNREACHABLE; + } + } + +exit: + LOG_DEBUG("name: '%.*s'", (int) *value_len, name); + LOG_TRACE("multipart_get_name exit"); + return name; +} + +const char* +multipart_get_filename(const char* str, size_t len, size_t* value_len) +{ + LOG_TRACE("multipart_get_filename enter"); + + const char* str_end = &str[len]; + const char* p = str; + + const char* value_start; + const char* filename = NULL; + + typedef enum + { s_F + , s_FI + , s_FIL + , s_FILE + , s_FILEN + , s_FILENA + , s_FILENAM + , s_FILENAME + , s_FILENAME_EQ + , s_FILENAME_EQ_QUOT + , s_value_start + , s_value + } e_state; + + for (e_state state = s_F; p < str_end; ++p) { + const char ch = *p; + + switch (state) { + case s_F: + _reset: + if (UNLIKELY(LOWER(ch) == 'f')) { + state = s_FI; + } + continue; + + case s_FI: + if (LIKELY(ch == 'i') || ch == 'I') { + state = s_FIL; + } else { + state = s_F; + goto _reset; + } + continue; + + case s_FIL: + if (LIKELY(ch == 'l') || ch == 'L') { + state = s_FILE; + } else { + state = s_F; + goto _reset; + } + continue; + + case s_FILE: + if (LIKELY(ch == 'e') || ch == 'E') { + state = s_FILEN; + } else { + state = s_F; + goto _reset; + } + continue; + + case s_FILEN: + if (LIKELY(ch == 'n') || ch == 'N') { + state = s_FILENA; + } else { + state = s_F; + goto _reset; + } + continue; + + case s_FILENA: + if (LIKELY(ch == 'a') || ch == 'A') { + state = s_FILENAM; + } else { + state = s_F; + goto _reset; + } + continue; + + case s_FILENAM: + if (LIKELY(ch == 'm') || ch == 'M') { + state = s_FILENAME; + } else { + state = s_F; + goto _reset; + } + continue; + + case s_FILENAME: + if (LIKELY(ch == 'e') || ch == 'E') { + state = s_FILENAME_EQ; + } else { + state = s_F; + goto _reset; + } + continue; + + case s_FILENAME_EQ: + if (LIKELY(ch == '=')) { + state = s_FILENAME_EQ_QUOT; + } else { + if (ch == ' ') { /* Skip whitespace */ + continue; + } + + state = s_F; + goto _reset; + } + continue; + + case s_FILENAME_EQ_QUOT: + if (LIKELY(ch == '"')) { + state = s_value_start; + } else { + if (ch == ' ') { /* Skip whitespace */ + continue; + } + + state = s_F; + goto _reset; + } + continue; + + case s_value_start: + value_start = p; + state = s_value; + FALLTHROUGH; + + case s_value: + if (LIKELY(ch != '"')) { + continue; + } else { + *value_len = p - value_start; + filename = value_start; + goto exit; + } + + default: + UNREACHABLE; + } + } + +exit: + LOG_DEBUG("filename: '%.*s'", (int) *value_len, filename); + LOG_TRACE("multipart_get_filename exit"); + return filename; +} diff --git a/src/utils/multipart_parser.h b/src/utils/multipart_parser.h new file mode 100644 index 0000000..2780de0 --- /dev/null +++ b/src/utils/multipart_parser.h @@ -0,0 +1,65 @@ +#ifndef MULTIPART_PARSER_H +#define MULTIPART_PARSER_H + +#include +#include + +typedef struct multipart_parser multipart_parser; +typedef struct multipart_parser_settings multipart_parser_settings; + +/* Callbacks should return non-zero to indicate an error. The parser will then halt execution. + */ +typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length); +typedef int (*multipart_cb) (multipart_parser*); + +struct multipart_parser_settings { + multipart_cb on_boundary_begin; + multipart_data_cb on_header_field; + multipart_data_cb on_header_value; + multipart_cb on_headers_complete; + multipart_data_cb on_body; + multipart_cb on_body_parts_complete; +}; + +struct multipart_parser { + /** PRIVATE **/ + unsigned char state; /* enum state */ + uint64_t nread; /* bytes read in various scenarios */ + + /** PUBLIC **/ + void *data; /* pointer to user data */ + + const char *boundary; /* set this to a boundary string taken from headers */ + size_t boundary_len; +}; + +void multipart_parser_init(multipart_parser *parser, const char *boundary, size_t boundary_len, void *data); + +void * multipart_parser_get_data(multipart_parser *parser); + +/* Main function + * Returns -1 on error + */ +int multipart_parser_execute(multipart_parser *parser, const multipart_parser_settings *settings, + const char *data, size_t len); + +/* Helper function to get the boundary string from Content-Type header + * eg. `multipart/form-data; boundary=--123` + * returns: `--123` +*/ +const char* multipart_get_boundary(const char* content_type, size_t* boundary_len); + +/* Helper function to get the field name from Content-Disposition header + * eg. `form-data; name="file1"; filename="a.txt"` + * returns: `file1` + */ +const char* multipart_get_name(const char* str, size_t len, size_t* value_len); + +/** + * Helper function to get the `filename` value from a header string value + * eg. `form-data; name="file1"; filename="a.txt"` + * returns: `a.txt` + */ +const char* multipart_get_filename(const char* str, size_t len, size_t* value_len); + +#endif // MULTIPART_PARSER_H \ No newline at end of file diff --git a/src/utils/response_builder.c b/src/utils/response_builder.c index 1a932b9..fdb6987 100644 --- a/src/utils/response_builder.c +++ b/src/utils/response_builder.c @@ -98,3 +98,50 @@ error: LOG_ERROR("build_response_from_signature exit with error"); return NULL; } + +char * +build_response_from_signer_cert(const cert_info_t *cert_info) +{ + JsonBuilder *jbuilder; + char *response; + + LOG_TRACE("build_response_from_signer_cert enter"); + + jbuilder = json_builder_new(); + if (jbuilder == NULL) { + LOG_ERROR("json_builder_new failed"); + goto error; + } + + if (json_builder_begin_object(jbuilder) == NULL) { + LOG_ERROR("json_builder_begin_object failed"); + goto error; + } + + if (json_write_member_string(jbuilder, "signer_subject", cert_info->subject)) { + goto error; + } + + if (json_write_member_string(jbuilder, "issuer_name", cert_info->issuer)) { + goto error; + } + + if (json_builder_end_object(jbuilder) == NULL) { + LOG_ERROR("json_builder_end_object failed"); + goto error; + } + + response = json_write_to_str(jbuilder); + + g_object_unref(jbuilder); + + LOG_TRACE("build_response_from_signer_cert exit"); + return response; + +error: + if (jbuilder != NULL) { + g_object_unref(jbuilder); + } + LOG_ERROR("build_response_from_signer_cert exit with error"); + return NULL; +} diff --git a/src/utils/response_builder.h b/src/utils/response_builder.h index 1dd8dc1..0d5ec83 100644 --- a/src/utils/response_builder.h +++ b/src/utils/response_builder.h @@ -1,6 +1,7 @@ #ifndef RESPONSE_BUILDER_H_INCLUDED #define RESPONSE_BUILDER_H_INCLUDED +#include "utils/cryptopro.h" /* common functions */ @@ -10,5 +11,9 @@ char * build_response_from_error_code(const char *error_code); char * build_response_from_signature(const char *signature, const char *state); +/* functions for verify detached message signature service */ + +char * build_response_from_signer_cert(const cert_info_t *cert_info); + #endif // RESPONSE_BUILDER_H_INCLUDED diff --git a/src/utils/timer.c b/src/utils/timer.c index 667d785..f75eb32 100644 --- a/src/utils/timer.c +++ b/src/utils/timer.c @@ -119,6 +119,27 @@ void timer_on_cryptopro_verify_exit(timer_context_t *ctx) set_timer(ctx->is_timer_on, &ctx->cryptopro_verify_end, "cryptopro_verify_end"); } +void timer_on_cryptopro_verify_detached_enter(timer_context_t *ctx) +{ + set_timer(ctx->is_timer_on, &ctx->cryptopro_verify_detached_start, "cryptopro_verify_detached_start"); +} + +void timer_on_cryptopro_verify_detached_exit(timer_context_t *ctx) +{ + set_timer(ctx->is_timer_on, &ctx->cryptopro_verify_detached_end, "cryptopro_verify_detached_end"); +} + +void timer_on_verify_detached_message_signature_enter(timer_context_t *ctx) +{ + set_timer(ctx->is_timer_on, &ctx->verify_detached_msg_sign_start, "verify_detached_msg_sign_start"); +} + +void timer_on_verify_detached_message_signature_exit(timer_context_t *ctx) +{ + set_timer(ctx->is_timer_on, &ctx->verify_detached_msg_sign_end, "verify_detached_msg_sign_end"); +} + + void timer_log_sign(const timer_context_t *ctx) { @@ -159,4 +180,24 @@ timer_log_verify(const timer_context_t *ctx) LOG_INFO("verify_handler: %ld verify_cert_chain: %ld", diff_verify_handler, diff_verify_cert_chain); +} + +void +timer_log_verify_detached(const timer_context_t *ctx) +{ + if (!ctx->is_timer_on) { + return; + } + + time_t diff_verify_handler = get_diff(&ctx->cryptopro_verify_detached_start, + &ctx->cryptopro_verify_detached_end); + time_t diff_verify_cert_chain = get_diff(&ctx->verify_cert_chain_start, + &ctx->verify_cert_chain_end); + time_t diff_verify_detached_msg_sign = get_diff(&ctx->verify_detached_msg_sign_start, + &ctx->verify_detached_msg_sign_end); + + LOG_INFO("verify_detached_handler: %ld verify_cert_chain: %ld verify_detached_msg_sign: %ld", + diff_verify_handler, + diff_verify_cert_chain, + diff_verify_detached_msg_sign); } \ No newline at end of file diff --git a/src/utils/timer.h b/src/utils/timer.h index 762ee35..5e987ea 100644 --- a/src/utils/timer.h +++ b/src/utils/timer.h @@ -29,6 +29,12 @@ typedef struct timer_context_s struct timeval cryptopro_verify_start; struct timeval cryptopro_verify_end; + struct timeval cryptopro_verify_detached_start; + struct timeval cryptopro_verify_detached_end; + + struct timeval verify_detached_msg_sign_start; + struct timeval verify_detached_msg_sign_end; + } timer_context_t; void init_timers(timer_context_t *ctx); @@ -54,7 +60,14 @@ void timer_on_get_cert_chain_exit(timer_context_t *ctx); void timer_on_cryptopro_verify_enter(timer_context_t *ctx); void timer_on_cryptopro_verify_exit(timer_context_t *ctx); +void timer_on_cryptopro_verify_detached_enter(timer_context_t *ctx); +void timer_on_cryptopro_verify_detached_exit(timer_context_t *ctx); + +void timer_on_verify_detached_message_signature_enter(timer_context_t *ctx); +void timer_on_verify_detached_message_signature_exit(timer_context_t *ctx); + void timer_log_sign(const timer_context_t *ctx); void timer_log_verify(const timer_context_t *ctx); +void timer_log_verify_detached(const timer_context_t *ctx); #endif // TIMER_H_INCLUDED \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6351e3f..f165b2b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,19 +7,25 @@ include_directories ( ) set (ESM_SOURCES + ../src/utils/detached_sign_payload_parser.c ../src/utils/glib_utils.c ../src/utils/json_parser.c ../src/utils/jwt.c ../src/utils/logger.c + ../src/utils/multipart_parser.c ) set (HEADERS + utils/test_detached_sign_payload_parser.h utils/test_jwt.h + utils/test_multipart_parser.h ) set (SOURCES main.c + utils/test_detached_sign_payload_parser.c utils/test_jwt.c + utils/test_multipart_parser.c "${ESM_SOURCES}" ) diff --git a/tests/main.c b/tests/main.c index b17b044..53b9774 100644 --- a/tests/main.c +++ b/tests/main.c @@ -1,4 +1,6 @@ +#include "utils/test_detached_sign_payload_parser.h" #include "utils/test_jwt.h" +#include "utils/test_multipart_parser.h" #include @@ -11,6 +13,8 @@ int main(int arv, char** argc) if (CUE_SUCCESS != CU_initialize_registry()) return CU_get_error(); + // jwt + CU_pSuite suite_jwt = CU_add_suite("JWTSuit", NULL, NULL); if (NULL == suite_jwt) { goto exit; @@ -19,6 +23,30 @@ int main(int arv, char** argc) if (NULL == CU_ADD_TEST(suite_jwt, test_jwt_get_header_payload_and_sign)) goto exit; if (NULL == CU_ADD_TEST(suite_jwt, test_jwt_get_alg_from_header)) goto exit; + // multipart/form-data parser + + CU_pSuite suite_multipart = CU_add_suite("MultipartSuit", NULL, NULL); + if (NULL == suite_multipart) { + goto exit; + } + + if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_boundary)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_boundary_failure_invalid_header)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_boundary_failure_empty_boundary)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_name)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_name_2)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_name_failure)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_filename)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_filename_2)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_filename_failure)) goto exit; + + if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload_failure_sign_is_missing)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload_failure_data_is_missing)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload_failure_extra_field)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload_failure_unacceptable_content_type)) goto exit; + if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload_failure_invalid_header)) goto exit; + CU_basic_set_mode(CU_BRM_NORMAL); CU_basic_run_tests(); diff --git a/tests/utils/test_detached_sign_payload_parser.c b/tests/utils/test_detached_sign_payload_parser.c new file mode 100644 index 0000000..54355ac --- /dev/null +++ b/tests/utils/test_detached_sign_payload_parser.c @@ -0,0 +1,169 @@ +#include "test_detached_sign_payload_parser.h" + +#include "utils/detached_sign_payload_parser.h" + +#include + +static const str_t HEADER = str_t_const( + "multipart/form-data; boundary=---------------------------9051914041544843365972754266" +); + +static const str_t INVALID_HEADER = str_t_const( + "multipart/form-data; boundary=--invalid_boundary--" +); + +static const str_t BODY = str_t_const( +"-----------------------------9051914041544843365972754266\r\n" \ +"Content-Disposition: form-data; name=\"data\"; filename=\"a.txt\"\r\n" \ +"Content-Type: application/octet-stream\r\n" \ +"\r\n" \ +"This is a data." \ +"\r\n" \ +"-----------------------------9051914041544843365972754266\r\n" \ +"Content-Disposition: form-data; name=\"sign\"; filename=\"a.txt.sig\"\r\n" \ +"Content-Type: application/octet-stream\r\n" \ +"\r\n" \ +"This is a sign." \ +"\r\n" \ +"-----------------------------9051914041544843365972754266--" +); + +static const str_t BODY_WITHOUT_SIGN = str_t_const( +"-----------------------------9051914041544843365972754266\r\n" \ +"Content-Disposition: form-data; name=\"data\"; filename=\"a.txt\"\r\n" \ +"Content-Type: application/octet-stream\r\n" \ +"\r\n" \ +"This is a data." \ +"\r\n" \ +"-----------------------------9051914041544843365972754266--" +); + +static const str_t BODY_WITHOUT_DATA = str_t_const( +"-----------------------------9051914041544843365972754266\r\n" \ +"Content-Disposition: form-data; name=\"sign\"; filename=\"a.txt.sig\"\r\n" \ +"Content-Type: application/octet-stream\r\n" \ +"\r\n" \ +"This is a sign." \ +"\r\n" \ +"-----------------------------9051914041544843365972754266--" +); + +static const str_t BODY_EXTRA_FIELD = str_t_const( +"-----------------------------9051914041544843365972754266\r\n" \ +"Content-Disposition: form-data; name=\"data\"; filename=\"a.txt\"\r\n" \ +"Content-Type: application/octet-stream\r\n" \ +"\r\n" \ +"This is a data." \ +"\r\n" \ +"-----------------------------9051914041544843365972754266\r\n" \ +"Content-Disposition: form-data; name=\"sign\"; filename=\"a.txt.sig\"\r\n" \ +"Content-Type: application/octet-stream\r\n" \ +"\r\n" \ +"This is a sign." \ +"\r\n" \ +"-----------------------------9051914041544843365972754266\r\n" \ +"Content-Disposition: form-data; name=\"extra\"; filename=\"extra.txt\"\r\n" \ +"Content-Type: application/octet-stream\r\n" \ +"\r\n" \ +"This is an extra field." \ +"\r\n" \ +"-----------------------------9051914041544843365972754266--" +); + +static const str_t BODY_UNACCEPTABLE_CONTENT_TYPE = str_t_const( +"-----------------------------9051914041544843365972754266\r\n" \ +"Content-Disposition: form-data; name=\"data\"; filename=\"a.txt\"\r\n" \ +"Content-Type: text/plain\r\n" \ +"\r\n" \ +"This is a data." \ +"\r\n" \ +"-----------------------------9051914041544843365972754266\r\n" \ +"Content-Disposition: form-data; name=\"sign\"; filename=\"a.txt.sig\"\r\n" \ +"Content-Type: text/plain\r\n" \ +"\r\n" \ +"This is a sign." \ +"\r\n" \ +"-----------------------------9051914041544843365972754266--" +); + +void test_parse_detached_sign_payload() +{ + str_t data = str_t_null; + str_t sign = str_t_null; + + int rc = parse_detached_sign_payload(HEADER.data, BODY.data, BODY.len, &data, &sign); + CU_ASSERT_EQUAL(rc, 0); + CU_ASSERT_NSTRING_EQUAL(data.data, "This is a data.", data.len); + CU_ASSERT_NSTRING_EQUAL(sign.data, "This is a sign.", sign.len); +} + +void test_parse_detached_sign_payload_failure_sign_is_missing() +{ + int rc; + str_t data = str_t_null; + str_t sign = str_t_null; + + rc = parse_detached_sign_payload(HEADER.data, BODY_WITHOUT_SIGN.data, BODY_WITHOUT_SIGN.len, &data, &sign); + CU_ASSERT_NOT_EQUAL(rc, 0); + CU_ASSERT_EQUAL(data.data, NULL); + CU_ASSERT_EQUAL(data.len, 0); + CU_ASSERT_EQUAL(sign.data, NULL); + CU_ASSERT_EQUAL(sign.len, 0); +} + +void test_parse_detached_sign_payload_failure_data_is_missing() +{ + int rc; + str_t data = str_t_null; + str_t sign = str_t_null; + + rc = parse_detached_sign_payload(HEADER.data, BODY_WITHOUT_DATA.data, BODY_WITHOUT_DATA.len, &data, &sign); + CU_ASSERT_NOT_EQUAL(rc, 0); + CU_ASSERT_EQUAL(data.data, NULL); + CU_ASSERT_EQUAL(data.len, 0); + CU_ASSERT_EQUAL(sign.data, NULL); + CU_ASSERT_EQUAL(sign.len, 0); +} + +void test_parse_detached_sign_payload_failure_extra_field() +{ + int rc; + str_t data = str_t_null; + str_t sign = str_t_null; + + rc = parse_detached_sign_payload(HEADER.data, BODY_EXTRA_FIELD.data, BODY_EXTRA_FIELD.len, &data, &sign); + CU_ASSERT_NOT_EQUAL(rc, 0); + CU_ASSERT_EQUAL(data.data, NULL); + CU_ASSERT_EQUAL(data.len, 0); + CU_ASSERT_EQUAL(sign.data, NULL); + CU_ASSERT_EQUAL(sign.len, 0); +} + +void test_parse_detached_sign_payload_failure_unacceptable_content_type() +{ + int rc; + str_t data = str_t_null; + str_t sign = str_t_null; + + rc = parse_detached_sign_payload(HEADER.data, BODY_UNACCEPTABLE_CONTENT_TYPE.data, + BODY_UNACCEPTABLE_CONTENT_TYPE.len, &data, &sign); + CU_ASSERT_NOT_EQUAL(rc, 0); + CU_ASSERT_EQUAL(data.data, NULL); + CU_ASSERT_EQUAL(data.len, 0); + CU_ASSERT_EQUAL(sign.data, NULL); + CU_ASSERT_EQUAL(sign.len, 0); +} + +void test_parse_detached_sign_payload_failure_invalid_header() +{ + int rc; + str_t data = str_t_null; + str_t sign = str_t_null; + + rc = parse_detached_sign_payload(INVALID_HEADER.data, BODY.data, BODY.len, &data, &sign); + CU_ASSERT_NOT_EQUAL(rc, 0); + CU_ASSERT_EQUAL(data.data, NULL); + CU_ASSERT_EQUAL(data.len, 0); + CU_ASSERT_EQUAL(sign.data, NULL); + CU_ASSERT_EQUAL(sign.len, 0); +} \ No newline at end of file diff --git a/tests/utils/test_detached_sign_payload_parser.h b/tests/utils/test_detached_sign_payload_parser.h new file mode 100644 index 0000000..b0ba8b3 --- /dev/null +++ b/tests/utils/test_detached_sign_payload_parser.h @@ -0,0 +1,11 @@ +#ifndef TEST_DETACHED_SIGN_PAYLOAD_PARSER_H +#define TEST_DETACHED_SIGN_PAYLOAD_PARSER_H + +void test_parse_detached_sign_payload(); +void test_parse_detached_sign_payload_failure_sign_is_missing(); +void test_parse_detached_sign_payload_failure_data_is_missing(); +void test_parse_detached_sign_payload_failure_extra_field(); +void test_parse_detached_sign_payload_failure_unacceptable_content_type(); +void test_parse_detached_sign_payload_failure_invalid_header(); + +#endif // TEST_DETACHED_SIGN_PAYLOAD_PARSER_H \ No newline at end of file diff --git a/tests/utils/test_multipart_parser.c b/tests/utils/test_multipart_parser.c new file mode 100644 index 0000000..73114bd --- /dev/null +++ b/tests/utils/test_multipart_parser.c @@ -0,0 +1,149 @@ +#include "test_multipart_parser.h" + +#include "utils/multipart_parser.h" +#include "utils/str_t.h" + +#include + +static const str_t BOUNDARY = str_t_const( + "---------------------------9051914041544843365972754266" +); + +static const str_t CONTENT_TYPE_HEADER = str_t_const( + "multipart/form-data; boundary=---------------------------9051914041544843365972754266" +); + +static const str_t INVALID_CONTENT_TYPE_HEADER = str_t_const( + "multipart/form-data;" +); + +static const str_t INVALID_CONTENT_TYPE_HEADER_EMPTY_BOUNDARY = str_t_const( + "multipart/form-data; boundary=" +); + +static const str_t FIELD_NAME = str_t_const( + "data" +); + +static const str_t FILENAME = str_t_const( + "Название файла.txt" +); + +static const str_t CONTENT_DISPOSITION_HEADER = str_t_const( + "Content-Disposition: form-data; name=\"data\"; filename=\"Название файла.txt\"" +); + +static const str_t CONTENT_DISPOSITION_HEADER_2 = str_t_const( + "Content-Disposition: form-data;filename=\"Название файла.txt\";name=\"data\";" +); + +static const str_t CONTENT_DISPOSITION_HEADER_WITHOUT_NAME = str_t_const( + "Content-Disposition: form-data; filename=\"Название файла.txt\"" +); + +static const str_t CONTENT_DISPOSITION_HEADER_WITHOUT_FILENAME = str_t_const( + "Content-Disposition: form-data; name=\"data\"" +); + +void test_multipart_get_boundary() +{ + const char *b = NULL; + size_t b_len = 0; + + b = multipart_get_boundary(CONTENT_TYPE_HEADER.data, &b_len); + CU_ASSERT_NOT_EQUAL_FATAL(b, NULL); + CU_ASSERT_EQUAL(b_len, BOUNDARY.len); + CU_ASSERT_NSTRING_EQUAL(b, BOUNDARY.data, b_len); +} + +void test_multipart_get_boundary_failure_invalid_header() +{ + const char *b = NULL; + size_t b_len = 0; + + b = multipart_get_boundary(INVALID_CONTENT_TYPE_HEADER.data, &b_len); + CU_ASSERT_EQUAL_FATAL(b, NULL); + CU_ASSERT_EQUAL(b_len, 0); +} + +void test_multipart_get_boundary_failure_empty_boundary() +{ + const char *b = NULL; + size_t b_len = 0; + + b = multipart_get_boundary(INVALID_CONTENT_TYPE_HEADER_EMPTY_BOUNDARY.data, &b_len); + CU_ASSERT_EQUAL_FATAL(b, NULL); + CU_ASSERT_EQUAL(b_len, 0); +} + +void test_multipart_get_name() +{ + size_t name_len = 0; + const char *name = NULL; + + name = multipart_get_name(CONTENT_DISPOSITION_HEADER.data, CONTENT_DISPOSITION_HEADER.len, + &name_len); + CU_ASSERT_NOT_EQUAL_FATAL(name, NULL); + CU_ASSERT_EQUAL(name_len, FIELD_NAME.len); + CU_ASSERT_NSTRING_EQUAL(name, FIELD_NAME.data, name_len); +} + +void test_multipart_get_name_2() +{ + size_t name_len = 0; + const char *name = NULL; + + name = multipart_get_name(CONTENT_DISPOSITION_HEADER_2.data, CONTENT_DISPOSITION_HEADER_2.len, + &name_len); + CU_ASSERT_NOT_EQUAL_FATAL(name, NULL); + CU_ASSERT_EQUAL(name_len, FIELD_NAME.len); + CU_ASSERT_NSTRING_EQUAL(name, FIELD_NAME.data, name_len); +} + +void test_multipart_get_name_failure() +{ + size_t name_len = 0; + const char *name = NULL; + + name = multipart_get_name(CONTENT_DISPOSITION_HEADER_WITHOUT_NAME.data, + CONTENT_DISPOSITION_HEADER_WITHOUT_NAME.len, + &name_len); + CU_ASSERT_EQUAL_FATAL(name, NULL); + CU_ASSERT_EQUAL(name_len, 0); +} + +void test_multipart_get_filename() +{ + size_t filename_len = 0; + const char *filename = NULL; + + filename = multipart_get_filename(CONTENT_DISPOSITION_HEADER.data, CONTENT_DISPOSITION_HEADER.len, + &filename_len); + CU_ASSERT_NOT_EQUAL_FATAL(filename, NULL); + CU_ASSERT_EQUAL(filename_len, FILENAME.len); + CU_ASSERT_NSTRING_EQUAL(filename, FILENAME.data, filename_len); +} + +void test_multipart_get_filename_2() +{ + size_t filename_len = 0; + const char *filename = NULL; + + filename = multipart_get_filename(CONTENT_DISPOSITION_HEADER_2.data, CONTENT_DISPOSITION_HEADER_2.len, + &filename_len); + CU_ASSERT_NOT_EQUAL_FATAL(filename, NULL); + CU_ASSERT_EQUAL(filename_len, FILENAME.len); + CU_ASSERT_NSTRING_EQUAL(filename, FILENAME.data, filename_len); +} + +void test_multipart_get_filename_failure() +{ + size_t filename_len = 0; + const char *filename = NULL; + + filename = multipart_get_filename(CONTENT_DISPOSITION_HEADER_WITHOUT_FILENAME.data, + CONTENT_DISPOSITION_HEADER_WITHOUT_FILENAME.len, + &filename_len); + CU_ASSERT_EQUAL_FATAL(filename, NULL); + CU_ASSERT_EQUAL(filename_len, 0); +} \ No newline at end of file diff --git a/tests/utils/test_multipart_parser.h b/tests/utils/test_multipart_parser.h new file mode 100644 index 0000000..a1eae8b --- /dev/null +++ b/tests/utils/test_multipart_parser.h @@ -0,0 +1,18 @@ +#ifndef TEST_MULTIPART_PARSER_H_INCLUDED +#define TEST_MULTIPART_PARSER_H_INCLUDED + + +void test_multipart_get_boundary(); +void test_multipart_get_boundary_failure_invalid_header(); +void test_multipart_get_boundary_failure_empty_boundary(); + +void test_multipart_get_name(); +void test_multipart_get_name_2(); +void test_multipart_get_name_failure(); + +void test_multipart_get_filename(); +void test_multipart_get_filename_2(); +void test_multipart_get_filename_failure(); + + +#endif // TEST_MULTIPART_PARSER_H_INCLUDED \ No newline at end of file From 4d100f235f74f50437ac75d777679e667e9acb17 Mon Sep 17 00:00:00 2001 From: alashkova Date: Fri, 29 Aug 2025 13:10:21 +0300 Subject: [PATCH 04/10] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=201.?= =?UTF-8?q?4.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- ...укция по обновлению с версии 1.3.0 до 1.4.0.txt | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 docs/Инструкция по обновлению с версии 1.3.0 до 1.4.0.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 29b0889..ac18f21 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.3.4 LANGUAGES C) +PROJECT (ervu-sign-module VERSION 1.4.0 LANGUAGES C) IF (CMAKE_VERBOSE) SET (CMAKE_VERBOSE_MAKEFILE 1) diff --git a/docs/Инструкция по обновлению с версии 1.3.0 до 1.4.0.txt b/docs/Инструкция по обновлению с версии 1.3.0 до 1.4.0.txt new file mode 100644 index 0000000..2e7a54e --- /dev/null +++ b/docs/Инструкция по обновлению с версии 1.3.0 до 1.4.0.txt @@ -0,0 +1,41 @@ +В версии 1.4.0 появились следующие функции: +- Проверка подписанного сообщения, содержащего отсоединённую подпись + +Для обновления приложения необходимо выполнить следующие шаги (команды выполняются от имени root): + +1. Остановить службу: +``` +systemctl stop ervu-sign-module +``` + +2. Обновить приложение: +``` +cp ./ervu-sign-module /opt/ervu-sign-module/ +``` + +3. Запустить службу: +``` +systemctl start ervu-sign-module +``` + +4. Проверить версию приложения: +``` +/opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/version +``` + +Статус-код ответа должен быть равен 200 OK. +В ответе должна быть возвращена строчка "1.4.0". + +5. В конфигурационном файле /etc/nginx/nginx.conf рекомендуется настроить значение 'client_max_body_size': +``` +server { + ... + client_max_body_size 10M; + ... +} +``` + +6. После обновления конфигурационного файла /etc/nginx/nginx.conf перезапустить службу nginx: +``` +systemctl reload nginx +``` From 57968e3b8f9bc9ae7a8d22ac59437dd9f230f867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A5=D0=B0=D0=BB=D1=82=D0=BE=D0=B1=D0=B8=D0=BD=20=D0=95?= =?UTF-8?q?=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9?= Date: Tue, 2 Sep 2025 08:11:30 +0300 Subject: [PATCH 05/10] updated nexus url --- Dockerfile.micord | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.micord b/Dockerfile.micord index 18b9c63..5b0a68b 100644 --- a/Dockerfile.micord +++ b/Dockerfile.micord @@ -1,4 +1,4 @@ -ARG BUILDER_IMAGE=repo.micord.ru/alt/alt-cprocsp-kc2:c10f1-5.0.13000-20250710 +ARG BUILDER_IMAGE=repo.asd.center.cg:8082/alt/alt-cprocsp-kc2:c10f1-5.0.13000-20250710 ARG RUNTIME_IMAGE=registry.altlinux.org/basealt/altsp:c10f1 ARG RUNTIME_USER=ervu From 23d9f411d42c3a1ce77d116f1dea96127b9688f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A5=D0=B0=D0=BB=D1=82=D0=BE=D0=B1=D0=B8=D0=BD=20=D0=95?= =?UTF-8?q?=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9?= Date: Tue, 2 Sep 2025 08:26:09 +0300 Subject: [PATCH 06/10] Revert "updated nexus url" This reverts commit 57968e3b8f9bc9ae7a8d22ac59437dd9f230f867. --- Dockerfile.micord | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.micord b/Dockerfile.micord index 5b0a68b..18b9c63 100644 --- a/Dockerfile.micord +++ b/Dockerfile.micord @@ -1,4 +1,4 @@ -ARG BUILDER_IMAGE=repo.asd.center.cg:8082/alt/alt-cprocsp-kc2:c10f1-5.0.13000-20250710 +ARG BUILDER_IMAGE=repo.micord.ru/alt/alt-cprocsp-kc2:c10f1-5.0.13000-20250710 ARG RUNTIME_IMAGE=registry.altlinux.org/basealt/altsp:c10f1 ARG RUNTIME_USER=ervu From 6929ecbf564a261f968eabe6bf7fd6f1f6267169 Mon Sep 17 00:00:00 2001 From: alashkova Date: Wed, 10 Sep 2025 16:17:29 +0300 Subject: [PATCH 07/10] SUPPORT-9333. fix multipart payload parsing --- src/utils/detached_sign_payload_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/detached_sign_payload_parser.c b/src/utils/detached_sign_payload_parser.c index 8264447..0a4382a 100644 --- a/src/utils/detached_sign_payload_parser.c +++ b/src/utils/detached_sign_payload_parser.c @@ -118,7 +118,7 @@ on_header_value(multipart_parser* parser, const char *at, size_t length) if (check_content_type(&data->content_type)) { goto error; } - } else { + } else if (data->cur_header != NULL) { LOG_ERROR("Unknown header value: '%.*s'", (int) length, at); goto error; } From b09ade7bf4e1c14f48ab7606d6c6d526cbf0d24b Mon Sep 17 00:00:00 2001 From: alashkova Date: Wed, 15 Oct 2025 16:59:17 +0300 Subject: [PATCH 08/10] =?UTF-8?q?SUPPORT-9482.=20=D0=9E=D1=82=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B5=D0=BD=D0=BE=20=D0=B0=D0=B2=D1=82=D0=BE=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=BE=D0=B5=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA?= =?UTF-8?q?=D0=B0=D0=B2=D1=8B=D1=87=D0=B5=D0=BA=20=D0=B2=D0=BE=D0=BA=D1=80?= =?UTF-8?q?=D1=83=D0=B3=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B9?= =?UTF-8?q?=20=D0=B2=20DN=20(Distinguished=20Name)=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/cryptopro.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/utils/cryptopro.c b/src/utils/cryptopro.c index 6c38b1f..eafe541 100644 --- a/src/utils/cryptopro.c +++ b/src/utils/cryptopro.c @@ -910,10 +910,12 @@ get_subject_name(PCCERT_CONTEXT cert, /*out*/ char **subject_name) { LOG_TRACE("get_subject_name enter"); + DWORD dwFlags = CERT_X500_NAME_STR | CERT_NAME_STR_NO_QUOTING_FLAG; + DWORD len = cp_function_list.CertNameToStr( cert->dwCertEncodingType, &cert->pCertInfo->Subject, - CERT_X500_NAME_STR, + dwFlags, NULL, 0 ); @@ -927,7 +929,7 @@ get_subject_name(PCCERT_CONTEXT cert, /*out*/ char **subject_name) cp_function_list.CertNameToStr( cert->dwCertEncodingType, &cert->pCertInfo->Subject, - CERT_X500_NAME_STR, + dwFlags, *subject_name, len ); @@ -948,10 +950,12 @@ get_issuer_name(PCCERT_CONTEXT cert, /*out*/ char **issuer_name) { LOG_TRACE("get_issuer_name enter"); + DWORD dwFlags = CERT_X500_NAME_STR | CERT_NAME_STR_NO_QUOTING_FLAG; + DWORD len = cp_function_list.CertNameToStr( cert->dwCertEncodingType, &cert->pCertInfo->Issuer, - CERT_X500_NAME_STR, + dwFlags, NULL, 0 ); @@ -965,7 +969,7 @@ get_issuer_name(PCCERT_CONTEXT cert, /*out*/ char **issuer_name) cp_function_list.CertNameToStr( cert->dwCertEncodingType, &cert->pCertInfo->Issuer, - CERT_X500_NAME_STR, + dwFlags, *issuer_name, len ); From 9af14689f0af15d080e23f5f6a5f318f51faf52c Mon Sep 17 00:00:00 2001 From: alashkova Date: Wed, 15 Oct 2025 17:01:26 +0300 Subject: [PATCH 09/10] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D0=B8=D1=8F=201.?= =?UTF-8?q?4.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac18f21..57982a4 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.0 LANGUAGES C) +PROJECT (ervu-sign-module VERSION 1.4.1 LANGUAGES C) IF (CMAKE_VERBOSE) SET (CMAKE_VERBOSE_MAKEFILE 1) From b0a22acf9252cc45e57cd1e0764731116189facf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B0=D0=B8=D0=BB=D1=8F=20=D0=90=D0=BB=D0=B0=D1=88?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=D0=B0?= Date: Fri, 17 Oct 2025 08:31:26 +0300 Subject: [PATCH 10/10] =?UTF-8?q?nginx.conf:=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=20upstream=20fastcgi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/nginx.conf | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/conf/nginx.conf b/conf/nginx.conf index 092571a..24cc616 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -27,7 +27,7 @@ http { default_type application/octet-stream; sendfile on; - + gzip on; # text/html doesn't need to be defined there, it's compressed always @@ -36,13 +36,27 @@ http { # gzip_comp_level 9; include /etc/nginx/sites-enabled.d/*.conf; + upstream fastcgi { + server 127.0.0.1:9009; + server 127.0.0.1:9010; + server 127.0.0.1:9011; + server 127.0.0.1:9012; + server 127.0.0.1:9013; + server 127.0.0.1:9014; + server 127.0.0.1:9015; + server 127.0.0.1:9016; + server 127.0.0.1:9017; + server 127.0.0.1:9018; + } + server { listen 80; server_name localhost; + client_max_body_size 10M; - location / { - fastcgi_pass localhost:9009; - include fastcgi_params; - } - } + location / { + fastcgi_pass fastcgi; + include fastcgi_params; + } + } }