SUPPORT-9333. Проверка подписанного сообщения, содержащего отсоединённую подпись
This commit is contained in:
parent
9d2f2be25a
commit
ac08303c90
34 changed files with 2403 additions and 114 deletions
|
|
@ -118,6 +118,7 @@ ADD_EXECUTABLE (${PROJECT_NAME}
|
||||||
${UTILS_DIR}/capi.c
|
${UTILS_DIR}/capi.c
|
||||||
${UTILS_DIR}/conf_file_context.c
|
${UTILS_DIR}/conf_file_context.c
|
||||||
${UTILS_DIR}/cryptopro.c
|
${UTILS_DIR}/cryptopro.c
|
||||||
|
${UTILS_DIR}/detached_sign_payload_parser.c
|
||||||
${UTILS_DIR}/gconf_file.c
|
${UTILS_DIR}/gconf_file.c
|
||||||
${UTILS_DIR}/glib_utils.c
|
${UTILS_DIR}/glib_utils.c
|
||||||
${UTILS_DIR}/json_parser.c
|
${UTILS_DIR}/json_parser.c
|
||||||
|
|
@ -125,6 +126,7 @@ ADD_EXECUTABLE (${PROJECT_NAME}
|
||||||
${UTILS_DIR}/jwt.c
|
${UTILS_DIR}/jwt.c
|
||||||
${UTILS_DIR}/library.c
|
${UTILS_DIR}/library.c
|
||||||
${UTILS_DIR}/logger.c
|
${UTILS_DIR}/logger.c
|
||||||
|
${UTILS_DIR}/multipart_parser.c
|
||||||
${UTILS_DIR}/response_builder.c
|
${UTILS_DIR}/response_builder.c
|
||||||
${UTILS_DIR}/str_t.c
|
${UTILS_DIR}/str_t.c
|
||||||
${UTILS_DIR}/timer.c
|
${UTILS_DIR}/timer.c
|
||||||
|
|
@ -135,6 +137,7 @@ ADD_EXECUTABLE (${PROJECT_NAME}
|
||||||
${FCGISRV_DIR}/fcgi_utils.c
|
${FCGISRV_DIR}/fcgi_utils.c
|
||||||
${FCGISRV_DIR}/fcgi_worker.c
|
${FCGISRV_DIR}/fcgi_worker.c
|
||||||
${MODULES_DIR}/service_sign.c
|
${MODULES_DIR}/service_sign.c
|
||||||
|
${MODULES_DIR}/service_verify_detached.c
|
||||||
${MODULES_DIR}/service_verify.c
|
${MODULES_DIR}/service_verify.c
|
||||||
${MODULES_DIR}/service_version.c
|
${MODULES_DIR}/service_version.c
|
||||||
)
|
)
|
||||||
|
|
|
||||||
43
README.md
43
README.md
|
|
@ -3,6 +3,7 @@
|
||||||
В модуле реализованы следующие функции:
|
В модуле реализованы следующие функции:
|
||||||
- подпись данных
|
- подпись данных
|
||||||
- проверка подписи маркера доступа
|
- проверка подписи маркера доступа
|
||||||
|
- проверка подписанного сообщения, содержащего отсоединённую (detached) подпись
|
||||||
|
|
||||||
## Подпись данных
|
## Подпись данных
|
||||||
|
|
||||||
|
|
@ -41,7 +42,7 @@ $ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/sign -H "Content-Type: text/pl
|
||||||
Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain).
|
Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain).
|
||||||
Проверяет подпись маркера доступа, полученного в теле запроса.
|
Проверяет подпись маркера доступа, полученного в теле запроса.
|
||||||
В ответе возвращает один из следующих статус-кодов:
|
В ответе возвращает один из следующих статус-кодов:
|
||||||
- `200 OK`- подпись валидна
|
- `200 OK` - подпись валидна
|
||||||
- `401 Unauthorized` - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера)
|
- `401 Unauthorized` - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера)
|
||||||
- `500 Internal Server Error` - внутренняя ошибка сервера (подробнее см. в `Обработка ошибок`)
|
- `500 Internal Server Error` - внутренняя ошибка сервера (подробнее см. в `Обработка ошибок`)
|
||||||
|
|
||||||
|
|
@ -65,6 +66,43 @@ $ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/verify -H "Content-Type: text/
|
||||||
<
|
<
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Проверка подписанного сообщения, содержащего отсоединённую подпись
|
||||||
|
|
||||||
|
Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: multipart/form-data).
|
||||||
|
Проверяет подписанное сообщение, содержащее отсоединённую (detached) подпись.
|
||||||
|
В ответе возвращает один из следующих статус-кодов:
|
||||||
|
- `200 OK` - подпись валидна, в ответе возвращает поля "Subject" и "Issuer" из свойств сертификата, которым было подписано сообщение
|
||||||
|
- `401 Unauthorized` - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера)
|
||||||
|
- `500 Internal Server Error` - внутренняя ошибка сервера (подробнее см. в `Обработка ошибок`)
|
||||||
|
|
||||||
|
Пример выполнения запроса:
|
||||||
|
```
|
||||||
|
$ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/msg/verify_detached \
|
||||||
|
-F "data=@data.csv;type=application/octet-stream" \
|
||||||
|
-F "sign=@data.csv.sig;type=application/octet-stream"
|
||||||
|
* Trying 127.0.0.1:80...
|
||||||
|
* TCP_NODELAY set
|
||||||
|
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
|
||||||
|
> POST /msg/verify_detached HTTP/1.1
|
||||||
|
> Host: 127.0.0.1
|
||||||
|
> User-Agent: curl/7.65.3-DEV
|
||||||
|
> Accept: */*
|
||||||
|
> Content-Length: 2770
|
||||||
|
> Content-Type: multipart/form-data; boundary=------------------------d4c9f889765b7342
|
||||||
|
>
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Server: nginx/1.24.0
|
||||||
|
< Date: Tue, 26 Aug 2025 08:58:18 GMT
|
||||||
|
< Content-Type: application/json
|
||||||
|
< Transfer-Encoding: chunked
|
||||||
|
< Connection: keep-alive
|
||||||
|
<
|
||||||
|
{
|
||||||
|
"signer_subject":"C=RU, O=Организация, OU=Отдел тестирования отсоединенной подписи, CN=Фамилия Имя Отчество",
|
||||||
|
"issuer_name": "C=RU, O=УЦ, CN=GOST CA Root"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Обработка ошибок
|
## Обработка ошибок
|
||||||
|
|
||||||
В случае ошибки сервер возвращает ответ со статус-кодом `500 Internal Server Error` и телом в формате JSON, содержащим поле `error_code`.
|
В случае ошибки сервер возвращает ответ со статус-кодом `500 Internal Server Error` и телом в формате JSON, содержащим поле `error_code`.
|
||||||
|
|
@ -121,6 +159,9 @@ sign_cert_password = \*\*\*\* *\# пароль от контейнера*
|
||||||
location = /verify *\# значение по умолчанию: /verify*
|
location = /verify *\# значение по умолчанию: /verify*
|
||||||
esia_cert_thumbprint = sha1_thumbprint_of_esia_cert0,sha1_thumbprint_of_esia_cert1 *\# список SHA1 отпечатков сертификатов ЕСИА, указанных через запятую (без пробелов)*
|
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:
|
***Для включения*** логирования времени обработки запросов необходимо задать переменную окружения SIGN_LOG_TIME:
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,6 @@ sign_cert_password = ****
|
||||||
[verify]
|
[verify]
|
||||||
#location = /verify
|
#location = /verify
|
||||||
esia_cert_thumbprint =
|
esia_cert_thumbprint =
|
||||||
|
|
||||||
|
#[verify_detached]
|
||||||
|
#location = /msg/verify_detached
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
|
client_max_body_size 10M;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
fastcgi_pass ervu-sign-module:9009;
|
fastcgi_pass ervu-sign-module:9009;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ http {
|
||||||
proxy_temp_path /var/spool/nginx/tmp/proxy;
|
proxy_temp_path /var/spool/nginx/tmp/proxy;
|
||||||
fastcgi_temp_path /var/spool/nginx/tmp/fastcgi;
|
fastcgi_temp_path /var/spool/nginx/tmp/fastcgi;
|
||||||
client_body_temp_path /var/spool/nginx/tmp/client;
|
client_body_temp_path /var/spool/nginx/tmp/client;
|
||||||
|
client_max_body_size 10M;
|
||||||
|
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
default_type application/octet-stream;
|
default_type application/octet-stream;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#include "fcgi_server_internal.h"
|
#include "fcgi_server_internal.h"
|
||||||
|
|
||||||
#include "utils/logger.h"
|
|
||||||
#include "utils/response_builder.h"
|
#include "utils/response_builder.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
@ -48,7 +47,7 @@ check_content_length(int content_length, int client_max_body_size)
|
||||||
|
|
||||||
|
|
||||||
fcgi_handler_status_t
|
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");
|
LOG_TRACE("check_content_type");
|
||||||
|
|
||||||
|
|
@ -58,13 +57,23 @@ check_content_type(const FCGX_Request* request, const char* acceptable_content_t
|
||||||
return HANDLER_HTTP_BAD_REQUEST;
|
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");
|
LOG_DEBUG("Content-Type is ok");
|
||||||
return HANDLER_SUCCESS;
|
return HANDLER_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_WARN("Content-Type is not acceptable, '%s' (!= '%s')",
|
LOG_WARN("Content-Type is not acceptable, '%s' (!= '%.*s')",
|
||||||
content_type, acceptable_content_type);
|
content_type, (int) acceptable_content_type->len, acceptable_content_type->data);
|
||||||
|
|
||||||
return HANDLER_HTTP_NOT_ACCEPTABLE;
|
return HANDLER_HTTP_NOT_ACCEPTABLE;
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +173,47 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va
|
||||||
return HANDLER_SUCCESS;
|
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_handler_status_t
|
||||||
fcgi_500_internal_server_error_handler(const FCGX_Request* request, const char *error_code)
|
fcgi_500_internal_server_error_handler(const FCGX_Request* request, const char *error_code)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "fcgi_server.h"
|
#include "fcgi_server.h"
|
||||||
#include "utils/logger.h"
|
#include "utils/logger.h"
|
||||||
|
#include "utils/str_t.h"
|
||||||
|
|
||||||
#include "stdbool.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
|
* Returns: HANDLER_SUCCESS, HANDLER_HTTP_BAD_REQUEST, HANDLER_HTTP_NOT_ACCEPTABLE
|
||||||
*/
|
*/
|
||||||
fcgi_handler_status_t
|
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
|
* 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 CRLF "\r\n"
|
||||||
|
|
||||||
#define FCGI_200_RESPONSE_FORMAT \
|
#define FCGI_200_EMPTY_RESPONSE_FORMAT \
|
||||||
"Status: 200 OK" CRLF\
|
"Status: 200 OK" CRLF\
|
||||||
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 \
|
#define FCGI_400_RESPONSE_FORMAT \
|
||||||
"Status: 400 Bad Request" CRLF\
|
"Status: 400 Bad Request" CRLF\
|
||||||
"Content-type: text/plain" 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\
|
"Status: 401 Unauthorized" CRLF\
|
||||||
"Content-type: text/plain" CRLF\
|
"Content-type: text/plain" CRLF\
|
||||||
CRLF\
|
CRLF\
|
||||||
"401 Unauthorized" CRLF
|
"%s" CRLF
|
||||||
|
|
||||||
#define FCGI_404_RESPONSE_FORMAT \
|
#define FCGI_404_RESPONSE_FORMAT \
|
||||||
"Status: 404 Not Found" CRLF\
|
"Status: 404 Not Found" CRLF\
|
||||||
|
|
@ -158,12 +165,8 @@ fcgi_print_log(const FCGX_Request* request, const char* status)
|
||||||
LOG_INFO("%s %s, '%s'", method, uri, status);
|
LOG_INFO("%s %s, '%s'", method, uri, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline fcgi_handler_status_t
|
fcgi_handler_status_t
|
||||||
fcgi_200_ok_handler(const FCGX_Request* request, void* ctx __attribute__((unused)))
|
fcgi_200_ok_handler(const FCGX_Request* request, const char* response);
|
||||||
{
|
|
||||||
LOG_DEBUG("response status: '200 OK'");
|
|
||||||
return FCGI_CHECK_PRINTF_STATUS(request, FCGI_200_RESPONSE_FORMAT, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline fcgi_handler_status_t
|
static inline fcgi_handler_status_t
|
||||||
fcgi_400_bad_request_handler(const FCGX_Request* request, void* ctx __attribute__((unused)))
|
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_handler_status_t
|
||||||
fcgi_401_unauthorized_handler(const FCGX_Request* request, void* ctx __attribute__((unused)))
|
fcgi_401_bad_signature_handler(const FCGX_Request* request, const char* verify_error);
|
||||||
{
|
|
||||||
fcgi_print_log(request, "401 Unauthorized");
|
|
||||||
return FCGI_CHECK_PRINTF_STATUS(request, FCGI_401_RESPONSE_FORMAT, 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline fcgi_handler_status_t
|
static inline fcgi_handler_status_t
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,10 @@
|
||||||
#define SIGN_CONF_KEY_SIGN_CERT_THUMBPRINT "sign_cert_thumbprint"
|
#define SIGN_CONF_KEY_SIGN_CERT_THUMBPRINT "sign_cert_thumbprint"
|
||||||
#define SIGN_CONF_KEY_SIGN_CERT_PASSWORD "sign_cert_password"
|
#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 str_t SIGN_CONF_DEFAULT_LOCATION = str_t_const("/sign");
|
||||||
|
|
||||||
static const int CLIENT_MAX_BODY_SIZE = 4096;
|
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 {
|
typedef struct sign_service_s {
|
||||||
const sign_conf_t *conf;
|
const sign_conf_t *conf;
|
||||||
|
|
@ -194,7 +188,7 @@ fcgi_sign_handler(FCGX_Request* request, void* ctx)
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = check_content_type(request, ACCEPTABLE_CONTENT_TYPE);
|
status = check_content_type(request, &ACCEPTABLE_CONTENT_TYPE, NULL);
|
||||||
if (status != HANDLER_SUCCESS) {
|
if (status != HANDLER_SUCCESS) {
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
@ -238,31 +232,14 @@ fcgi_sign_request_clear(fcgi_sign_request_t *req_info)
|
||||||
memset(req_info, 0, sizeof(fcgi_sign_request_t));
|
memset(req_info, 0, sizeof(fcgi_sign_request_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
static fcgi_handler_status_t
|
|
||||||
fcgi_ok_handler(const FCGX_Request* request, const fcgi_sign_request_t *req_info)
|
|
||||||
{
|
|
||||||
LOG_TRACE("fcgi_ok_handler");
|
|
||||||
|
|
||||||
LOG_DEBUG("response status: " FCGI_OK_RESPONSE_FORMAT, req_info->response);
|
|
||||||
|
|
||||||
if (FCGX_FPrintF(request->out, FCGI_OK_RESPONSE_FORMAT, req_info->response) < 0) {
|
|
||||||
LOG_ERROR("FCGX_FPrintF() failed");
|
|
||||||
return HANDLER_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
return HANDLER_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fcgi_handler_status_t
|
static fcgi_handler_status_t
|
||||||
fcgi_request_finalize(const FCGX_Request* request, fcgi_sign_request_t *req_info,
|
fcgi_request_finalize(const FCGX_Request* request, fcgi_sign_request_t *req_info,
|
||||||
fcgi_handler_status_t status)
|
fcgi_handler_status_t status)
|
||||||
{
|
{
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case HANDLER_SUCCESS:
|
case HANDLER_SUCCESS:
|
||||||
return fcgi_ok_handler(request, req_info);
|
|
||||||
|
|
||||||
case HANDLER_HTTP_OK:
|
case HANDLER_HTTP_OK:
|
||||||
return fcgi_200_ok_handler(request, req_info);
|
return fcgi_200_ok_handler(request, req_info->response);
|
||||||
|
|
||||||
case HANDLER_HTTP_BAD_REQUEST:
|
case HANDLER_HTTP_BAD_REQUEST:
|
||||||
return fcgi_400_bad_request_handler(request, req_info);
|
return fcgi_400_bad_request_handler(request, req_info);
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,10 @@
|
||||||
#define VERIFY_CONF_KEY_LOCATION "location"
|
#define VERIFY_CONF_KEY_LOCATION "location"
|
||||||
#define VERIFY_CONF_KEY_THUMBPRINT "esia_cert_thumbprint"
|
#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 str_t VERIFY_CONF_DEFAULT_LOCATION = str_t_const("/verify");
|
||||||
|
|
||||||
static const int CLIENT_MAX_BODY_SIZE = 8192;
|
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 {
|
typedef struct verify_service_s {
|
||||||
|
|
@ -170,7 +164,7 @@ fcgi_verify_handler(FCGX_Request* request, void* ctx)
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = check_content_type(request, ACCEPTABLE_CONTENT_TYPE);
|
status = check_content_type(request, &ACCEPTABLE_CONTENT_TYPE, NULL);
|
||||||
if (status != HANDLER_SUCCESS) {
|
if (status != HANDLER_SUCCESS) {
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
@ -191,29 +185,6 @@ exit:
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fcgi_handler_status_t
|
|
||||||
fcgi_401_bad_signature_handler(const FCGX_Request* request, void *ctx)
|
|
||||||
{
|
|
||||||
LOG_TRACE("fcgi_401_bad_signature_handler");
|
|
||||||
|
|
||||||
const fcgi_verify_request_t *req_info = (fcgi_verify_request_t*) ctx;
|
|
||||||
|
|
||||||
assert(req_info->verify_error != NULL);
|
|
||||||
|
|
||||||
LOG_DEBUG("response status: " FCGI_401_BAD_SIGNATURE_RESPONSE_FORMAT,
|
|
||||||
req_info->verify_error);
|
|
||||||
|
|
||||||
if (FCGX_FPrintF(request->out, FCGI_401_BAD_SIGNATURE_RESPONSE_FORMAT,
|
|
||||||
req_info->verify_error) < 0) {
|
|
||||||
LOG_ERROR("FCGX_FPrintF() failed");
|
|
||||||
return HANDLER_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
fcgi_print_log(request, "401 Unauthorized");
|
|
||||||
|
|
||||||
return HANDLER_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fcgi_handler_status_t
|
static fcgi_handler_status_t
|
||||||
fcgi_request_finalize(const FCGX_Request* request, fcgi_verify_request_t *req_info,
|
fcgi_request_finalize(const FCGX_Request* request, fcgi_verify_request_t *req_info,
|
||||||
fcgi_handler_status_t status)
|
fcgi_handler_status_t status)
|
||||||
|
|
@ -221,13 +192,13 @@ fcgi_request_finalize(const FCGX_Request* request, fcgi_verify_request_t *req_in
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case HANDLER_SUCCESS:
|
case HANDLER_SUCCESS:
|
||||||
case HANDLER_HTTP_OK:
|
case HANDLER_HTTP_OK:
|
||||||
return fcgi_200_ok_handler(request, req_info);
|
return fcgi_200_ok_handler(request, NULL);
|
||||||
|
|
||||||
case HANDLER_HTTP_BAD_REQUEST:
|
case HANDLER_HTTP_BAD_REQUEST:
|
||||||
return fcgi_400_bad_request_handler(request, req_info);
|
return fcgi_400_bad_request_handler(request, req_info);
|
||||||
|
|
||||||
case HANDLER_HTTP_UNAUTHORIZED:
|
case HANDLER_HTTP_UNAUTHORIZED:
|
||||||
return fcgi_401_bad_signature_handler(request, req_info);
|
return fcgi_401_bad_signature_handler(request, req_info->verify_error);
|
||||||
|
|
||||||
case HANDLER_HTTP_NOT_ACCEPTABLE:
|
case HANDLER_HTTP_NOT_ACCEPTABLE:
|
||||||
return fcgi_406_not_acceptable_handler(request, req_info);
|
return fcgi_406_not_acceptable_handler(request, req_info);
|
||||||
|
|
@ -315,24 +286,12 @@ verify_jwt_sign(fcgi_verify_request_t* req_info, verify_service_t *ctx)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
sign.len = base64_decoded_length(sign_base64.len);
|
if (decode_base64_url_with_alloc(&sign, &sign_base64)) {
|
||||||
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)) {
|
|
||||||
LOG_ERROR("Could not decode jwt sign");
|
LOG_ERROR("Could not decode jwt sign");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
header.len = base64_decoded_length(header_base64.len);
|
if (decode_base64_url_with_alloc(&header, &header_base64)) {
|
||||||
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)) {
|
|
||||||
LOG_ERROR("Could not decode jwt header");
|
LOG_ERROR("Could not decode jwt header");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
275
src/modules/service_verify_detached.c
Normal file
275
src/modules/service_verify_detached.c
Normal 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);
|
||||||
|
}
|
||||||
29
src/modules/service_verify_detached.h
Normal file
29
src/modules/service_verify_detached.h
Normal 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
|
||||||
|
|
@ -166,6 +166,11 @@ service_manager_load_conf(const char* config_file_name, service_manager_conf_t*
|
||||||
goto error;
|
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)) {
|
if (version_conf_load(&services_cf->version_cf)) {
|
||||||
LOG_ERROR("Show version service configuraton loading failed");
|
LOG_ERROR("Show version service configuraton loading failed");
|
||||||
goto error;
|
goto error;
|
||||||
|
|
@ -193,6 +198,7 @@ service_manager_clear_conf(service_manager_conf_t* services_cf)
|
||||||
version_conf_clear(&services_cf->version_cf);
|
version_conf_clear(&services_cf->version_cf);
|
||||||
sign_conf_clear(&services_cf->sign_cf);
|
sign_conf_clear(&services_cf->sign_cf);
|
||||||
verify_conf_clear(&services_cf->verify_cf);
|
verify_conf_clear(&services_cf->verify_cf);
|
||||||
|
verify_detached_conf_clear(&services_cf->verify_detached_cf);
|
||||||
fcgi_clear_conf(&services_cf->fcgi_cf);
|
fcgi_clear_conf(&services_cf->fcgi_cf);
|
||||||
main_conf_clear(&services_cf->main_cf);
|
main_conf_clear(&services_cf->main_cf);
|
||||||
}
|
}
|
||||||
|
|
@ -212,6 +218,8 @@ deinit_services(service_manager_t* services)
|
||||||
|
|
||||||
verify_service_free(services->hverify);
|
verify_service_free(services->hverify);
|
||||||
|
|
||||||
|
verify_detached_service_free(services->hverify_detached);
|
||||||
|
|
||||||
LOG_TRACE("deinit_services exit");
|
LOG_TRACE("deinit_services exit");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -262,6 +270,19 @@ init_services(service_manager_t* services, const service_manager_conf_t* service
|
||||||
goto error;
|
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 */
|
/* show version service */
|
||||||
if (register_service(services, services_cf, fcgi_version_handler, NULL,
|
if (register_service(services, services_cf, fcgi_version_handler, NULL,
|
||||||
services_cf->version_cf.location)) {
|
services_cf->version_cf.location)) {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "modules/service_sign.h"
|
#include "modules/service_sign.h"
|
||||||
#include "modules/service_verify.h"
|
#include "modules/service_verify.h"
|
||||||
|
#include "modules/service_verify_detached.h"
|
||||||
#include "modules/service_version.h"
|
#include "modules/service_version.h"
|
||||||
|
|
||||||
struct service_s;
|
struct service_s;
|
||||||
|
|
@ -45,11 +46,12 @@ struct service_s {
|
||||||
|
|
||||||
|
|
||||||
typedef struct service_manager_conf_s {
|
typedef struct service_manager_conf_s {
|
||||||
fcgi_conf_t fcgi_cf;
|
fcgi_conf_t fcgi_cf;
|
||||||
main_conf_t main_cf;
|
main_conf_t main_cf;
|
||||||
sign_conf_t sign_cf;
|
sign_conf_t sign_cf;
|
||||||
verify_conf_t verify_cf;
|
verify_conf_t verify_cf;
|
||||||
version_conf_t version_cf;
|
verify_detached_conf_t verify_detached_cf;
|
||||||
|
version_conf_t version_cf;
|
||||||
|
|
||||||
} service_manager_conf_t;
|
} service_manager_conf_t;
|
||||||
|
|
||||||
|
|
@ -57,6 +59,7 @@ typedef struct service_manager_s {
|
||||||
HFcgi hfcgi;
|
HFcgi hfcgi;
|
||||||
HSign hsign;
|
HSign hsign;
|
||||||
HVerify hverify;
|
HVerify hverify;
|
||||||
|
HVerifyDetached hverify_detached;
|
||||||
|
|
||||||
} service_manager_t;
|
} service_manager_t;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,3 +167,31 @@ error:
|
||||||
LOG_ERROR("decode_base64_url exit with error");
|
LOG_ERROR("decode_base64_url exit with error");
|
||||||
return -1;
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,6 @@
|
||||||
void encode_base64_url(str_t *dst, const str_t *src);
|
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(str_t *dst, const str_t *src);
|
||||||
|
|
||||||
|
int decode_base64_url_with_alloc(str_t *dst, const str_t *src);
|
||||||
|
|
||||||
#endif // BASE64_H
|
#endif // BASE64_H
|
||||||
|
|
@ -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->CertFreeCertificateChain, lib, "CertFreeCertificateChain");
|
||||||
LIBRARY_RESOLVE(fl->CryptGenRandom, lib, "CryptGenRandom");
|
LIBRARY_RESOLVE(fl->CryptGenRandom, lib, "CryptGenRandom");
|
||||||
LIBRARY_RESOLVE(fl->CertGetIntendedKeyUsage, lib, "CertGetIntendedKeyUsage");
|
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
|
#ifdef UNICODE
|
||||||
LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashW");
|
LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashW");
|
||||||
LIBRARY_RESOLVE(fl->CryptVerifySignature, lib, "CryptVerifySignatureW");
|
LIBRARY_RESOLVE(fl->CryptVerifySignature, lib, "CryptVerifySignatureW");
|
||||||
LIBRARY_RESOLVE(fl->CryptAcquireContext, lib, "CryptAcquireContextW");
|
LIBRARY_RESOLVE(fl->CryptAcquireContext, lib, "CryptAcquireContextW");
|
||||||
|
LIBRARY_RESOLVE(fl->CertNameToStr, lib, "CertNameToStrW");
|
||||||
#else
|
#else
|
||||||
LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashA");
|
LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashA");
|
||||||
LIBRARY_RESOLVE(fl->CryptVerifySignature, lib, "CryptVerifySignatureA");
|
LIBRARY_RESOLVE(fl->CryptVerifySignature, lib, "CryptVerifySignatureA");
|
||||||
LIBRARY_RESOLVE(fl->CryptAcquireContext, lib, "CryptAcquireContextA");
|
LIBRARY_RESOLVE(fl->CryptAcquireContext, lib, "CryptAcquireContextA");
|
||||||
|
LIBRARY_RESOLVE(fl->CertNameToStr, lib, "CertNameToStrA");
|
||||||
#endif // !UNICODE
|
#endif // !UNICODE
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,18 @@ DECLARE_FN(WINADVAPI,
|
||||||
LPCWSTR szDescription,
|
LPCWSTR szDescription,
|
||||||
DWORD dwFlags));
|
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,
|
DECLARE_FN(WINADVAPI,
|
||||||
BOOL,
|
BOOL,
|
||||||
CRYPT_ACQUIRE_CONTEXT_A,
|
CRYPT_ACQUIRE_CONTEXT_A,
|
||||||
|
|
@ -205,14 +217,65 @@ DECLARE_FN(WINADVAPI,
|
||||||
BYTE *pbKeyUsage,
|
BYTE *pbKeyUsage,
|
||||||
IN DWORD cbKeyUsage));
|
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
|
#ifdef UNICODE
|
||||||
#define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_W_FN
|
#define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_W_FN
|
||||||
#define CRYPT_VERIFY_SIGNATURE_FN CRYPT_VERIFY_SIGNATURE_W_FN
|
#define CRYPT_VERIFY_SIGNATURE_FN CRYPT_VERIFY_SIGNATURE_W_FN
|
||||||
#define CRYPT_ACQUIRE_CONTEXT_FN CRYPT_ACQUIRE_CONTEXT_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
|
#else
|
||||||
#define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_A_FN
|
#define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_A_FN
|
||||||
#define CRYPT_VERIFY_SIGNATURE_FN CRYPT_VERIFY_SIGNATURE_A_FN
|
#define CRYPT_VERIFY_SIGNATURE_FN CRYPT_VERIFY_SIGNATURE_A_FN
|
||||||
#define CRYPT_ACQUIRE_CONTEXT_FN CRYPT_ACQUIRE_CONTEXT_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
|
#endif // !UNICODE
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -231,6 +294,7 @@ typedef struct {
|
||||||
CERT_GET_CERTIFICATE_CONTEXT_PROPERTY_FN CertGetCertificateContextProperty;
|
CERT_GET_CERTIFICATE_CONTEXT_PROPERTY_FN CertGetCertificateContextProperty;
|
||||||
CERT_SET_CERTIFICATE_CONTEXT_PROPERTY_FN CertSetCertificateContextProperty;
|
CERT_SET_CERTIFICATE_CONTEXT_PROPERTY_FN CertSetCertificateContextProperty;
|
||||||
CRYPT_VERIFY_SIGNATURE_FN CryptVerifySignature;
|
CRYPT_VERIFY_SIGNATURE_FN CryptVerifySignature;
|
||||||
|
CRYPT_VERIFY_DETACHED_MESSAGE_SIGNATURE_FN CryptVerifyDetachedMessageSignature;
|
||||||
CRYPT_ACQUIRE_CONTEXT_FN CryptAcquireContext;
|
CRYPT_ACQUIRE_CONTEXT_FN CryptAcquireContext;
|
||||||
CRYPT_IMPORT_PUBLIC_KEY_INFO_FN CryptImportPublicKeyInfo;
|
CRYPT_IMPORT_PUBLIC_KEY_INFO_FN CryptImportPublicKeyInfo;
|
||||||
CRYPT_DESTROY_KEY_FN CryptDestroyKey;
|
CRYPT_DESTROY_KEY_FN CryptDestroyKey;
|
||||||
|
|
@ -239,6 +303,11 @@ typedef struct {
|
||||||
CERT_FREE_CERTIFICATE_CHAIN_FN CertFreeCertificateChain;
|
CERT_FREE_CERTIFICATE_CHAIN_FN CertFreeCertificateChain;
|
||||||
CRYPT_GEN_RANDOM_FN CryptGenRandom;
|
CRYPT_GEN_RANDOM_FN CryptGenRandom;
|
||||||
CERT_GET_INTENDED_KEY_USAGE_FN CertGetIntendedKeyUsage;
|
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;
|
} capi_function_list_t;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,13 +100,75 @@ free_cert_chain(PCCERT_CHAIN_CONTEXT chain_ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_trust_status_error(DWORD status, /*out*/ const char **error_code)
|
print_crl_urls_from_certificate(PCCERT_CONTEXT cert)
|
||||||
|
{
|
||||||
|
LOG_TRACE("print_crl_urls_from_certificate enter");
|
||||||
|
|
||||||
|
PCERT_EXTENSION extension = cp_function_list.CertFindExtension(
|
||||||
|
szOID_CRL_DIST_POINTS,
|
||||||
|
cert->pCertInfo->cExtension,
|
||||||
|
cert->pCertInfo->rgExtension
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!extension) {
|
||||||
|
LOG_WARN("CRL Distribution Points extension not found");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD info_size = 0;
|
||||||
|
CRL_DIST_POINTS_INFO *crl_dist_points = NULL;
|
||||||
|
|
||||||
|
if (!cp_function_list.CryptDecodeObjectEx(
|
||||||
|
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
||||||
|
X509_CRL_DIST_POINTS,
|
||||||
|
extension->Value.pbData,
|
||||||
|
extension->Value.cbData,
|
||||||
|
CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
|
||||||
|
NULL,
|
||||||
|
&crl_dist_points,
|
||||||
|
&info_size
|
||||||
|
)) {
|
||||||
|
LOG_ERROR("CryptDecodeObjectEx failed. Desc: 0x%x", cp_function_list.GetLastError());
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crl_dist_points && crl_dist_points->cDistPoint > 0) {
|
||||||
|
for (DWORD i = 0; i < crl_dist_points->cDistPoint; i++) {
|
||||||
|
PCRL_DIST_POINT pDistPoint = &crl_dist_points->rgDistPoint[i];
|
||||||
|
|
||||||
|
if (pDistPoint->DistPointName.dwDistPointNameChoice == CRL_DIST_POINT_FULL_NAME) {
|
||||||
|
PCERT_ALT_NAME_ENTRY pAltNameEntry = pDistPoint->DistPointName._empty_union_.FullName.rgAltEntry;
|
||||||
|
|
||||||
|
for (DWORD j = 0; j < pDistPoint->DistPointName._empty_union_.FullName.cAltEntry; j++) {
|
||||||
|
if (pAltNameEntry[j].dwAltNameChoice == CERT_ALT_NAME_URL) {
|
||||||
|
LOG_INFO("CRL URL: %ls", pAltNameEntry[j]._empty_union_.pwszURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crl_dist_points) {
|
||||||
|
cp_function_list.LocalFree(crl_dist_points);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_TRACE("print_crl_urls_from_certificate exit");
|
||||||
|
return;
|
||||||
|
|
||||||
|
error:
|
||||||
|
LOG_ERROR("print_crl_urls_from_certificate exit with error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
process_trust_status_error(PCCERT_CONTEXT cert, DWORD status, /*out*/ const char **error_code)
|
||||||
{
|
{
|
||||||
LOG_TRACE("process_trust_status_error enter");
|
LOG_TRACE("process_trust_status_error enter");
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case CERT_TRUST_REVOCATION_STATUS_UNKNOWN:
|
case CERT_TRUST_REVOCATION_STATUS_UNKNOWN:
|
||||||
*error_code = CERT_TRUST_REVOCATION_STATUS_UNKNOWN_ERR_CODE;
|
*error_code = CERT_TRUST_REVOCATION_STATUS_UNKNOWN_ERR_CODE;
|
||||||
|
print_crl_urls_from_certificate(cert);
|
||||||
break;
|
break;
|
||||||
case CERT_TRUST_IS_UNTRUSTED_ROOT:
|
case CERT_TRUST_IS_UNTRUSTED_ROOT:
|
||||||
*error_code = CERT_TRUST_IS_UNTRUSTED_ROOT_ERR_CODE;
|
*error_code = CERT_TRUST_IS_UNTRUSTED_ROOT_ERR_CODE;
|
||||||
|
|
@ -153,7 +215,7 @@ get_cert_chain(PCCERT_CONTEXT certificate, /*out*/ const char **error_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chain_ctx->TrustStatus.dwErrorStatus) {
|
if (chain_ctx->TrustStatus.dwErrorStatus) {
|
||||||
process_trust_status_error(chain_ctx->TrustStatus.dwErrorStatus, error_code);
|
process_trust_status_error(certificate, chain_ctx->TrustStatus.dwErrorStatus, error_code);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,6 +282,8 @@ verify_cert_chain(PCCERT_CONTEXT certificate, timer_context_t *timer_ctx, /*out*
|
||||||
|
|
||||||
LOG_TRACE("verify_cert_chain enter");
|
LOG_TRACE("verify_cert_chain enter");
|
||||||
|
|
||||||
|
timer_on_verify_cert_chain_enter(timer_ctx);
|
||||||
|
|
||||||
timer_on_get_cert_chain_enter(timer_ctx);
|
timer_on_get_cert_chain_enter(timer_ctx);
|
||||||
|
|
||||||
PCCERT_CHAIN_CONTEXT chain_ctx = get_cert_chain(certificate, error_code);
|
PCCERT_CHAIN_CONTEXT chain_ctx = get_cert_chain(certificate, error_code);
|
||||||
|
|
@ -240,6 +304,8 @@ exit:
|
||||||
|
|
||||||
LOG_DEBUG("cert_chain: %s", is_verified ? "verified" : "failed");
|
LOG_DEBUG("cert_chain: %s", is_verified ? "verified" : "failed");
|
||||||
|
|
||||||
|
timer_on_verify_cert_chain_exit(timer_ctx);
|
||||||
|
|
||||||
LOG_DEBUG("verify_cert_chain exit");
|
LOG_DEBUG("verify_cert_chain exit");
|
||||||
return is_verified;
|
return is_verified;
|
||||||
}
|
}
|
||||||
|
|
@ -293,7 +359,6 @@ open_signer_cert(cryptopro_context_t *ctx)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
LOG_TRACE("open_signer_cert exit");
|
LOG_TRACE("open_signer_cert exit");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -353,14 +418,10 @@ sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t
|
||||||
BYTE *pbSignedMessageBlob;
|
BYTE *pbSignedMessageBlob;
|
||||||
DWORD cbSignedMessageBlob;
|
DWORD cbSignedMessageBlob;
|
||||||
|
|
||||||
timer_on_verify_cert_chain_enter(ctx->timer_ctx);
|
|
||||||
|
|
||||||
if (!verify_cert_chain(ctx->signer_cert, ctx->timer_ctx, error_code)) {
|
if (!verify_cert_chain(ctx->signer_cert, ctx->timer_ctx, error_code)) {
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
timer_on_verify_cert_chain_exit(ctx->timer_ctx);
|
|
||||||
|
|
||||||
timer_on_acquire_private_key_enter(ctx->timer_ctx);
|
timer_on_acquire_private_key_enter(ctx->timer_ctx);
|
||||||
|
|
||||||
if (!cp_function_list.CryptAcquireCertificatePrivateKey(
|
if (!cp_function_list.CryptAcquireCertificatePrivateKey(
|
||||||
|
|
@ -394,6 +455,10 @@ sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t
|
||||||
}
|
}
|
||||||
|
|
||||||
pbSignedMessageBlob = malloc(cbSignedMessageBlob * sizeof(BYTE));
|
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)) {
|
if (!cp_function_list.CryptSignHash(hash, AT_KEYEXCHANGE, NULL, 0, pbSignedMessageBlob, &cbSignedMessageBlob)) {
|
||||||
LOG_ERROR("CryptSignHash() failed");
|
LOG_ERROR("CryptSignHash() failed");
|
||||||
|
|
@ -703,6 +768,8 @@ get_verify_error(char** verify_error)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("verify error: %s", *verify_error);
|
||||||
|
|
||||||
LOG_TRACE("get_verify_error exit");
|
LOG_TRACE("get_verify_error exit");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -743,14 +810,10 @@ cryptopro_verify(cryptopro_context_t* ctx, const str_t* alg, const str_t* data,
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
timer_on_verify_cert_chain_enter(ctx->timer_ctx);
|
|
||||||
|
|
||||||
if (!verify_cert_chain(certificate, ctx->timer_ctx, error_code)) {
|
if (!verify_cert_chain(certificate, ctx->timer_ctx, error_code)) {
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
timer_on_verify_cert_chain_exit(ctx->timer_ctx);
|
|
||||||
|
|
||||||
LOG_DEBUG("provider: '%s', prov_type: %u", ctx->provider, ctx->prov_type);
|
LOG_DEBUG("provider: '%s', prov_type: %u", ctx->provider, ctx->prov_type);
|
||||||
|
|
||||||
if (!cp_function_list.CryptAcquireContext(&hCryptProv, NULL, ctx->provider, ctx->prov_type,
|
if (!cp_function_list.CryptAcquireContext(&hCryptProv, NULL, ctx->provider, ctx->prov_type,
|
||||||
|
|
@ -842,6 +905,224 @@ exit:
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
get_subject_name(PCCERT_CONTEXT cert, /*out*/ char **subject_name)
|
||||||
|
{
|
||||||
|
LOG_TRACE("get_subject_name enter");
|
||||||
|
|
||||||
|
DWORD len = cp_function_list.CertNameToStr(
|
||||||
|
cert->dwCertEncodingType,
|
||||||
|
&cert->pCertInfo->Subject,
|
||||||
|
CERT_X500_NAME_STR,
|
||||||
|
NULL,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
*subject_name = malloc(len);
|
||||||
|
if (*subject_name == NULL) {
|
||||||
|
LOG_ERROR("Could not allocate memory for Subject Name str (%d bytes)", len);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp_function_list.CertNameToStr(
|
||||||
|
cert->dwCertEncodingType,
|
||||||
|
&cert->pCertInfo->Subject,
|
||||||
|
CERT_X500_NAME_STR,
|
||||||
|
*subject_name,
|
||||||
|
len
|
||||||
|
);
|
||||||
|
|
||||||
|
LOG_DEBUG("Subject Name: %s", *subject_name);
|
||||||
|
|
||||||
|
LOG_TRACE("get_subject_name exit");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error:
|
||||||
|
free(*subject_name);
|
||||||
|
LOG_ERROR("get_subject_name exit with error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
get_issuer_name(PCCERT_CONTEXT cert, /*out*/ char **issuer_name)
|
||||||
|
{
|
||||||
|
LOG_TRACE("get_issuer_name enter");
|
||||||
|
|
||||||
|
DWORD len = cp_function_list.CertNameToStr(
|
||||||
|
cert->dwCertEncodingType,
|
||||||
|
&cert->pCertInfo->Issuer,
|
||||||
|
CERT_X500_NAME_STR,
|
||||||
|
NULL,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
*issuer_name = malloc(len);
|
||||||
|
if (*issuer_name == NULL) {
|
||||||
|
LOG_ERROR("Could not allocate memory for Issuer Name str (%d bytes)", len);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp_function_list.CertNameToStr(
|
||||||
|
cert->dwCertEncodingType,
|
||||||
|
&cert->pCertInfo->Issuer,
|
||||||
|
CERT_X500_NAME_STR,
|
||||||
|
*issuer_name,
|
||||||
|
len
|
||||||
|
);
|
||||||
|
|
||||||
|
LOG_DEBUG("Issuer Name: %s", *issuer_name);
|
||||||
|
|
||||||
|
LOG_TRACE("get_issuer_name exit");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error:
|
||||||
|
free(*issuer_name);
|
||||||
|
LOG_ERROR("get_issuer_name exit with error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
get_signer_cert_info(PCCERT_CONTEXT signer_cert, cert_info_t *signer_cert_info)
|
||||||
|
{
|
||||||
|
LOG_TRACE("get_signer_cert_info enter");
|
||||||
|
|
||||||
|
if (!get_subject_name(signer_cert, &signer_cert_info->subject)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!get_issuer_name(signer_cert, &signer_cert_info->issuer)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_TRACE("get_signer_cert_info exit");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error:
|
||||||
|
LOG_ERROR("get_signer_cert_info exit with error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Проверка количества подписантов сообщения
|
||||||
|
* Предполагается, что количество подписантов должно быть равно 1
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
check_message_signer_count(const str_t *sign)
|
||||||
|
{
|
||||||
|
int signer_count = cp_function_list.CryptGetMessageSignerCount(
|
||||||
|
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
|
||||||
|
(const BYTE *) sign->data,
|
||||||
|
(DWORD) sign->len);
|
||||||
|
|
||||||
|
LOG_DEBUG("signer count = %d", signer_count);
|
||||||
|
|
||||||
|
if (signer_count == 1) {
|
||||||
|
LOG_DEBUG("signer count is ok");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signer_count < 0) {
|
||||||
|
LOG_ERROR("Could not get signer count. Last error code: 0x%08x",
|
||||||
|
cp_function_list.GetLastError());
|
||||||
|
} else {
|
||||||
|
LOG_ERROR("Invalid signer count: %d", signer_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Проверка подписи и извлечение сертификата подписанта
|
||||||
|
* Предполагается, что количество подписантов проверено и равно 1
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
verify_detached_message_signature(const str_t *data, const str_t *sign,
|
||||||
|
timer_context_t *timer_ctx,
|
||||||
|
/*out*/ PCCERT_CONTEXT *signer_cert)
|
||||||
|
{
|
||||||
|
LOG_TRACE("verify_detached_message_signature enter");
|
||||||
|
|
||||||
|
timer_on_verify_detached_message_signature_enter(timer_ctx);
|
||||||
|
|
||||||
|
CRYPT_VERIFY_MESSAGE_PARA verifyParams = {
|
||||||
|
.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA),
|
||||||
|
.dwMsgAndCertEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOL r = cp_function_list.CryptVerifyDetachedMessageSignature(
|
||||||
|
&verifyParams,
|
||||||
|
0, // индекс проверяемой подписи
|
||||||
|
(CONST BYTE *) sign->data,
|
||||||
|
sign->len,
|
||||||
|
1, // количество подписанных сообщений
|
||||||
|
(const BYTE **) &(data->data),
|
||||||
|
(DWORD*) &(data->len),
|
||||||
|
signer_cert
|
||||||
|
);
|
||||||
|
|
||||||
|
timer_on_verify_detached_message_signature_exit(timer_ctx);
|
||||||
|
|
||||||
|
LOG_TRACE("verify_detached_message_signature exit");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cryptopro_verify_detached(cryptopro_context_t *ctx, const str_t *data, const str_t *sign,
|
||||||
|
bool *is_verified, /*out*/ cert_info_t *signer_cert_info,
|
||||||
|
/*out*/ char **verify_error, /*out*/ const char **error_code)
|
||||||
|
{
|
||||||
|
LOG_TRACE("cryptopro_verify_detached enter");
|
||||||
|
|
||||||
|
timer_on_cryptopro_verify_detached_enter(ctx->timer_ctx);
|
||||||
|
|
||||||
|
*is_verified = false;
|
||||||
|
PCCERT_CONTEXT signer_cert = NULL;
|
||||||
|
|
||||||
|
if (!check_message_signer_count(sign)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verify_detached_message_signature(data, sign, ctx->timer_ctx, &signer_cert)) {
|
||||||
|
LOG_DEBUG("sign is valid");
|
||||||
|
|
||||||
|
if (!check_cert_key_usage(signer_cert)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!verify_cert_chain(signer_cert, ctx->timer_ctx, error_code)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!get_signer_cert_info(signer_cert, signer_cert_info)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
*is_verified = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG("sign is invalid");
|
||||||
|
get_verify_error(verify_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signer_cert != NULL) {
|
||||||
|
cp_function_list.CertFreeCertificateContext(signer_cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
timer_on_cryptopro_verify_detached_exit(ctx->timer_ctx);
|
||||||
|
|
||||||
|
LOG_TRACE("cryptopro_verify_detached exit");
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (signer_cert != NULL) {
|
||||||
|
cp_function_list.CertFreeCertificateContext(signer_cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR("cryptopro_verify_detached exit with error. Last error code: 0x%08x",
|
||||||
|
cp_function_list.GetLastError());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
cryptopro_gen_random(const cryptopro_context_t *ctx, unsigned char* data, size_t len)
|
cryptopro_gen_random(const cryptopro_context_t *ctx, unsigned char* data, size_t len)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@ typedef struct cryptopro_context_s {
|
||||||
|
|
||||||
} cryptopro_context_t;
|
} cryptopro_context_t;
|
||||||
|
|
||||||
|
typedef struct cert_info_s {
|
||||||
|
char *subject;
|
||||||
|
char *issuer;
|
||||||
|
|
||||||
|
} cert_info_t;
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
cryptopro_context_set(cryptopro_context_t *ctx, const str_t *cert_thumbprint,
|
cryptopro_context_set(cryptopro_context_t *ctx, const str_t *cert_thumbprint,
|
||||||
|
|
@ -36,6 +41,17 @@ cryptopro_context_set(cryptopro_context_t *ctx, const str_t *cert_thumbprint,
|
||||||
ctx->timer_ctx = timer_ctx;
|
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);
|
bool cryptopro_init(const char* cp_file);
|
||||||
|
|
||||||
int open_signer_cert(cryptopro_context_t *ctx);
|
int open_signer_cert(cryptopro_context_t *ctx);
|
||||||
|
|
@ -48,6 +64,10 @@ int cryptopro_verify(cryptopro_context_t* ctx, const str_t* alg, const str_t* da
|
||||||
const str_t* sign, bool* is_verified, char** verify_error,
|
const str_t* sign, bool* is_verified, char** verify_error,
|
||||||
const char** error_code);
|
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);
|
int cryptopro_gen_random(const cryptopro_context_t *ctx, unsigned char* data, size_t len);
|
||||||
|
|
||||||
#endif // CRYPTOPRO_H_INCLUDED
|
#endif // CRYPTOPRO_H_INCLUDED
|
||||||
|
|
|
||||||
279
src/utils/detached_sign_payload_parser.c
Normal file
279
src/utils/detached_sign_payload_parser.c
Normal 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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
13
src/utils/detached_sign_payload_parser.h
Normal file
13
src/utils/detached_sign_payload_parser.h
Normal 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
|
||||||
|
|
@ -111,14 +111,13 @@ json_get_string_member(Hjson_parser hparser, const char* field_name,
|
||||||
field_value->data);
|
field_value->data);
|
||||||
return 0;
|
return 0;
|
||||||
case 1:
|
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. "
|
"Desc: field does not exist or it has not proper type. "
|
||||||
"Expected: string member",
|
"Expected: string member",
|
||||||
(int) field_value->len, field_value->data);
|
field_name);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_ERROR("json_get_object_member_as_string failed, member: '%.*s'",
|
LOG_ERROR("json_get_object_member_as_string failed, member: '%s'", field_name);
|
||||||
(int) field_value->len, field_value->data);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
677
src/utils/multipart_parser.c
Normal file
677
src/utils/multipart_parser.c
Normal 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;
|
||||||
|
}
|
||||||
65
src/utils/multipart_parser.h
Normal file
65
src/utils/multipart_parser.h
Normal 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
|
||||||
|
|
@ -98,3 +98,50 @@ error:
|
||||||
LOG_ERROR("build_response_from_signature exit with error");
|
LOG_ERROR("build_response_from_signature exit with error");
|
||||||
return NULL;
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef RESPONSE_BUILDER_H_INCLUDED
|
#ifndef RESPONSE_BUILDER_H_INCLUDED
|
||||||
#define RESPONSE_BUILDER_H_INCLUDED
|
#define RESPONSE_BUILDER_H_INCLUDED
|
||||||
|
|
||||||
|
#include "utils/cryptopro.h"
|
||||||
|
|
||||||
/* common functions */
|
/* common functions */
|
||||||
|
|
||||||
|
|
@ -10,5 +11,9 @@ char * build_response_from_error_code(const char *error_code);
|
||||||
|
|
||||||
char * build_response_from_signature(const char *signature, const char *state);
|
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
|
#endif // RESPONSE_BUILDER_H_INCLUDED
|
||||||
|
|
|
||||||
|
|
@ -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");
|
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
|
void
|
||||||
timer_log_sign(const timer_context_t *ctx)
|
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_handler,
|
||||||
diff_verify_cert_chain);
|
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);
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,12 @@ typedef struct timer_context_s
|
||||||
struct timeval cryptopro_verify_start;
|
struct timeval cryptopro_verify_start;
|
||||||
struct timeval cryptopro_verify_end;
|
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;
|
} timer_context_t;
|
||||||
|
|
||||||
void init_timers(timer_context_t *ctx);
|
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_enter(timer_context_t *ctx);
|
||||||
void timer_on_cryptopro_verify_exit(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_sign(const timer_context_t *ctx);
|
||||||
void timer_log_verify(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
|
#endif // TIMER_H_INCLUDED
|
||||||
|
|
@ -7,19 +7,25 @@ include_directories (
|
||||||
)
|
)
|
||||||
|
|
||||||
set (ESM_SOURCES
|
set (ESM_SOURCES
|
||||||
|
../src/utils/detached_sign_payload_parser.c
|
||||||
../src/utils/glib_utils.c
|
../src/utils/glib_utils.c
|
||||||
../src/utils/json_parser.c
|
../src/utils/json_parser.c
|
||||||
../src/utils/jwt.c
|
../src/utils/jwt.c
|
||||||
../src/utils/logger.c
|
../src/utils/logger.c
|
||||||
|
../src/utils/multipart_parser.c
|
||||||
)
|
)
|
||||||
|
|
||||||
set (HEADERS
|
set (HEADERS
|
||||||
|
utils/test_detached_sign_payload_parser.h
|
||||||
utils/test_jwt.h
|
utils/test_jwt.h
|
||||||
|
utils/test_multipart_parser.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set (SOURCES
|
set (SOURCES
|
||||||
main.c
|
main.c
|
||||||
|
utils/test_detached_sign_payload_parser.c
|
||||||
utils/test_jwt.c
|
utils/test_jwt.c
|
||||||
|
utils/test_multipart_parser.c
|
||||||
"${ESM_SOURCES}"
|
"${ESM_SOURCES}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
28
tests/main.c
28
tests/main.c
|
|
@ -1,4 +1,6 @@
|
||||||
|
#include "utils/test_detached_sign_payload_parser.h"
|
||||||
#include "utils/test_jwt.h"
|
#include "utils/test_jwt.h"
|
||||||
|
#include "utils/test_multipart_parser.h"
|
||||||
|
|
||||||
#include <CUnit/Basic.h>
|
#include <CUnit/Basic.h>
|
||||||
|
|
||||||
|
|
@ -11,6 +13,8 @@ int main(int arv, char** argc)
|
||||||
if (CUE_SUCCESS != CU_initialize_registry())
|
if (CUE_SUCCESS != CU_initialize_registry())
|
||||||
return CU_get_error();
|
return CU_get_error();
|
||||||
|
|
||||||
|
// jwt
|
||||||
|
|
||||||
CU_pSuite suite_jwt = CU_add_suite("JWTSuit", NULL, NULL);
|
CU_pSuite suite_jwt = CU_add_suite("JWTSuit", NULL, NULL);
|
||||||
if (NULL == suite_jwt) {
|
if (NULL == suite_jwt) {
|
||||||
goto exit;
|
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_header_payload_and_sign)) goto exit;
|
||||||
if (NULL == CU_ADD_TEST(suite_jwt, test_jwt_get_alg_from_header)) 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_set_mode(CU_BRM_NORMAL);
|
||||||
CU_basic_run_tests();
|
CU_basic_run_tests();
|
||||||
|
|
||||||
|
|
|
||||||
169
tests/utils/test_detached_sign_payload_parser.c
Normal file
169
tests/utils/test_detached_sign_payload_parser.c
Normal 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);
|
||||||
|
}
|
||||||
11
tests/utils/test_detached_sign_payload_parser.h
Normal file
11
tests/utils/test_detached_sign_payload_parser.h
Normal 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
|
||||||
149
tests/utils/test_multipart_parser.c
Normal file
149
tests/utils/test_multipart_parser.c
Normal 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);
|
||||||
|
}
|
||||||
18
tests/utils/test_multipart_parser.h
Normal file
18
tests/utils/test_multipart_parser.h
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue