Merge branch 'release/1.4.1'

This commit is contained in:
Zaripov Emil 2025-11-27 15:49:02 +03:00
commit 13092d7508
35 changed files with 2726 additions and 237 deletions

View file

@ -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.4.1 LANGUAGES C)
IF (CMAKE_VERBOSE)
SET (CMAKE_VERBOSE_MAKEFILE 1)
@ -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,8 @@ 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
${UTILS_DIR}/uuid.c
@ -134,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
)

View file

@ -3,8 +3,9 @@
В модуле реализованы следующие функции:
- подпись данных
- проверка подписи маркера доступа
- проверка подписанного сообщения, содержащего отсоединённую (detached) подпись
### Подпись данных
## Подпись данных
Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain).
C помощью ДСЧ генерирует state - набор случайных символов, генерируется по стандарту UUID.
@ -36,14 +37,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 +66,61 @@ $ /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`.
Пример ответа:
```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"
@ -103,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:
@ -116,5 +175,3 @@ export SIGN_LOG_TIME=on
```
unset SIGN_LOG_TIME
```

View file

@ -17,3 +17,6 @@ sign_cert_password = ****
[verify]
#location = /verify
esia_cert_thumbprint =
#[verify_detached]
#location = /msg/verify_detached

View file

@ -1,6 +1,7 @@
server {
listen 80;
server_name localhost;
client_max_body_size 10M;
location / {
fastcgi_pass ervu-sign-module:9009;

View file

@ -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;
@ -35,12 +36,26 @@ 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;
fastcgi_pass fastcgi;
include fastcgi_params;
}
}

View file

@ -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
```

View file

@ -1,9 +1,10 @@
#include "fcgi_server_internal.h"
#include "utils/logger.h"
#include "utils/response_builder.h"
#include <assert.h>
int
fcgi_get_content_length(const FCGX_Request* request)
{
@ -46,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");
@ -56,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;
}
@ -161,3 +172,72 @@ 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)
{
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;
}

View file

@ -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\
@ -131,9 +138,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
@ -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
@ -213,12 +212,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);
/******************************************************************************/

View file

@ -3,24 +3,19 @@
#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"
#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;
@ -36,12 +31,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);
@ -190,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;
}
@ -212,7 +210,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,55 +233,29 @@ 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_request_finalize(const FCGX_Request* request, fcgi_sign_request_t *req_info,
fcgi_handler_status_t status)
{
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) {
LOG_ERROR("FCGX_FPrintF() failed");
return HANDLER_ERROR;
}
return HANDLER_SUCCESS;
}
static fcgi_request_handler_pt
fcgi_request_finalize_handler(fcgi_handler_status_t status)
{
fcgi_request_handler_pt handler;
switch (status) {
case HANDLER_SUCCESS:
handler = fcgi_ok_handler;
break;
case HANDLER_HTTP_OK:
handler = fcgi_200_ok_handler;
break;
return fcgi_200_ok_handler(request, req_info->response);
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 +290,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 +310,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;

View file

@ -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 {
@ -37,12 +31,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);
@ -167,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;
}
@ -180,7 +177,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);
@ -189,62 +186,32 @@ exit:
}
static fcgi_handler_status_t
fcgi_401_bad_signature_handler(const FCGX_Request* request, void *ctx)
fcgi_request_finalize(const FCGX_Request* request, fcgi_verify_request_t *req_info,
fcgi_handler_status_t status)
{
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_request_handler_pt
fcgi_request_finalize_handler(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, NULL);
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->verify_error);
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 +226,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 +246,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);
}
@ -318,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;
}
@ -347,7 +303,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;
}

View file

@ -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);
}

View file

@ -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

View file

@ -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)) {

View file

@ -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;
@ -49,6 +50,7 @@ typedef struct service_manager_conf_s {
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;

View file

@ -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;
}

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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,100 @@ free_cert_chain(PCCERT_CHAIN_CONTEXT chain_ctx)
LOG_TRACE("free_cert_chain exit");
}
static void
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;
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 +215,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(certificate, chain_ctx->TrustStatus.dwErrorStatus, error_code);
goto error;
}
@ -180,15 +276,17 @@ 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;
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);
PCCERT_CHAIN_CONTEXT chain_ctx = get_cert_chain(certificate, error_code);
if (chain_ctx == NULL) {
goto exit;
}
@ -206,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;
}
@ -259,7 +359,6 @@ open_signer_cert(cryptopro_context_t *ctx)
goto error;
}
LOG_TRACE("open_signer_cert exit");
return 0;
@ -310,7 +409,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;
@ -319,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)) {
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(
@ -360,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");
@ -669,12 +768,14 @@ get_verify_error(char** verify_error)
return;
}
LOG_DEBUG("verify error: %s", *verify_error);
LOG_TRACE("get_verify_error exit");
}
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;
@ -709,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)) {
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,
@ -808,6 +905,228 @@ exit:
return rc;
}
static bool
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,
dwFlags,
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,
dwFlags,
*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 dwFlags = CERT_X500_NAME_STR | CERT_NAME_STR_NO_QUOTING_FLAG;
DWORD len = cp_function_list.CertNameToStr(
cert->dwCertEncodingType,
&cert->pCertInfo->Issuer,
dwFlags,
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,
dwFlags,
*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)
{

View file

@ -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,15 +41,32 @@ 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);
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_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);

View file

@ -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 if (data->cur_header != NULL) {
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;
}

View file

@ -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

View file

@ -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;
}

View file

@ -0,0 +1,677 @@
#include "multipart_parser.h"
#include <assert.h>
#include <stddef.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#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;
}

View file

@ -0,0 +1,65 @@
#ifndef MULTIPART_PARSER_H
#define MULTIPART_PARSER_H
#include <stddef.h>
#include <stdint.h>
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

View file

@ -0,0 +1,147 @@
#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;
}
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;
}

View file

@ -0,0 +1,19 @@
#ifndef RESPONSE_BUILDER_H_INCLUDED
#define RESPONSE_BUILDER_H_INCLUDED
#include "utils/cryptopro.h"
/* 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);
/* functions for verify detached message signature service */
char * build_response_from_signer_cert(const cert_info_t *cert_info);
#endif // RESPONSE_BUILDER_H_INCLUDED

View file

@ -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;
}
@ -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)
{
@ -160,3 +181,23 @@ timer_log_verify(const timer_context_t *ctx)
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);
}

View file

@ -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

View file

@ -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}"
)

View file

@ -1,4 +1,6 @@
#include "utils/test_detached_sign_payload_parser.h"
#include "utils/test_jwt.h"
#include "utils/test_multipart_parser.h"
#include <CUnit/Basic.h>
@ -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();

View file

@ -0,0 +1,169 @@
#include "test_detached_sign_payload_parser.h"
#include "utils/detached_sign_payload_parser.h"
#include <CUnit/Basic.h>
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);
}

View file

@ -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

View file

@ -0,0 +1,149 @@
#include "test_multipart_parser.h"
#include "utils/multipart_parser.h"
#include "utils/str_t.h"
#include <CUnit/Basic.h>
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);
}

View file

@ -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