Merge branch 'release/1.4.1'
This commit is contained in:
commit
13092d7508
35 changed files with 2726 additions and 237 deletions
|
|
@ -2,7 +2,7 @@ CMAKE_MINIMUM_REQUIRED (VERSION 3.0)
|
|||
|
||||
SET (CMAKE_C_COMPILER "gcc")
|
||||
|
||||
PROJECT (ervu-sign-module VERSION 1.3.3 LANGUAGES C)
|
||||
PROJECT (ervu-sign-module VERSION 1.4.1 LANGUAGES C)
|
||||
|
||||
IF (CMAKE_VERBOSE)
|
||||
SET (CMAKE_VERBOSE_MAKEFILE 1)
|
||||
|
|
@ -118,6 +118,7 @@ ADD_EXECUTABLE (${PROJECT_NAME}
|
|||
${UTILS_DIR}/capi.c
|
||||
${UTILS_DIR}/conf_file_context.c
|
||||
${UTILS_DIR}/cryptopro.c
|
||||
${UTILS_DIR}/detached_sign_payload_parser.c
|
||||
${UTILS_DIR}/gconf_file.c
|
||||
${UTILS_DIR}/glib_utils.c
|
||||
${UTILS_DIR}/json_parser.c
|
||||
|
|
@ -125,6 +126,8 @@ ADD_EXECUTABLE (${PROJECT_NAME}
|
|||
${UTILS_DIR}/jwt.c
|
||||
${UTILS_DIR}/library.c
|
||||
${UTILS_DIR}/logger.c
|
||||
${UTILS_DIR}/multipart_parser.c
|
||||
${UTILS_DIR}/response_builder.c
|
||||
${UTILS_DIR}/str_t.c
|
||||
${UTILS_DIR}/timer.c
|
||||
${UTILS_DIR}/uuid.c
|
||||
|
|
@ -134,6 +137,7 @@ ADD_EXECUTABLE (${PROJECT_NAME}
|
|||
${FCGISRV_DIR}/fcgi_utils.c
|
||||
${FCGISRV_DIR}/fcgi_worker.c
|
||||
${MODULES_DIR}/service_sign.c
|
||||
${MODULES_DIR}/service_verify_detached.c
|
||||
${MODULES_DIR}/service_verify.c
|
||||
${MODULES_DIR}/service_version.c
|
||||
)
|
||||
|
|
|
|||
71
README.md
71
README.md
|
|
@ -3,8 +3,9 @@
|
|||
В модуле реализованы следующие функции:
|
||||
- подпись данных
|
||||
- проверка подписи маркера доступа
|
||||
- проверка подписанного сообщения, содержащего отсоединённую (detached) подпись
|
||||
|
||||
### Подпись данных
|
||||
## Подпись данных
|
||||
|
||||
Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain).
|
||||
C помощью ДСЧ генерирует state - набор случайных символов, генерируется по стандарту UUID.
|
||||
|
|
@ -36,14 +37,14 @@ $ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/sign -H "Content-Type: text/pl
|
|||
}
|
||||
```
|
||||
|
||||
### Проверка подписи маркера доступа
|
||||
## Проверка подписи маркера доступа
|
||||
|
||||
Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain).
|
||||
Проверяет подпись маркера доступа, полученного в теле запроса.
|
||||
В ответе возвращает один из следующих статус-кодов:
|
||||
- 200 OK - подпись валидна
|
||||
- 401 Unauthorized - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера)
|
||||
- 500 Internal Server Error - внутренняя ошибка сервера
|
||||
- `200 OK` - подпись валидна
|
||||
- `401 Unauthorized` - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера)
|
||||
- `500 Internal Server Error` - внутренняя ошибка сервера (подробнее см. в `Обработка ошибок`)
|
||||
|
||||
Пример выполнения запроса:
|
||||
```
|
||||
|
|
@ -65,6 +66,61 @@ $ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/verify -H "Content-Type: text/
|
|||
<
|
||||
```
|
||||
|
||||
### Проверка подписанного сообщения, содержащего отсоединённую подпись
|
||||
|
||||
Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: multipart/form-data).
|
||||
Проверяет подписанное сообщение, содержащее отсоединённую (detached) подпись.
|
||||
В ответе возвращает один из следующих статус-кодов:
|
||||
- `200 OK` - подпись валидна, в ответе возвращает поля "Subject" и "Issuer" из свойств сертификата, которым было подписано сообщение
|
||||
- `401 Unauthorized` - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера)
|
||||
- `500 Internal Server Error` - внутренняя ошибка сервера (подробнее см. в `Обработка ошибок`)
|
||||
|
||||
Пример выполнения запроса:
|
||||
```
|
||||
$ /opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/msg/verify_detached \
|
||||
-F "data=@data.csv;type=application/octet-stream" \
|
||||
-F "sign=@data.csv.sig;type=application/octet-stream"
|
||||
* Trying 127.0.0.1:80...
|
||||
* TCP_NODELAY set
|
||||
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
|
||||
> POST /msg/verify_detached HTTP/1.1
|
||||
> Host: 127.0.0.1
|
||||
> User-Agent: curl/7.65.3-DEV
|
||||
> Accept: */*
|
||||
> Content-Length: 2770
|
||||
> Content-Type: multipart/form-data; boundary=------------------------d4c9f889765b7342
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Server: nginx/1.24.0
|
||||
< Date: Tue, 26 Aug 2025 08:58:18 GMT
|
||||
< Content-Type: application/json
|
||||
< Transfer-Encoding: chunked
|
||||
< Connection: keep-alive
|
||||
<
|
||||
{
|
||||
"signer_subject":"C=RU, O=Организация, OU=Отдел тестирования отсоединенной подписи, CN=Фамилия Имя Отчество",
|
||||
"issuer_name": "C=RU, O=УЦ, CN=GOST CA Root"
|
||||
}
|
||||
```
|
||||
|
||||
## Обработка ошибок
|
||||
|
||||
В случае ошибки сервер возвращает ответ со статус-кодом `500 Internal Server Error` и телом в формате JSON, содержащим поле `error_code`.
|
||||
Пример ответа:
|
||||
```json
|
||||
{
|
||||
"error_code": "CERT_TRUST_REVOCATION_STATUS_UNKNOWN"
|
||||
}
|
||||
```
|
||||
|
||||
### Возможные коды ошибок (`error_code`)
|
||||
|
||||
| Код | Описание |
|
||||
|-----------------------------------------|----------|
|
||||
| `CERT_TRUST_REVOCATION_STATUS_UNKNOWN` | Неизвестен статус отзыва сертификата или одного из сертификатов в цепочке (проблема с CRL) |
|
||||
| `CERT_TRUST_IS_UNTRUSTED_ROOT` | Сертификат или цепочка сертификатов основана на ненадежном корневом сертификате |
|
||||
| `CERT_TRUST_IS_NOT_TIME_VALID` | Этот сертификат или один из сертификатов в цепочке сертификатов является недопустимым по времени |
|
||||
|
||||
## Сборка из исходников
|
||||
|
||||
Инструкции по сборке из исходников см. "Инструкция по сборке.md"
|
||||
|
|
@ -103,6 +159,9 @@ sign_cert_password = \*\*\*\* *\# пароль от контейнера*
|
|||
location = /verify *\# значение по умолчанию: /verify*
|
||||
esia_cert_thumbprint = sha1_thumbprint_of_esia_cert0,sha1_thumbprint_of_esia_cert1 *\# список SHA1 отпечатков сертификатов ЕСИА, указанных через запятую (без пробелов)*
|
||||
|
||||
- В секции **\[verify_detached\]** задать настройки проверки подписанного сообщения, содержащего отсоединённую подпись:
|
||||
location = /msg/verify_detached *\# значение по умолчанию: /msg/verify_detached*
|
||||
|
||||
## Логирование времени обработки запросов
|
||||
|
||||
***Для включения*** логирования времени обработки запросов необходимо задать переменную окружения SIGN_LOG_TIME:
|
||||
|
|
@ -116,5 +175,3 @@ export SIGN_LOG_TIME=on
|
|||
```
|
||||
unset SIGN_LOG_TIME
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,3 +17,6 @@ sign_cert_password = ****
|
|||
[verify]
|
||||
#location = /verify
|
||||
esia_cert_thumbprint =
|
||||
|
||||
#[verify_detached]
|
||||
#location = /msg/verify_detached
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
client_max_body_size 10M;
|
||||
|
||||
location / {
|
||||
fastcgi_pass ervu-sign-module:9009;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ http {
|
|||
proxy_temp_path /var/spool/nginx/tmp/proxy;
|
||||
fastcgi_temp_path /var/spool/nginx/tmp/fastcgi;
|
||||
client_body_temp_path /var/spool/nginx/tmp/client;
|
||||
client_max_body_size 10M;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
|
@ -35,12 +36,26 @@ http {
|
|||
# gzip_comp_level 9;
|
||||
include /etc/nginx/sites-enabled.d/*.conf;
|
||||
|
||||
upstream fastcgi {
|
||||
server 127.0.0.1:9009;
|
||||
server 127.0.0.1:9010;
|
||||
server 127.0.0.1:9011;
|
||||
server 127.0.0.1:9012;
|
||||
server 127.0.0.1:9013;
|
||||
server 127.0.0.1:9014;
|
||||
server 127.0.0.1:9015;
|
||||
server 127.0.0.1:9016;
|
||||
server 127.0.0.1:9017;
|
||||
server 127.0.0.1:9018;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
client_max_body_size 10M;
|
||||
|
||||
location / {
|
||||
fastcgi_pass localhost:9009;
|
||||
fastcgi_pass fastcgi;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
41
docs/Инструкция по обновлению с версии 1.3.0 до 1.4.0.txt
Normal file
41
docs/Инструкция по обновлению с версии 1.3.0 до 1.4.0.txt
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
В версии 1.4.0 появились следующие функции:
|
||||
- Проверка подписанного сообщения, содержащего отсоединённую подпись
|
||||
|
||||
Для обновления приложения необходимо выполнить следующие шаги (команды выполняются от имени root):
|
||||
|
||||
1. Остановить службу:
|
||||
```
|
||||
systemctl stop ervu-sign-module
|
||||
```
|
||||
|
||||
2. Обновить приложение:
|
||||
```
|
||||
cp ./ervu-sign-module /opt/ervu-sign-module/
|
||||
```
|
||||
|
||||
3. Запустить службу:
|
||||
```
|
||||
systemctl start ervu-sign-module
|
||||
```
|
||||
|
||||
4. Проверить версию приложения:
|
||||
```
|
||||
/opt/cprocsp/bin/amd64/curl -v http://127.0.0.1/version
|
||||
```
|
||||
|
||||
Статус-код ответа должен быть равен 200 OK.
|
||||
В ответе должна быть возвращена строчка "1.4.0".
|
||||
|
||||
5. В конфигурационном файле /etc/nginx/nginx.conf рекомендуется настроить значение 'client_max_body_size':
|
||||
```
|
||||
server {
|
||||
...
|
||||
client_max_body_size 10M;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
6. После обновления конфигурационного файла /etc/nginx/nginx.conf перезапустить службу nginx:
|
||||
```
|
||||
systemctl reload nginx
|
||||
```
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
#include "fcgi_server_internal.h"
|
||||
|
||||
#include "utils/logger.h"
|
||||
#include "utils/response_builder.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
int
|
||||
fcgi_get_content_length(const FCGX_Request* request)
|
||||
{
|
||||
|
|
@ -46,7 +47,7 @@ check_content_length(int content_length, int client_max_body_size)
|
|||
|
||||
|
||||
fcgi_handler_status_t
|
||||
check_content_type(const FCGX_Request* request, const char* acceptable_content_type)
|
||||
check_content_type(const FCGX_Request* request, const str_t* acceptable_content_type, const char** actual_content_type)
|
||||
{
|
||||
LOG_TRACE("check_content_type");
|
||||
|
||||
|
|
@ -56,13 +57,23 @@ check_content_type(const FCGX_Request* request, const char* acceptable_content_t
|
|||
return HANDLER_HTTP_BAD_REQUEST;
|
||||
}
|
||||
|
||||
if (strcmp(content_type, acceptable_content_type) == 0) {
|
||||
if (actual_content_type) {
|
||||
*actual_content_type = content_type;
|
||||
}
|
||||
|
||||
/*
|
||||
* Проверяем, начинается ли заголовок Content-Type с допустимого префикса
|
||||
* Сравнение производится только для начальной части строки
|
||||
* (игнорируя дополнительные параметры после точки с запятой)
|
||||
* Пример: "multipart/form-data;boundary=--123" является допустимым для "multipart/form-data"
|
||||
*/
|
||||
if (strncmp(content_type, acceptable_content_type->data, acceptable_content_type->len) == 0) {
|
||||
LOG_DEBUG("Content-Type is ok");
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
LOG_WARN("Content-Type is not acceptable, '%s' (!= '%s')",
|
||||
content_type, acceptable_content_type);
|
||||
LOG_WARN("Content-Type is not acceptable, '%s' (!= '%.*s')",
|
||||
content_type, (int) acceptable_content_type->len, acceptable_content_type->data);
|
||||
|
||||
return HANDLER_HTTP_NOT_ACCEPTABLE;
|
||||
}
|
||||
|
|
@ -161,3 +172,72 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va
|
|||
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
fcgi_handler_status_t
|
||||
fcgi_200_ok_handler(const FCGX_Request* request, const char* response)
|
||||
{
|
||||
LOG_TRACE("fcgi_200_ok_handler");
|
||||
|
||||
const char *format = response ? FCGI_200_JSON_RESPONSE_FORMAT : FCGI_200_EMPTY_RESPONSE_FORMAT;
|
||||
|
||||
if (response) {
|
||||
LOG_DEBUG("response:\n" FCGI_200_JSON_RESPONSE_FORMAT, response);
|
||||
} else {
|
||||
LOG_DEBUG("response:\n" FCGI_200_EMPTY_RESPONSE_FORMAT);
|
||||
}
|
||||
|
||||
if (FCGX_FPrintF(request->out, format, response) < 0) {
|
||||
LOG_ERROR("FCGX_FPrintF() failed");
|
||||
LOG_ERROR("fcgi_200_ok_handler exit with error");
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
fcgi_handler_status_t
|
||||
fcgi_401_bad_signature_handler(const FCGX_Request* request, const char* verify_error)
|
||||
{
|
||||
LOG_TRACE("fcgi_401_bad_signature_handler");
|
||||
|
||||
assert(verify_error != NULL);
|
||||
|
||||
LOG_DEBUG("response status: " FCGI_401_RESPONSE_FORMAT, verify_error);
|
||||
|
||||
if (FCGX_FPrintF(request->out, FCGI_401_RESPONSE_FORMAT, verify_error) < 0) {
|
||||
LOG_ERROR("FCGX_FPrintF() failed");
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
fcgi_print_log(request, "401 Unauthorized");
|
||||
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
fcgi_handler_status_t
|
||||
fcgi_500_internal_server_error_handler(const FCGX_Request* request, const char *error_code)
|
||||
{
|
||||
LOG_TRACE("fcgi_500_internal_server_error_handler");
|
||||
|
||||
char *response = build_response_from_error_code(error_code);
|
||||
if (response == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
LOG_DEBUG("response status: " FCGI_500_RESPONSE_FORMAT, response);
|
||||
|
||||
if (FCGX_FPrintF(request->out, FCGI_500_RESPONSE_FORMAT, response) < 0) {
|
||||
LOG_ERROR("FCGX_FPrintF() failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
fcgi_print_log(request, "500 Internal Server Error");
|
||||
|
||||
free(response);
|
||||
return HANDLER_SUCCESS;
|
||||
|
||||
error:
|
||||
free(response);
|
||||
LOG_ERROR("fcgi_500_internal_server_error_handler exit with error");
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "fcgi_server.h"
|
||||
#include "utils/logger.h"
|
||||
#include "utils/str_t.h"
|
||||
|
||||
#include "stdbool.h"
|
||||
|
||||
|
|
@ -67,7 +68,7 @@ check_content_length(int content_length, int client_max_body_size);
|
|||
* Returns: HANDLER_SUCCESS, HANDLER_HTTP_BAD_REQUEST, HANDLER_HTTP_NOT_ACCEPTABLE
|
||||
*/
|
||||
fcgi_handler_status_t
|
||||
check_content_type(const FCGX_Request* request, const char* acceptable_content_type);
|
||||
check_content_type(const FCGX_Request* request, const str_t* acceptable_content_type, const char** actual_content_type);
|
||||
|
||||
/**
|
||||
* Returns: HANDLER_SUCCESS, HANDLER_HTTP_BAD_REQUEST, HANDLER_ERROR
|
||||
|
|
@ -89,10 +90,16 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va
|
|||
|
||||
#define CRLF "\r\n"
|
||||
|
||||
#define FCGI_200_RESPONSE_FORMAT \
|
||||
#define FCGI_200_EMPTY_RESPONSE_FORMAT \
|
||||
"Status: 200 OK" CRLF\
|
||||
CRLF
|
||||
|
||||
#define FCGI_200_JSON_RESPONSE_FORMAT \
|
||||
"Status: 200 OK" CRLF\
|
||||
"Content-type: application/json" CRLF\
|
||||
CRLF\
|
||||
"%s" CRLF
|
||||
|
||||
#define FCGI_400_RESPONSE_FORMAT \
|
||||
"Status: 400 Bad Request" CRLF\
|
||||
"Content-type: text/plain" CRLF\
|
||||
|
|
@ -103,7 +110,7 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va
|
|||
"Status: 401 Unauthorized" CRLF\
|
||||
"Content-type: text/plain" CRLF\
|
||||
CRLF\
|
||||
"401 Unauthorized" CRLF
|
||||
"%s" CRLF
|
||||
|
||||
#define FCGI_404_RESPONSE_FORMAT \
|
||||
"Status: 404 Not Found" CRLF\
|
||||
|
|
@ -131,9 +138,9 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va
|
|||
|
||||
#define FCGI_500_RESPONSE_FORMAT \
|
||||
"Status: 500 Internal Server Error" CRLF\
|
||||
"Content-type: text/plain" CRLF\
|
||||
"Content-type: application/json" CRLF\
|
||||
CRLF\
|
||||
"500 Internal Server Error" CRLF
|
||||
"%s" CRLF
|
||||
|
||||
|
||||
|
||||
|
|
@ -158,12 +165,8 @@ fcgi_print_log(const FCGX_Request* request, const char* status)
|
|||
LOG_INFO("%s %s, '%s'", method, uri, status);
|
||||
}
|
||||
|
||||
static inline fcgi_handler_status_t
|
||||
fcgi_200_ok_handler(const FCGX_Request* request, void* ctx __attribute__((unused)))
|
||||
{
|
||||
LOG_DEBUG("response status: '200 OK'");
|
||||
return FCGI_CHECK_PRINTF_STATUS(request, FCGI_200_RESPONSE_FORMAT, 200);
|
||||
}
|
||||
fcgi_handler_status_t
|
||||
fcgi_200_ok_handler(const FCGX_Request* request, const char* response);
|
||||
|
||||
static inline fcgi_handler_status_t
|
||||
fcgi_400_bad_request_handler(const FCGX_Request* request, void* ctx __attribute__((unused)))
|
||||
|
|
@ -173,12 +176,8 @@ fcgi_400_bad_request_handler(const FCGX_Request* request, void* ctx __attribute_
|
|||
}
|
||||
|
||||
|
||||
static inline fcgi_handler_status_t
|
||||
fcgi_401_unauthorized_handler(const FCGX_Request* request, void* ctx __attribute__((unused)))
|
||||
{
|
||||
fcgi_print_log(request, "401 Unauthorized");
|
||||
return FCGI_CHECK_PRINTF_STATUS(request, FCGI_401_RESPONSE_FORMAT, 401);
|
||||
}
|
||||
fcgi_handler_status_t
|
||||
fcgi_401_bad_signature_handler(const FCGX_Request* request, const char* verify_error);
|
||||
|
||||
|
||||
static inline fcgi_handler_status_t
|
||||
|
|
@ -213,12 +212,8 @@ fcgi_413_request_entity_too_large_handler(const FCGX_Request* request, void* ctx
|
|||
}
|
||||
|
||||
|
||||
static inline fcgi_handler_status_t
|
||||
fcgi_500_internal_server_error_handler(const FCGX_Request* request, void* ctx __attribute__((unused)))
|
||||
{
|
||||
fcgi_print_log(request, "500 Internal Server Error");
|
||||
return FCGI_CHECK_PRINTF_STATUS(request, FCGI_500_RESPONSE_FORMAT, 500);
|
||||
}
|
||||
fcgi_handler_status_t
|
||||
fcgi_500_internal_server_error_handler(const FCGX_Request* request, const char *error_code);
|
||||
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
|||
|
|
@ -3,24 +3,19 @@
|
|||
#include "fcgisrv/fcgi_utils.h"
|
||||
|
||||
#include "utils/cryptopro.h"
|
||||
#include "utils/json_writer.h"
|
||||
#include "utils/timer.h"
|
||||
#include "utils/uuid.h"
|
||||
#include "utils/response_builder.h"
|
||||
|
||||
#define SIGN_CONF_SECTION "sign"
|
||||
#define SIGN_CONF_KEY_LOCATION "location"
|
||||
#define SIGN_CONF_KEY_SIGN_CERT_THUMBPRINT "sign_cert_thumbprint"
|
||||
#define SIGN_CONF_KEY_SIGN_CERT_PASSWORD "sign_cert_password"
|
||||
|
||||
#define FCGI_OK_RESPONSE_FORMAT \
|
||||
"Content-type: application/json" CRLF\
|
||||
CRLF\
|
||||
"%s" CRLF
|
||||
|
||||
static const str_t SIGN_CONF_DEFAULT_LOCATION = str_t_const("/sign");
|
||||
|
||||
static const int CLIENT_MAX_BODY_SIZE = 4096;
|
||||
static const char* ACCEPTABLE_CONTENT_TYPE = "text/plain";
|
||||
static const str_t ACCEPTABLE_CONTENT_TYPE = str_t_const("text/plain");
|
||||
|
||||
typedef struct sign_service_s {
|
||||
const sign_conf_t *conf;
|
||||
|
|
@ -36,12 +31,15 @@ typedef struct fcgi_sign_request_s {
|
|||
int content_length;
|
||||
|
||||
char *response;
|
||||
const char *error_code;
|
||||
|
||||
} fcgi_sign_request_t;
|
||||
|
||||
static void fcgi_sign_request_clear(fcgi_sign_request_t *req_info);
|
||||
|
||||
static fcgi_request_handler_pt fcgi_request_finalize_handler(fcgi_handler_status_t status);
|
||||
static fcgi_handler_status_t fcgi_request_finalize(const FCGX_Request* request,
|
||||
fcgi_sign_request_t *req_info,
|
||||
fcgi_handler_status_t status);
|
||||
|
||||
static int sign_content_with_state(const sign_service_t *hsign, fcgi_sign_request_t *req_info);
|
||||
|
||||
|
|
@ -190,7 +188,7 @@ fcgi_sign_handler(FCGX_Request* request, void* ctx)
|
|||
goto exit;
|
||||
}
|
||||
|
||||
status = check_content_type(request, ACCEPTABLE_CONTENT_TYPE);
|
||||
status = check_content_type(request, &ACCEPTABLE_CONTENT_TYPE, NULL);
|
||||
if (status != HANDLER_SUCCESS) {
|
||||
goto exit;
|
||||
}
|
||||
|
|
@ -212,7 +210,7 @@ fcgi_sign_handler(FCGX_Request* request, void* ctx)
|
|||
// status = HANDLER_SUCCESS;
|
||||
|
||||
exit:
|
||||
status = fcgi_request_finalize_handler(status)(request, &req_info);
|
||||
status = fcgi_request_finalize(request, &req_info, status);
|
||||
|
||||
fcgi_sign_request_clear(&req_info);
|
||||
|
||||
|
|
@ -235,55 +233,29 @@ fcgi_sign_request_clear(fcgi_sign_request_t *req_info)
|
|||
}
|
||||
|
||||
static fcgi_handler_status_t
|
||||
fcgi_ok_handler(const FCGX_Request* request, void *ctx)
|
||||
fcgi_request_finalize(const FCGX_Request* request, fcgi_sign_request_t *req_info,
|
||||
fcgi_handler_status_t status)
|
||||
{
|
||||
LOG_TRACE("fcgi_ok_handler");
|
||||
|
||||
const fcgi_sign_request_t *req_info = (fcgi_sign_request_t*) ctx;
|
||||
|
||||
LOG_DEBUG("response status: " FCGI_OK_RESPONSE_FORMAT, req_info->response);
|
||||
|
||||
if (FCGX_FPrintF(request->out, FCGI_OK_RESPONSE_FORMAT, req_info->response) < 0) {
|
||||
LOG_ERROR("FCGX_FPrintF() failed");
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
static fcgi_request_handler_pt
|
||||
fcgi_request_finalize_handler(fcgi_handler_status_t status)
|
||||
{
|
||||
fcgi_request_handler_pt handler;
|
||||
|
||||
switch (status) {
|
||||
case HANDLER_SUCCESS:
|
||||
handler = fcgi_ok_handler;
|
||||
break;
|
||||
|
||||
case HANDLER_HTTP_OK:
|
||||
handler = fcgi_200_ok_handler;
|
||||
break;
|
||||
return fcgi_200_ok_handler(request, req_info->response);
|
||||
|
||||
case HANDLER_HTTP_BAD_REQUEST:
|
||||
handler = fcgi_400_bad_request_handler;
|
||||
break;
|
||||
return fcgi_400_bad_request_handler(request, req_info);
|
||||
|
||||
case HANDLER_HTTP_NOT_ACCEPTABLE:
|
||||
handler = fcgi_406_not_acceptable_handler;
|
||||
break;
|
||||
return fcgi_406_not_acceptable_handler(request, req_info);
|
||||
|
||||
case HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE:
|
||||
handler = fcgi_413_request_entity_too_large_handler;
|
||||
break;
|
||||
return fcgi_413_request_entity_too_large_handler(request, req_info);
|
||||
|
||||
case HANDLER_ERROR:
|
||||
default:
|
||||
handler = fcgi_500_internal_server_error_handler;
|
||||
break;
|
||||
}
|
||||
|
||||
return handler;
|
||||
return fcgi_500_internal_server_error_handler(request, req_info->error_code);
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
@ -318,52 +290,7 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
static char *
|
||||
generate_response(const char *signature, const char *state)
|
||||
{
|
||||
JsonBuilder *jbuilder;
|
||||
char *response;
|
||||
|
||||
LOG_TRACE("generate_response enter");
|
||||
|
||||
jbuilder = json_builder_new();
|
||||
if (jbuilder == NULL) {
|
||||
LOG_ERROR("json_builder_new failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_builder_begin_object(jbuilder) == NULL) {
|
||||
LOG_ERROR("json_builder_begin_object failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_write_member_string(jbuilder, "signature", signature)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_write_member_string(jbuilder, "state", state)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_builder_end_object(jbuilder) == NULL) {
|
||||
LOG_ERROR("json_builder_end_object failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
response = json_write_to_str(jbuilder);
|
||||
|
||||
g_object_unref(jbuilder);
|
||||
|
||||
LOG_TRACE("generate_response exit");
|
||||
return response;
|
||||
|
||||
error:
|
||||
if (jbuilder != NULL) {
|
||||
g_object_unref(jbuilder);
|
||||
}
|
||||
LOG_ERROR("generate_response exit with error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
sign_content_with_state(const sign_service_t *hsign, fcgi_sign_request_t *req_info)
|
||||
|
|
@ -383,13 +310,14 @@ sign_content_with_state(const sign_service_t *hsign, fcgi_sign_request_t *req_in
|
|||
goto error;
|
||||
}
|
||||
|
||||
if (cryptopro_sign(&hsign->cryptopro_ctx, &content_state, &signature)) {
|
||||
if (cryptopro_sign(&hsign->cryptopro_ctx, &content_state, &signature, &req_info->error_code)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
assert(str_t_is_null_terminated(signature));
|
||||
assert(req_info->error_code == NULL);
|
||||
|
||||
req_info->response = generate_response(signature.data, state);
|
||||
req_info->response = build_response_from_signature(signature.data, state);
|
||||
|
||||
if (req_info->response == NULL) {
|
||||
goto error;
|
||||
|
|
|
|||
|
|
@ -11,16 +11,10 @@
|
|||
#define VERIFY_CONF_KEY_LOCATION "location"
|
||||
#define VERIFY_CONF_KEY_THUMBPRINT "esia_cert_thumbprint"
|
||||
|
||||
#define FCGI_401_BAD_SIGNATURE_RESPONSE_FORMAT \
|
||||
"Status: 401 Unauthorized" CRLF\
|
||||
"Content-type: text/plain" CRLF\
|
||||
CRLF\
|
||||
"%s" CRLF
|
||||
|
||||
static const str_t VERIFY_CONF_DEFAULT_LOCATION = str_t_const("/verify");
|
||||
|
||||
static const int CLIENT_MAX_BODY_SIZE = 8192;
|
||||
static const char* ACCEPTABLE_CONTENT_TYPE = "text/plain";
|
||||
static const str_t ACCEPTABLE_CONTENT_TYPE = str_t_const("text/plain");
|
||||
|
||||
|
||||
typedef struct verify_service_s {
|
||||
|
|
@ -37,12 +31,15 @@ typedef struct fcgi_verify_request_s {
|
|||
char *content;
|
||||
int content_length;
|
||||
|
||||
char *verify_error;
|
||||
char *verify_error; // клиентская ошибка (подпись невалидна) - статус 401
|
||||
const char *error_code; // серверная ошибка - статус 500
|
||||
|
||||
} fcgi_verify_request_t;
|
||||
|
||||
|
||||
static fcgi_request_handler_pt fcgi_request_finalize_handler(fcgi_handler_status_t status);
|
||||
static fcgi_handler_status_t fcgi_request_finalize(const FCGX_Request* request,
|
||||
fcgi_verify_request_t *req_info,
|
||||
fcgi_handler_status_t status);
|
||||
static void fcgi_verify_request_clear(fcgi_verify_request_t *req_info);
|
||||
static fcgi_handler_status_t verify_jwt_sign(fcgi_verify_request_t* req_info,
|
||||
verify_service_t *ctx);
|
||||
|
|
@ -167,7 +164,7 @@ fcgi_verify_handler(FCGX_Request* request, void* ctx)
|
|||
goto exit;
|
||||
}
|
||||
|
||||
status = check_content_type(request, ACCEPTABLE_CONTENT_TYPE);
|
||||
status = check_content_type(request, &ACCEPTABLE_CONTENT_TYPE, NULL);
|
||||
if (status != HANDLER_SUCCESS) {
|
||||
goto exit;
|
||||
}
|
||||
|
|
@ -180,7 +177,7 @@ fcgi_verify_handler(FCGX_Request* request, void* ctx)
|
|||
status = verify_jwt_sign(&req_info, ctx);
|
||||
|
||||
exit:
|
||||
status = fcgi_request_finalize_handler(status)(request, &req_info);
|
||||
status = fcgi_request_finalize(request, &req_info, status);
|
||||
|
||||
fcgi_verify_request_clear(&req_info);
|
||||
|
||||
|
|
@ -189,62 +186,32 @@ exit:
|
|||
}
|
||||
|
||||
static fcgi_handler_status_t
|
||||
fcgi_401_bad_signature_handler(const FCGX_Request* request, void *ctx)
|
||||
fcgi_request_finalize(const FCGX_Request* request, fcgi_verify_request_t *req_info,
|
||||
fcgi_handler_status_t status)
|
||||
{
|
||||
LOG_TRACE("fcgi_401_bad_signature_handler");
|
||||
|
||||
const fcgi_verify_request_t *req_info = (fcgi_verify_request_t*) ctx;
|
||||
|
||||
assert(req_info->verify_error != NULL);
|
||||
|
||||
LOG_DEBUG("response status: " FCGI_401_BAD_SIGNATURE_RESPONSE_FORMAT,
|
||||
req_info->verify_error);
|
||||
|
||||
if (FCGX_FPrintF(request->out, FCGI_401_BAD_SIGNATURE_RESPONSE_FORMAT,
|
||||
req_info->verify_error) < 0) {
|
||||
LOG_ERROR("FCGX_FPrintF() failed");
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
fcgi_print_log(request, "401 Unauthorized");
|
||||
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
static fcgi_request_handler_pt
|
||||
fcgi_request_finalize_handler(fcgi_handler_status_t status)
|
||||
{
|
||||
fcgi_request_handler_pt handler;
|
||||
|
||||
switch (status) {
|
||||
case HANDLER_SUCCESS:
|
||||
case HANDLER_HTTP_OK:
|
||||
handler = fcgi_200_ok_handler;
|
||||
break;
|
||||
return fcgi_200_ok_handler(request, NULL);
|
||||
|
||||
case HANDLER_HTTP_BAD_REQUEST:
|
||||
handler = fcgi_400_bad_request_handler;
|
||||
break;
|
||||
return fcgi_400_bad_request_handler(request, req_info);
|
||||
|
||||
case HANDLER_HTTP_UNAUTHORIZED:
|
||||
handler = fcgi_401_bad_signature_handler;
|
||||
break;
|
||||
return fcgi_401_bad_signature_handler(request, req_info->verify_error);
|
||||
|
||||
case HANDLER_HTTP_NOT_ACCEPTABLE:
|
||||
handler = fcgi_406_not_acceptable_handler;
|
||||
break;
|
||||
return fcgi_406_not_acceptable_handler(request, req_info);
|
||||
|
||||
case HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE:
|
||||
handler = fcgi_413_request_entity_too_large_handler;
|
||||
break;
|
||||
return fcgi_413_request_entity_too_large_handler(request, req_info);
|
||||
|
||||
case HANDLER_ERROR:
|
||||
default:
|
||||
handler = fcgi_500_internal_server_error_handler;
|
||||
break;
|
||||
}
|
||||
|
||||
return handler;
|
||||
return fcgi_500_internal_server_error_handler(request, req_info->error_code);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -259,9 +226,10 @@ fcgi_verify_request_clear(fcgi_verify_request_t *req_info)
|
|||
}
|
||||
|
||||
static int
|
||||
verify_sign_using_thumbprint_list(cryptopro_context_t *ctx, const string_list_t* thumbprint_list,
|
||||
verify_sign_using_thumbprint_list(cryptopro_context_t* ctx, const string_list_t* thumbprint_list,
|
||||
const str_t* alg, const str_t* header_payload,
|
||||
const str_t* sign, bool* is_verified, char** verify_error)
|
||||
const str_t* sign, bool* is_verified, char** verify_error,
|
||||
const char** error_code)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
|
|
@ -278,7 +246,7 @@ verify_sign_using_thumbprint_list(cryptopro_context_t *ctx, const string_list_t*
|
|||
|
||||
ctx->cert_thumbprint = &thumbprint;
|
||||
|
||||
rc = cryptopro_verify(ctx, alg, header_payload, sign, is_verified, verify_error);
|
||||
rc = cryptopro_verify(ctx, alg, header_payload, sign, is_verified, verify_error, error_code);
|
||||
if (rc) {
|
||||
LOG_ERROR("cryptopro_verify() failed for cert with thumbprint '%s'", cert_thumbprint);
|
||||
}
|
||||
|
|
@ -318,24 +286,12 @@ verify_jwt_sign(fcgi_verify_request_t* req_info, verify_service_t *ctx)
|
|||
goto error;
|
||||
}
|
||||
|
||||
sign.len = base64_decoded_length(sign_base64.len);
|
||||
sign.data = calloc(1, sign.len + 1);
|
||||
if (sign.data == NULL) {
|
||||
LOG_ERROR("Could not allocate memory for decoded sign (%zd bytes)", sign.len + 1);
|
||||
goto error;
|
||||
}
|
||||
if (decode_base64_url(&sign, &sign_base64)) {
|
||||
if (decode_base64_url_with_alloc(&sign, &sign_base64)) {
|
||||
LOG_ERROR("Could not decode jwt sign");
|
||||
goto error;
|
||||
}
|
||||
|
||||
header.len = base64_decoded_length(header_base64.len);
|
||||
header.data = calloc(1, header.len + 1);
|
||||
if (header.data == NULL) {
|
||||
LOG_ERROR("Could not allocate memory for decoded header (%zd bytes)", header.len + 1);
|
||||
goto error;
|
||||
}
|
||||
if (decode_base64_url(&header, &header_base64)) {
|
||||
if (decode_base64_url_with_alloc(&header, &header_base64)) {
|
||||
LOG_ERROR("Could not decode jwt header");
|
||||
goto error;
|
||||
}
|
||||
|
|
@ -347,7 +303,7 @@ verify_jwt_sign(fcgi_verify_request_t* req_info, verify_service_t *ctx)
|
|||
if (verify_sign_using_thumbprint_list(&ctx->cryptopro_ctx,
|
||||
&ctx->conf->esia_cert_thumbprint_list, &alg,
|
||||
&header_payload, &sign, &is_verified,
|
||||
&req_info->verify_error)) {
|
||||
&req_info->verify_error, &req_info->error_code)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
|
|
|||
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;
|
||||
}
|
||||
|
||||
if (verify_detached_conf_load(&services_cf->verify_detached_cf, conf_file)) {
|
||||
LOG_ERROR("Verify detached message signature service configuraton loading failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (version_conf_load(&services_cf->version_cf)) {
|
||||
LOG_ERROR("Show version service configuraton loading failed");
|
||||
goto error;
|
||||
|
|
@ -193,6 +198,7 @@ service_manager_clear_conf(service_manager_conf_t* services_cf)
|
|||
version_conf_clear(&services_cf->version_cf);
|
||||
sign_conf_clear(&services_cf->sign_cf);
|
||||
verify_conf_clear(&services_cf->verify_cf);
|
||||
verify_detached_conf_clear(&services_cf->verify_detached_cf);
|
||||
fcgi_clear_conf(&services_cf->fcgi_cf);
|
||||
main_conf_clear(&services_cf->main_cf);
|
||||
}
|
||||
|
|
@ -212,6 +218,8 @@ deinit_services(service_manager_t* services)
|
|||
|
||||
verify_service_free(services->hverify);
|
||||
|
||||
verify_detached_service_free(services->hverify_detached);
|
||||
|
||||
LOG_TRACE("deinit_services exit");
|
||||
|
||||
return 0;
|
||||
|
|
@ -262,6 +270,19 @@ init_services(service_manager_t* services, const service_manager_conf_t* service
|
|||
goto error;
|
||||
}
|
||||
|
||||
/* verify detached message signature service */
|
||||
services->hverify_detached = verify_detached_service_create(&services_cf->verify_detached_cf,
|
||||
&services_cf->main_cf);
|
||||
if (services->hverify_detached == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (register_service(services, services_cf, fcgi_verify_detached_handler, services->hverify_detached,
|
||||
&services_cf->verify_detached_cf.location)) {
|
||||
LOG_ERROR("Could not register 'verify detached message signature service'");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* show version service */
|
||||
if (register_service(services, services_cf, fcgi_version_handler, NULL,
|
||||
services_cf->version_cf.location)) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "modules/service_sign.h"
|
||||
#include "modules/service_verify.h"
|
||||
#include "modules/service_verify_detached.h"
|
||||
#include "modules/service_version.h"
|
||||
|
||||
struct service_s;
|
||||
|
|
@ -49,6 +50,7 @@ typedef struct service_manager_conf_s {
|
|||
main_conf_t main_cf;
|
||||
sign_conf_t sign_cf;
|
||||
verify_conf_t verify_cf;
|
||||
verify_detached_conf_t verify_detached_cf;
|
||||
version_conf_t version_cf;
|
||||
|
||||
} service_manager_conf_t;
|
||||
|
|
@ -57,6 +59,7 @@ typedef struct service_manager_s {
|
|||
HFcgi hfcgi;
|
||||
HSign hsign;
|
||||
HVerify hverify;
|
||||
HVerifyDetached hverify_detached;
|
||||
|
||||
} service_manager_t;
|
||||
|
||||
|
|
|
|||
|
|
@ -167,3 +167,31 @@ error:
|
|||
LOG_ERROR("decode_base64_url exit with error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
decode_base64_url_with_alloc(str_t *dst, const str_t *src)
|
||||
{
|
||||
LOG_TRACE("decode_base64_url_with_alloc enter");
|
||||
|
||||
dst->len = base64_decoded_length(src->len);
|
||||
|
||||
dst->data = calloc(1, dst->len + 1);
|
||||
if (dst->data == NULL) {
|
||||
LOG_ERROR("Could not allocate memory for decoded data (%zd bytes)", dst->len + 1);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (decode_base64_url(dst, src)) {
|
||||
LOG_ERROR("Could not decode data");
|
||||
goto error;
|
||||
}
|
||||
|
||||
LOG_TRACE("decode_base64_url_with_alloc exit");
|
||||
return 0;
|
||||
|
||||
error:
|
||||
str_t_clear(dst);
|
||||
LOG_ERROR("decode_base64_url_with_alloc exit with error");
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,6 @@
|
|||
void encode_base64_url(str_t *dst, const str_t *src);
|
||||
int decode_base64_url(str_t *dst, const str_t *src);
|
||||
|
||||
int decode_base64_url_with_alloc(str_t *dst, const str_t *src);
|
||||
|
||||
#endif // BASE64_H
|
||||
|
|
@ -32,15 +32,22 @@ capi_function_list_init(library_t *lib, capi_function_list_t *fl)
|
|||
LIBRARY_RESOLVE(fl->CertFreeCertificateChain, lib, "CertFreeCertificateChain");
|
||||
LIBRARY_RESOLVE(fl->CryptGenRandom, lib, "CryptGenRandom");
|
||||
LIBRARY_RESOLVE(fl->CertGetIntendedKeyUsage, lib, "CertGetIntendedKeyUsage");
|
||||
LIBRARY_RESOLVE(fl->CryptVerifyDetachedMessageSignature, lib, "CryptVerifyDetachedMessageSignature");
|
||||
LIBRARY_RESOLVE(fl->CryptGetMessageSignerCount, lib, "CryptGetMessageSignerCount");
|
||||
LIBRARY_RESOLVE(fl->CertFindExtension, lib, "CertFindExtension");
|
||||
LIBRARY_RESOLVE(fl->CryptDecodeObjectEx, lib, "CryptDecodeObjectEx");
|
||||
LIBRARY_RESOLVE(fl->LocalFree, lib, "LocalFree");
|
||||
|
||||
#ifdef UNICODE
|
||||
LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashW");
|
||||
LIBRARY_RESOLVE(fl->CryptVerifySignature, lib, "CryptVerifySignatureW");
|
||||
LIBRARY_RESOLVE(fl->CryptAcquireContext, lib, "CryptAcquireContextW");
|
||||
LIBRARY_RESOLVE(fl->CertNameToStr, lib, "CertNameToStrW");
|
||||
#else
|
||||
LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashA");
|
||||
LIBRARY_RESOLVE(fl->CryptVerifySignature, lib, "CryptVerifySignatureA");
|
||||
LIBRARY_RESOLVE(fl->CryptAcquireContext, lib, "CryptAcquireContextA");
|
||||
LIBRARY_RESOLVE(fl->CertNameToStr, lib, "CertNameToStrA");
|
||||
#endif // !UNICODE
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -134,6 +134,18 @@ DECLARE_FN(WINADVAPI,
|
|||
LPCWSTR szDescription,
|
||||
DWORD dwFlags));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
BOOL,
|
||||
CRYPT_VERIFY_DETACHED_MESSAGE_SIGNATURE,
|
||||
(PCRYPT_VERIFY_MESSAGE_PARA pVerifyPara,
|
||||
DWORD dwSignerIndex,
|
||||
const BYTE *pbDetachedSignBlob,
|
||||
DWORD cbDetachedSignBlob,
|
||||
DWORD cToBeSigned,
|
||||
const BYTE *rgpbToBeSigned[],
|
||||
DWORD rgcbToBeSigned[],
|
||||
PCCERT_CONTEXT *ppSignerCert));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
BOOL,
|
||||
CRYPT_ACQUIRE_CONTEXT_A,
|
||||
|
|
@ -205,14 +217,65 @@ DECLARE_FN(WINADVAPI,
|
|||
BYTE *pbKeyUsage,
|
||||
IN DWORD cbKeyUsage));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
DWORD,
|
||||
CERT_NAME_TO_STR_A,
|
||||
(DWORD dwCertEncodingType,
|
||||
PCERT_NAME_BLOB pName,
|
||||
DWORD dwStrType,
|
||||
LPSTR psz,
|
||||
DWORD csz));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
DWORD,
|
||||
CERT_NAME_TO_STR_W,
|
||||
(DWORD dwCertEncodingType,
|
||||
PCERT_NAME_BLOB pName,
|
||||
DWORD dwStrType,
|
||||
LPWSTR psz,
|
||||
DWORD csz));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
LONG,
|
||||
CRYPT_GET_MESSAGE_SIGNER_COUNT,
|
||||
(DWORD dwMsgEncodingType,
|
||||
const BYTE *pbSignedBlob,
|
||||
DWORD cbSignedBlob));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
PCERT_EXTENSION,
|
||||
CERT_FIND_EXTENSION,
|
||||
(LPCSTR pszObjId,
|
||||
DWORD cExtensions,
|
||||
CERT_EXTENSION rgExtensions[]));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
BOOL,
|
||||
CRYPT_DECODE_OBJECT_EX,
|
||||
(DWORD dwCertEncodingType,
|
||||
LPCSTR lpszStructType,
|
||||
const BYTE *pbEncoded,
|
||||
DWORD cbEncoded,
|
||||
DWORD dwFlags,
|
||||
PCRYPT_DECODE_PARA pDecodePara,
|
||||
void *pvStructInfo,
|
||||
DWORD *pcbStructInfo));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
HLOCAL,
|
||||
LOCAL_FREE,
|
||||
(HLOCAL hMem));
|
||||
|
||||
#ifdef UNICODE
|
||||
#define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_W_FN
|
||||
#define CRYPT_VERIFY_SIGNATURE_FN CRYPT_VERIFY_SIGNATURE_W_FN
|
||||
#define CRYPT_ACQUIRE_CONTEXT_FN CRYPT_ACQUIRE_CONTEXT_W_FN
|
||||
#define CERT_NAME_TO_STR_FN CERT_NAME_TO_STR_W_FN
|
||||
#else
|
||||
#define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_A_FN
|
||||
#define CRYPT_VERIFY_SIGNATURE_FN CRYPT_VERIFY_SIGNATURE_A_FN
|
||||
#define CRYPT_ACQUIRE_CONTEXT_FN CRYPT_ACQUIRE_CONTEXT_A_FN
|
||||
#define CERT_NAME_TO_STR_FN CERT_NAME_TO_STR_A_FN
|
||||
#endif // !UNICODE
|
||||
|
||||
|
||||
|
|
@ -231,6 +294,7 @@ typedef struct {
|
|||
CERT_GET_CERTIFICATE_CONTEXT_PROPERTY_FN CertGetCertificateContextProperty;
|
||||
CERT_SET_CERTIFICATE_CONTEXT_PROPERTY_FN CertSetCertificateContextProperty;
|
||||
CRYPT_VERIFY_SIGNATURE_FN CryptVerifySignature;
|
||||
CRYPT_VERIFY_DETACHED_MESSAGE_SIGNATURE_FN CryptVerifyDetachedMessageSignature;
|
||||
CRYPT_ACQUIRE_CONTEXT_FN CryptAcquireContext;
|
||||
CRYPT_IMPORT_PUBLIC_KEY_INFO_FN CryptImportPublicKeyInfo;
|
||||
CRYPT_DESTROY_KEY_FN CryptDestroyKey;
|
||||
|
|
@ -239,6 +303,11 @@ typedef struct {
|
|||
CERT_FREE_CERTIFICATE_CHAIN_FN CertFreeCertificateChain;
|
||||
CRYPT_GEN_RANDOM_FN CryptGenRandom;
|
||||
CERT_GET_INTENDED_KEY_USAGE_FN CertGetIntendedKeyUsage;
|
||||
CERT_NAME_TO_STR_FN CertNameToStr;
|
||||
CRYPT_GET_MESSAGE_SIGNER_COUNT_FN CryptGetMessageSignerCount;
|
||||
CERT_FIND_EXTENSION_FN CertFindExtension;
|
||||
CRYPT_DECODE_OBJECT_EX_FN CryptDecodeObjectEx;
|
||||
LOCAL_FREE_FN LocalFree;
|
||||
|
||||
} capi_function_list_t;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@
|
|||
#include "library.h"
|
||||
#include "logger.h"
|
||||
|
||||
static const char* CERT_TRUST_IS_UNTRUSTED_ROOT_ERR_CODE = "CERT_TRUST_IS_UNTRUSTED_ROOT";
|
||||
static const char* CERT_TRUST_REVOCATION_STATUS_UNKNOWN_ERR_CODE = "CERT_TRUST_REVOCATION_STATUS_UNKNOWN";
|
||||
static const char* CERT_TRUST_IS_NOT_TIME_VALID_ERR_CODE = "CERT_TRUST_IS_NOT_TIME_VALID";
|
||||
|
||||
static capi_function_list_t cp_function_list;
|
||||
static library_t libcapi;
|
||||
|
||||
static int sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign);
|
||||
static int sign_hash_data(const cryptopro_context_t *ctx, const str_t *data,
|
||||
/*out*/ str_t *sign, /*out*/ const char **error_code);
|
||||
|
||||
static int reverse_sign(const str_t *sign, /*out*/ str_t *sign_reversed);
|
||||
|
||||
|
|
@ -38,7 +42,8 @@ cryptopro_init(const char* cp_file)
|
|||
}
|
||||
|
||||
int
|
||||
cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign)
|
||||
cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data,
|
||||
/*out*/ str_t *sign, /*out*/ const char **error_code)
|
||||
{
|
||||
str_t signed_data = str_t_null;
|
||||
str_t sign_reversed = str_t_null;
|
||||
|
|
@ -51,7 +56,7 @@ cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t
|
|||
assert(data != NULL && !str_t_is_null(*data));
|
||||
assert(sign != NULL);
|
||||
|
||||
if (sign_hash_data(ctx, data, &signed_data)) {
|
||||
if (sign_hash_data(ctx, data, &signed_data, error_code)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
|
@ -94,8 +99,100 @@ free_cert_chain(PCCERT_CHAIN_CONTEXT chain_ctx)
|
|||
LOG_TRACE("free_cert_chain exit");
|
||||
}
|
||||
|
||||
static void
|
||||
print_crl_urls_from_certificate(PCCERT_CONTEXT cert)
|
||||
{
|
||||
LOG_TRACE("print_crl_urls_from_certificate enter");
|
||||
|
||||
PCERT_EXTENSION extension = cp_function_list.CertFindExtension(
|
||||
szOID_CRL_DIST_POINTS,
|
||||
cert->pCertInfo->cExtension,
|
||||
cert->pCertInfo->rgExtension
|
||||
);
|
||||
|
||||
if (!extension) {
|
||||
LOG_WARN("CRL Distribution Points extension not found");
|
||||
goto error;
|
||||
}
|
||||
|
||||
DWORD info_size = 0;
|
||||
CRL_DIST_POINTS_INFO *crl_dist_points = NULL;
|
||||
|
||||
if (!cp_function_list.CryptDecodeObjectEx(
|
||||
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
||||
X509_CRL_DIST_POINTS,
|
||||
extension->Value.pbData,
|
||||
extension->Value.cbData,
|
||||
CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
|
||||
NULL,
|
||||
&crl_dist_points,
|
||||
&info_size
|
||||
)) {
|
||||
LOG_ERROR("CryptDecodeObjectEx failed. Desc: 0x%x", cp_function_list.GetLastError());
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (crl_dist_points && crl_dist_points->cDistPoint > 0) {
|
||||
for (DWORD i = 0; i < crl_dist_points->cDistPoint; i++) {
|
||||
PCRL_DIST_POINT pDistPoint = &crl_dist_points->rgDistPoint[i];
|
||||
|
||||
if (pDistPoint->DistPointName.dwDistPointNameChoice == CRL_DIST_POINT_FULL_NAME) {
|
||||
PCERT_ALT_NAME_ENTRY pAltNameEntry = pDistPoint->DistPointName._empty_union_.FullName.rgAltEntry;
|
||||
|
||||
for (DWORD j = 0; j < pDistPoint->DistPointName._empty_union_.FullName.cAltEntry; j++) {
|
||||
if (pAltNameEntry[j].dwAltNameChoice == CERT_ALT_NAME_URL) {
|
||||
LOG_INFO("CRL URL: %ls", pAltNameEntry[j]._empty_union_.pwszURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (crl_dist_points) {
|
||||
cp_function_list.LocalFree(crl_dist_points);
|
||||
}
|
||||
|
||||
LOG_TRACE("print_crl_urls_from_certificate exit");
|
||||
return;
|
||||
|
||||
error:
|
||||
LOG_ERROR("print_crl_urls_from_certificate exit with error");
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
process_trust_status_error(PCCERT_CONTEXT cert, DWORD status, /*out*/ const char **error_code)
|
||||
{
|
||||
LOG_TRACE("process_trust_status_error enter");
|
||||
|
||||
switch (status) {
|
||||
case CERT_TRUST_REVOCATION_STATUS_UNKNOWN:
|
||||
*error_code = CERT_TRUST_REVOCATION_STATUS_UNKNOWN_ERR_CODE;
|
||||
print_crl_urls_from_certificate(cert);
|
||||
break;
|
||||
case CERT_TRUST_IS_UNTRUSTED_ROOT:
|
||||
*error_code = CERT_TRUST_IS_UNTRUSTED_ROOT_ERR_CODE;
|
||||
break;
|
||||
case CERT_TRUST_IS_NOT_TIME_VALID:
|
||||
*error_code = CERT_TRUST_IS_NOT_TIME_VALID_ERR_CODE;
|
||||
break;
|
||||
default:
|
||||
*error_code = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*error_code) {
|
||||
LOG_WARN("The certificate is not trusted. CERT_TRUST_STATUS: '0x%08x' (%s)",
|
||||
status, *error_code);
|
||||
} else {
|
||||
LOG_WARN("The certificate is not trusted. CERT_TRUST_STATUS: '0x%08x'", status);
|
||||
}
|
||||
|
||||
LOG_TRACE("process_trust_status_error exit");
|
||||
}
|
||||
|
||||
static PCCERT_CHAIN_CONTEXT
|
||||
get_cert_chain(PCCERT_CONTEXT certificate)
|
||||
get_cert_chain(PCCERT_CONTEXT certificate, /*out*/ const char **error_code)
|
||||
{
|
||||
LOG_TRACE("get_cert_chain enter");
|
||||
|
||||
|
|
@ -118,8 +215,7 @@ get_cert_chain(PCCERT_CONTEXT certificate)
|
|||
}
|
||||
|
||||
if (chain_ctx->TrustStatus.dwErrorStatus) {
|
||||
LOG_WARN("The certificate is not trusted. CERT_TRUST_STATUS: '0x%08x'",
|
||||
chain_ctx->TrustStatus.dwErrorStatus);
|
||||
process_trust_status_error(certificate, chain_ctx->TrustStatus.dwErrorStatus, error_code);
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
|
@ -180,15 +276,17 @@ exit:
|
|||
}
|
||||
|
||||
static bool
|
||||
verify_cert_chain(PCCERT_CONTEXT certificate, timer_context_t *timer_ctx)
|
||||
verify_cert_chain(PCCERT_CONTEXT certificate, timer_context_t *timer_ctx, /*out*/ const char **error_code)
|
||||
{
|
||||
bool is_verified = false;
|
||||
|
||||
LOG_TRACE("verify_cert_chain enter");
|
||||
|
||||
timer_on_verify_cert_chain_enter(timer_ctx);
|
||||
|
||||
timer_on_get_cert_chain_enter(timer_ctx);
|
||||
|
||||
PCCERT_CHAIN_CONTEXT chain_ctx = get_cert_chain(certificate);
|
||||
PCCERT_CHAIN_CONTEXT chain_ctx = get_cert_chain(certificate, error_code);
|
||||
if (chain_ctx == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
|
|
@ -206,6 +304,8 @@ exit:
|
|||
|
||||
LOG_DEBUG("cert_chain: %s", is_verified ? "verified" : "failed");
|
||||
|
||||
timer_on_verify_cert_chain_exit(timer_ctx);
|
||||
|
||||
LOG_DEBUG("verify_cert_chain exit");
|
||||
return is_verified;
|
||||
}
|
||||
|
|
@ -259,7 +359,6 @@ open_signer_cert(cryptopro_context_t *ctx)
|
|||
goto error;
|
||||
}
|
||||
|
||||
|
||||
LOG_TRACE("open_signer_cert exit");
|
||||
|
||||
return 0;
|
||||
|
|
@ -310,7 +409,7 @@ log_sign_hash_data_last_error()
|
|||
}
|
||||
|
||||
static int
|
||||
sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign)
|
||||
sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign, /*out*/ const char **error_code)
|
||||
{
|
||||
int rc = -1;
|
||||
BOOL bReleaseContext = FALSE;
|
||||
|
|
@ -319,14 +418,10 @@ sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t
|
|||
BYTE *pbSignedMessageBlob;
|
||||
DWORD cbSignedMessageBlob;
|
||||
|
||||
timer_on_verify_cert_chain_enter(ctx->timer_ctx);
|
||||
|
||||
if (!verify_cert_chain(ctx->signer_cert, ctx->timer_ctx)) {
|
||||
if (!verify_cert_chain(ctx->signer_cert, ctx->timer_ctx, error_code)) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
timer_on_verify_cert_chain_exit(ctx->timer_ctx);
|
||||
|
||||
timer_on_acquire_private_key_enter(ctx->timer_ctx);
|
||||
|
||||
if (!cp_function_list.CryptAcquireCertificatePrivateKey(
|
||||
|
|
@ -360,6 +455,10 @@ sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t
|
|||
}
|
||||
|
||||
pbSignedMessageBlob = malloc(cbSignedMessageBlob * sizeof(BYTE));
|
||||
if (pbSignedMessageBlob == NULL) {
|
||||
LOG_ERROR("Could not allocate memory for pbSignedMessageBlob (%zd bytes)", cbSignedMessageBlob * sizeof(BYTE));
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!cp_function_list.CryptSignHash(hash, AT_KEYEXCHANGE, NULL, 0, pbSignedMessageBlob, &cbSignedMessageBlob)) {
|
||||
LOG_ERROR("CryptSignHash() failed");
|
||||
|
|
@ -669,12 +768,14 @@ get_verify_error(char** verify_error)
|
|||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("verify error: %s", *verify_error);
|
||||
|
||||
LOG_TRACE("get_verify_error exit");
|
||||
}
|
||||
|
||||
int
|
||||
cryptopro_verify(cryptopro_context_t *ctx, const str_t* alg, const str_t* data,
|
||||
const str_t* sign, bool* is_verified, char** verify_error)
|
||||
cryptopro_verify(cryptopro_context_t* ctx, const str_t* alg, const str_t* data,
|
||||
const str_t* sign, bool* is_verified, char** verify_error, const char** error_code)
|
||||
{
|
||||
int rc = -1;
|
||||
HCERTSTORE hStoreHandle = NULL;
|
||||
|
|
@ -709,14 +810,10 @@ cryptopro_verify(cryptopro_context_t *ctx, const str_t* alg, const str_t* data,
|
|||
goto exit;
|
||||
}
|
||||
|
||||
timer_on_verify_cert_chain_enter(ctx->timer_ctx);
|
||||
|
||||
if (!verify_cert_chain(certificate, ctx->timer_ctx)) {
|
||||
if (!verify_cert_chain(certificate, ctx->timer_ctx, error_code)) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
timer_on_verify_cert_chain_exit(ctx->timer_ctx);
|
||||
|
||||
LOG_DEBUG("provider: '%s', prov_type: %u", ctx->provider, ctx->prov_type);
|
||||
|
||||
if (!cp_function_list.CryptAcquireContext(&hCryptProv, NULL, ctx->provider, ctx->prov_type,
|
||||
|
|
@ -808,6 +905,228 @@ exit:
|
|||
return rc;
|
||||
}
|
||||
|
||||
static bool
|
||||
get_subject_name(PCCERT_CONTEXT cert, /*out*/ char **subject_name)
|
||||
{
|
||||
LOG_TRACE("get_subject_name enter");
|
||||
|
||||
DWORD dwFlags = CERT_X500_NAME_STR | CERT_NAME_STR_NO_QUOTING_FLAG;
|
||||
|
||||
DWORD len = cp_function_list.CertNameToStr(
|
||||
cert->dwCertEncodingType,
|
||||
&cert->pCertInfo->Subject,
|
||||
dwFlags,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
|
||||
*subject_name = malloc(len);
|
||||
if (*subject_name == NULL) {
|
||||
LOG_ERROR("Could not allocate memory for Subject Name str (%d bytes)", len);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cp_function_list.CertNameToStr(
|
||||
cert->dwCertEncodingType,
|
||||
&cert->pCertInfo->Subject,
|
||||
dwFlags,
|
||||
*subject_name,
|
||||
len
|
||||
);
|
||||
|
||||
LOG_DEBUG("Subject Name: %s", *subject_name);
|
||||
|
||||
LOG_TRACE("get_subject_name exit");
|
||||
return true;
|
||||
|
||||
error:
|
||||
free(*subject_name);
|
||||
LOG_ERROR("get_subject_name exit with error");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
get_issuer_name(PCCERT_CONTEXT cert, /*out*/ char **issuer_name)
|
||||
{
|
||||
LOG_TRACE("get_issuer_name enter");
|
||||
|
||||
DWORD dwFlags = CERT_X500_NAME_STR | CERT_NAME_STR_NO_QUOTING_FLAG;
|
||||
|
||||
DWORD len = cp_function_list.CertNameToStr(
|
||||
cert->dwCertEncodingType,
|
||||
&cert->pCertInfo->Issuer,
|
||||
dwFlags,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
|
||||
*issuer_name = malloc(len);
|
||||
if (*issuer_name == NULL) {
|
||||
LOG_ERROR("Could not allocate memory for Issuer Name str (%d bytes)", len);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cp_function_list.CertNameToStr(
|
||||
cert->dwCertEncodingType,
|
||||
&cert->pCertInfo->Issuer,
|
||||
dwFlags,
|
||||
*issuer_name,
|
||||
len
|
||||
);
|
||||
|
||||
LOG_DEBUG("Issuer Name: %s", *issuer_name);
|
||||
|
||||
LOG_TRACE("get_issuer_name exit");
|
||||
return true;
|
||||
|
||||
error:
|
||||
free(*issuer_name);
|
||||
LOG_ERROR("get_issuer_name exit with error");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
get_signer_cert_info(PCCERT_CONTEXT signer_cert, cert_info_t *signer_cert_info)
|
||||
{
|
||||
LOG_TRACE("get_signer_cert_info enter");
|
||||
|
||||
if (!get_subject_name(signer_cert, &signer_cert_info->subject)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!get_issuer_name(signer_cert, &signer_cert_info->issuer)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
LOG_TRACE("get_signer_cert_info exit");
|
||||
return true;
|
||||
|
||||
error:
|
||||
LOG_ERROR("get_signer_cert_info exit with error");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Проверка количества подписантов сообщения
|
||||
* Предполагается, что количество подписантов должно быть равно 1
|
||||
*/
|
||||
static bool
|
||||
check_message_signer_count(const str_t *sign)
|
||||
{
|
||||
int signer_count = cp_function_list.CryptGetMessageSignerCount(
|
||||
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
|
||||
(const BYTE *) sign->data,
|
||||
(DWORD) sign->len);
|
||||
|
||||
LOG_DEBUG("signer count = %d", signer_count);
|
||||
|
||||
if (signer_count == 1) {
|
||||
LOG_DEBUG("signer count is ok");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (signer_count < 0) {
|
||||
LOG_ERROR("Could not get signer count. Last error code: 0x%08x",
|
||||
cp_function_list.GetLastError());
|
||||
} else {
|
||||
LOG_ERROR("Invalid signer count: %d", signer_count);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Проверка подписи и извлечение сертификата подписанта
|
||||
* Предполагается, что количество подписантов проверено и равно 1
|
||||
*/
|
||||
static bool
|
||||
verify_detached_message_signature(const str_t *data, const str_t *sign,
|
||||
timer_context_t *timer_ctx,
|
||||
/*out*/ PCCERT_CONTEXT *signer_cert)
|
||||
{
|
||||
LOG_TRACE("verify_detached_message_signature enter");
|
||||
|
||||
timer_on_verify_detached_message_signature_enter(timer_ctx);
|
||||
|
||||
CRYPT_VERIFY_MESSAGE_PARA verifyParams = {
|
||||
.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA),
|
||||
.dwMsgAndCertEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING
|
||||
};
|
||||
|
||||
BOOL r = cp_function_list.CryptVerifyDetachedMessageSignature(
|
||||
&verifyParams,
|
||||
0, // индекс проверяемой подписи
|
||||
(CONST BYTE *) sign->data,
|
||||
sign->len,
|
||||
1, // количество подписанных сообщений
|
||||
(const BYTE **) &(data->data),
|
||||
(DWORD*) &(data->len),
|
||||
signer_cert
|
||||
);
|
||||
|
||||
timer_on_verify_detached_message_signature_exit(timer_ctx);
|
||||
|
||||
LOG_TRACE("verify_detached_message_signature exit");
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
cryptopro_verify_detached(cryptopro_context_t *ctx, const str_t *data, const str_t *sign,
|
||||
bool *is_verified, /*out*/ cert_info_t *signer_cert_info,
|
||||
/*out*/ char **verify_error, /*out*/ const char **error_code)
|
||||
{
|
||||
LOG_TRACE("cryptopro_verify_detached enter");
|
||||
|
||||
timer_on_cryptopro_verify_detached_enter(ctx->timer_ctx);
|
||||
|
||||
*is_verified = false;
|
||||
PCCERT_CONTEXT signer_cert = NULL;
|
||||
|
||||
if (!check_message_signer_count(sign)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (verify_detached_message_signature(data, sign, ctx->timer_ctx, &signer_cert)) {
|
||||
LOG_DEBUG("sign is valid");
|
||||
|
||||
if (!check_cert_key_usage(signer_cert)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!verify_cert_chain(signer_cert, ctx->timer_ctx, error_code)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!get_signer_cert_info(signer_cert, signer_cert_info)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
*is_verified = true;
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("sign is invalid");
|
||||
get_verify_error(verify_error);
|
||||
}
|
||||
|
||||
if (signer_cert != NULL) {
|
||||
cp_function_list.CertFreeCertificateContext(signer_cert);
|
||||
}
|
||||
|
||||
timer_on_cryptopro_verify_detached_exit(ctx->timer_ctx);
|
||||
|
||||
LOG_TRACE("cryptopro_verify_detached exit");
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (signer_cert != NULL) {
|
||||
cp_function_list.CertFreeCertificateContext(signer_cert);
|
||||
}
|
||||
|
||||
LOG_ERROR("cryptopro_verify_detached exit with error. Last error code: 0x%08x",
|
||||
cp_function_list.GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
cryptopro_gen_random(const cryptopro_context_t *ctx, unsigned char* data, size_t len)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ typedef struct cryptopro_context_s {
|
|||
|
||||
} cryptopro_context_t;
|
||||
|
||||
typedef struct cert_info_s {
|
||||
char *subject;
|
||||
char *issuer;
|
||||
|
||||
} cert_info_t;
|
||||
|
||||
static inline void
|
||||
cryptopro_context_set(cryptopro_context_t *ctx, const str_t *cert_thumbprint,
|
||||
|
|
@ -36,15 +41,32 @@ cryptopro_context_set(cryptopro_context_t *ctx, const str_t *cert_thumbprint,
|
|||
ctx->timer_ctx = timer_ctx;
|
||||
}
|
||||
|
||||
static inline void
|
||||
cert_info_clear(cert_info_t *info)
|
||||
{
|
||||
assert(info != NULL);
|
||||
|
||||
free(info->subject);
|
||||
free(info->issuer);
|
||||
|
||||
memset(info, 0, sizeof(cert_info_t));
|
||||
}
|
||||
|
||||
bool cryptopro_init(const char* cp_file);
|
||||
|
||||
int open_signer_cert(cryptopro_context_t *ctx);
|
||||
void close_signer_cert(cryptopro_context_t *ctx);
|
||||
|
||||
int cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign);
|
||||
int cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign,
|
||||
/*out*/ const char **error_code);
|
||||
|
||||
int cryptopro_verify(cryptopro_context_t *сtx, const str_t* alg, const str_t *data,
|
||||
const str_t *sign, bool* is_verified, char** verify_error);
|
||||
int cryptopro_verify(cryptopro_context_t* ctx, const str_t* alg, const str_t* data,
|
||||
const str_t* sign, bool* is_verified, char** verify_error,
|
||||
const char** error_code);
|
||||
|
||||
int cryptopro_verify_detached(cryptopro_context_t *ctx, const str_t *data, const str_t *sign,
|
||||
bool *is_verified, /*out*/ cert_info_t *signer_cert_info,
|
||||
/*out*/ char **verify_error, /*out*/ const char **error_code);
|
||||
|
||||
int cryptopro_gen_random(const cryptopro_context_t *ctx, unsigned char* data, size_t len);
|
||||
|
||||
|
|
|
|||
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 if (data->cur_header != NULL) {
|
||||
LOG_ERROR("Unknown header value: '%.*s'", (int) length, at);
|
||||
goto error;
|
||||
}
|
||||
|
||||
data->cur_header = NULL;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
LOG_ERROR("on_header_value exit with error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
on_body(multipart_parser* parser, const char *at, size_t length)
|
||||
{
|
||||
LOG_TRACE("on_body. length: %zd", length);
|
||||
|
||||
multipart_data_t *data = multipart_parser_get_data(parser);
|
||||
|
||||
if (data->cur_field != NULL) {
|
||||
data->cur_field->data = (char*) at;
|
||||
data->cur_field->len = length;
|
||||
} else {
|
||||
LOG_WARN("Unknown current field");
|
||||
return -1;
|
||||
}
|
||||
|
||||
data->cur_field = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
on_boundary_begin(multipart_parser* parser)
|
||||
{
|
||||
LOG_TRACE("on_boundary_begin");
|
||||
|
||||
multipart_data_t *data = multipart_parser_get_data(parser);
|
||||
|
||||
if (data->cur_field != NULL) {
|
||||
LOG_ERROR("on_boundary_begin. Current field is not set");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (data->cur_header != NULL) {
|
||||
LOG_ERROR("on_boundary_begin. Current header is not set");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
LOG_ERROR("on_boundary_begin exit with error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
on_headers_complete(multipart_parser* parser)
|
||||
{
|
||||
LOG_TRACE("on_headers_complete");
|
||||
|
||||
multipart_data_t *data = multipart_parser_get_data(parser);
|
||||
|
||||
if (data->cur_header != NULL) {
|
||||
LOG_ERROR("on_headers_complete. Current header is not set");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
on_body_parts_complete(multipart_parser* parser)
|
||||
{
|
||||
LOG_TRACE("on_body_parts_complete");
|
||||
|
||||
multipart_data_t *data = multipart_parser_get_data(parser);
|
||||
|
||||
if (data->cur_field != NULL) {
|
||||
LOG_ERROR("on_body_parts_complete. Current field is not set");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_multipart(const char *content_type_header, const char* content, size_t content_len,
|
||||
str_t *signed_data, str_t *sign)
|
||||
{
|
||||
LOG_TRACE("parse_multipart enter");
|
||||
|
||||
const char *boundary = NULL;
|
||||
size_t boundary_len = 0;
|
||||
multipart_data_t data = {0};
|
||||
multipart_parser parser = {0};
|
||||
|
||||
multipart_parser_settings settings = {
|
||||
.on_boundary_begin = on_boundary_begin,
|
||||
.on_header_field = on_header_field,
|
||||
.on_header_value = on_header_value,
|
||||
.on_headers_complete = on_headers_complete,
|
||||
.on_body = on_body,
|
||||
.on_body_parts_complete = on_body_parts_complete,
|
||||
};
|
||||
|
||||
boundary = multipart_get_boundary(content_type_header, &boundary_len);
|
||||
if (boundary == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
multipart_parser_init(&parser, boundary, boundary_len, &data);
|
||||
|
||||
if (multipart_parser_execute(&parser, &settings, content, content_len) != 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
*signed_data = data.signed_data;
|
||||
*sign = data.sign;
|
||||
|
||||
LOG_TRACE("parse_multipart exit");
|
||||
return 0;
|
||||
|
||||
error:
|
||||
LOG_ERROR("parse_multipart exit with error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
parse_detached_sign_payload(const char *content_type_header, const char* content,
|
||||
size_t content_len, str_t *data, str_t *sign)
|
||||
{
|
||||
LOG_TRACE("parse_detached_sign_payload enter");
|
||||
|
||||
if (parse_multipart(content_type_header, content, content_len, data, sign)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (str_t_is_null(*sign)) {
|
||||
LOG_ERROR("Could not get sign from request");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (str_t_is_null(*data)) {
|
||||
LOG_ERROR("Could not get signed data from request");
|
||||
goto error;
|
||||
}
|
||||
|
||||
LOG_TRACE("parse_detached_sign_payload exit");
|
||||
return 0;
|
||||
|
||||
error:
|
||||
str_t_nullify(data);
|
||||
str_t_nullify(sign);
|
||||
LOG_ERROR("parse_detached_sign_payload exit with error");
|
||||
return -1;
|
||||
}
|
||||
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);
|
||||
return 0;
|
||||
case 1:
|
||||
LOG_ERROR("Could not retrieve '%.*s' from json object. "
|
||||
LOG_ERROR("Could not retrieve '%s' from json object. "
|
||||
"Desc: field does not exist or it has not proper type. "
|
||||
"Expected: string member",
|
||||
(int) field_value->len, field_value->data);
|
||||
field_name);
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("json_get_object_member_as_string failed, member: '%.*s'",
|
||||
(int) field_value->len, field_value->data);
|
||||
LOG_ERROR("json_get_object_member_as_string failed, member: '%s'", field_name);
|
||||
break;
|
||||
|
||||
}
|
||||
|
|
|
|||
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
|
||||
147
src/utils/response_builder.c
Normal file
147
src/utils/response_builder.c
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#include "response_builder.h"
|
||||
|
||||
#include "utils/json_writer.h"
|
||||
#include "utils/logger.h"
|
||||
|
||||
static const char* DEFAULT_ERROR_CODE = "INTERNAL_ERROR";
|
||||
|
||||
char *
|
||||
build_response_from_error_code(const char *error_code)
|
||||
{
|
||||
JsonBuilder *jbuilder;
|
||||
char *response;
|
||||
|
||||
LOG_TRACE("build_response_from_error_code enter");
|
||||
|
||||
if (error_code == NULL) {
|
||||
error_code = DEFAULT_ERROR_CODE;
|
||||
}
|
||||
|
||||
jbuilder = json_builder_new();
|
||||
if (jbuilder == NULL) {
|
||||
LOG_ERROR("build_response_from_error_code. json_builder_new failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_builder_begin_object(jbuilder) == NULL) {
|
||||
LOG_ERROR("build_response_from_error_code. json_builder_begin_object failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_write_member_string(jbuilder, "error_code", error_code)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_builder_end_object(jbuilder) == NULL) {
|
||||
LOG_ERROR("build_response_from_error_code. json_builder_end_object failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
response = json_write_to_str(jbuilder);
|
||||
|
||||
g_object_unref(jbuilder);
|
||||
|
||||
LOG_TRACE("build_response_from_error_code exit");
|
||||
return response;
|
||||
|
||||
error:
|
||||
if (jbuilder != NULL) {
|
||||
g_object_unref(jbuilder);
|
||||
}
|
||||
LOG_ERROR("build_response_from_error_code exit with error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *
|
||||
build_response_from_signature(const char *signature, const char *state)
|
||||
{
|
||||
JsonBuilder *jbuilder;
|
||||
char *response;
|
||||
|
||||
LOG_TRACE("build_response_from_signature enter");
|
||||
|
||||
jbuilder = json_builder_new();
|
||||
if (jbuilder == NULL) {
|
||||
LOG_ERROR("json_builder_new failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_builder_begin_object(jbuilder) == NULL) {
|
||||
LOG_ERROR("json_builder_begin_object failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_write_member_string(jbuilder, "signature", signature)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_write_member_string(jbuilder, "state", state)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_builder_end_object(jbuilder) == NULL) {
|
||||
LOG_ERROR("json_builder_end_object failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
response = json_write_to_str(jbuilder);
|
||||
|
||||
g_object_unref(jbuilder);
|
||||
|
||||
LOG_TRACE("build_response_from_signature exit");
|
||||
return response;
|
||||
|
||||
error:
|
||||
if (jbuilder != NULL) {
|
||||
g_object_unref(jbuilder);
|
||||
}
|
||||
LOG_ERROR("build_response_from_signature exit with error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *
|
||||
build_response_from_signer_cert(const cert_info_t *cert_info)
|
||||
{
|
||||
JsonBuilder *jbuilder;
|
||||
char *response;
|
||||
|
||||
LOG_TRACE("build_response_from_signer_cert enter");
|
||||
|
||||
jbuilder = json_builder_new();
|
||||
if (jbuilder == NULL) {
|
||||
LOG_ERROR("json_builder_new failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_builder_begin_object(jbuilder) == NULL) {
|
||||
LOG_ERROR("json_builder_begin_object failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_write_member_string(jbuilder, "signer_subject", cert_info->subject)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_write_member_string(jbuilder, "issuer_name", cert_info->issuer)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (json_builder_end_object(jbuilder) == NULL) {
|
||||
LOG_ERROR("json_builder_end_object failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
response = json_write_to_str(jbuilder);
|
||||
|
||||
g_object_unref(jbuilder);
|
||||
|
||||
LOG_TRACE("build_response_from_signer_cert exit");
|
||||
return response;
|
||||
|
||||
error:
|
||||
if (jbuilder != NULL) {
|
||||
g_object_unref(jbuilder);
|
||||
}
|
||||
LOG_ERROR("build_response_from_signer_cert exit with error");
|
||||
return NULL;
|
||||
}
|
||||
19
src/utils/response_builder.h
Normal file
19
src/utils/response_builder.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef RESPONSE_BUILDER_H_INCLUDED
|
||||
#define RESPONSE_BUILDER_H_INCLUDED
|
||||
|
||||
#include "utils/cryptopro.h"
|
||||
|
||||
/* common functions */
|
||||
|
||||
char * build_response_from_error_code(const char *error_code);
|
||||
|
||||
/* functions for sign service */
|
||||
|
||||
char * build_response_from_signature(const char *signature, const char *state);
|
||||
|
||||
/* functions for verify detached message signature service */
|
||||
|
||||
char * build_response_from_signer_cert(const cert_info_t *cert_info);
|
||||
|
||||
|
||||
#endif // RESPONSE_BUILDER_H_INCLUDED
|
||||
|
|
@ -11,10 +11,10 @@ init_timers(timer_context_t *ctx)
|
|||
{
|
||||
const char* slt = getenv(ENV_SIGN_LOG_TIME);
|
||||
if (slt != NULL) {
|
||||
LOG_INFO("environment variable '" ENV_SIGN_LOG_TIME "'='%s' -> timings logging: ON", slt);
|
||||
LOG_DEBUG("environment variable '" ENV_SIGN_LOG_TIME "'='%s' -> timings logging: ON", slt);
|
||||
ctx->is_timer_on = true;
|
||||
} else {
|
||||
LOG_INFO("environment variable '" ENV_SIGN_LOG_TIME "' does not exist "
|
||||
LOG_DEBUG("environment variable '" ENV_SIGN_LOG_TIME "' does not exist "
|
||||
"-> timings logging: OFF");
|
||||
ctx->is_timer_on = false;
|
||||
}
|
||||
|
|
@ -119,6 +119,27 @@ void timer_on_cryptopro_verify_exit(timer_context_t *ctx)
|
|||
set_timer(ctx->is_timer_on, &ctx->cryptopro_verify_end, "cryptopro_verify_end");
|
||||
}
|
||||
|
||||
void timer_on_cryptopro_verify_detached_enter(timer_context_t *ctx)
|
||||
{
|
||||
set_timer(ctx->is_timer_on, &ctx->cryptopro_verify_detached_start, "cryptopro_verify_detached_start");
|
||||
}
|
||||
|
||||
void timer_on_cryptopro_verify_detached_exit(timer_context_t *ctx)
|
||||
{
|
||||
set_timer(ctx->is_timer_on, &ctx->cryptopro_verify_detached_end, "cryptopro_verify_detached_end");
|
||||
}
|
||||
|
||||
void timer_on_verify_detached_message_signature_enter(timer_context_t *ctx)
|
||||
{
|
||||
set_timer(ctx->is_timer_on, &ctx->verify_detached_msg_sign_start, "verify_detached_msg_sign_start");
|
||||
}
|
||||
|
||||
void timer_on_verify_detached_message_signature_exit(timer_context_t *ctx)
|
||||
{
|
||||
set_timer(ctx->is_timer_on, &ctx->verify_detached_msg_sign_end, "verify_detached_msg_sign_end");
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
timer_log_sign(const timer_context_t *ctx)
|
||||
{
|
||||
|
|
@ -160,3 +181,23 @@ timer_log_verify(const timer_context_t *ctx)
|
|||
diff_verify_handler,
|
||||
diff_verify_cert_chain);
|
||||
}
|
||||
|
||||
void
|
||||
timer_log_verify_detached(const timer_context_t *ctx)
|
||||
{
|
||||
if (!ctx->is_timer_on) {
|
||||
return;
|
||||
}
|
||||
|
||||
time_t diff_verify_handler = get_diff(&ctx->cryptopro_verify_detached_start,
|
||||
&ctx->cryptopro_verify_detached_end);
|
||||
time_t diff_verify_cert_chain = get_diff(&ctx->verify_cert_chain_start,
|
||||
&ctx->verify_cert_chain_end);
|
||||
time_t diff_verify_detached_msg_sign = get_diff(&ctx->verify_detached_msg_sign_start,
|
||||
&ctx->verify_detached_msg_sign_end);
|
||||
|
||||
LOG_INFO("verify_detached_handler: %ld verify_cert_chain: %ld verify_detached_msg_sign: %ld",
|
||||
diff_verify_handler,
|
||||
diff_verify_cert_chain,
|
||||
diff_verify_detached_msg_sign);
|
||||
}
|
||||
|
|
@ -29,6 +29,12 @@ typedef struct timer_context_s
|
|||
struct timeval cryptopro_verify_start;
|
||||
struct timeval cryptopro_verify_end;
|
||||
|
||||
struct timeval cryptopro_verify_detached_start;
|
||||
struct timeval cryptopro_verify_detached_end;
|
||||
|
||||
struct timeval verify_detached_msg_sign_start;
|
||||
struct timeval verify_detached_msg_sign_end;
|
||||
|
||||
} timer_context_t;
|
||||
|
||||
void init_timers(timer_context_t *ctx);
|
||||
|
|
@ -54,7 +60,14 @@ void timer_on_get_cert_chain_exit(timer_context_t *ctx);
|
|||
void timer_on_cryptopro_verify_enter(timer_context_t *ctx);
|
||||
void timer_on_cryptopro_verify_exit(timer_context_t *ctx);
|
||||
|
||||
void timer_on_cryptopro_verify_detached_enter(timer_context_t *ctx);
|
||||
void timer_on_cryptopro_verify_detached_exit(timer_context_t *ctx);
|
||||
|
||||
void timer_on_verify_detached_message_signature_enter(timer_context_t *ctx);
|
||||
void timer_on_verify_detached_message_signature_exit(timer_context_t *ctx);
|
||||
|
||||
void timer_log_sign(const timer_context_t *ctx);
|
||||
void timer_log_verify(const timer_context_t *ctx);
|
||||
void timer_log_verify_detached(const timer_context_t *ctx);
|
||||
|
||||
#endif // TIMER_H_INCLUDED
|
||||
|
|
@ -7,19 +7,25 @@ include_directories (
|
|||
)
|
||||
|
||||
set (ESM_SOURCES
|
||||
../src/utils/detached_sign_payload_parser.c
|
||||
../src/utils/glib_utils.c
|
||||
../src/utils/json_parser.c
|
||||
../src/utils/jwt.c
|
||||
../src/utils/logger.c
|
||||
../src/utils/multipart_parser.c
|
||||
)
|
||||
|
||||
set (HEADERS
|
||||
utils/test_detached_sign_payload_parser.h
|
||||
utils/test_jwt.h
|
||||
utils/test_multipart_parser.h
|
||||
)
|
||||
|
||||
set (SOURCES
|
||||
main.c
|
||||
utils/test_detached_sign_payload_parser.c
|
||||
utils/test_jwt.c
|
||||
utils/test_multipart_parser.c
|
||||
"${ESM_SOURCES}"
|
||||
)
|
||||
|
||||
|
|
|
|||
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_multipart_parser.h"
|
||||
|
||||
#include <CUnit/Basic.h>
|
||||
|
||||
|
|
@ -11,6 +13,8 @@ int main(int arv, char** argc)
|
|||
if (CUE_SUCCESS != CU_initialize_registry())
|
||||
return CU_get_error();
|
||||
|
||||
// jwt
|
||||
|
||||
CU_pSuite suite_jwt = CU_add_suite("JWTSuit", NULL, NULL);
|
||||
if (NULL == suite_jwt) {
|
||||
goto exit;
|
||||
|
|
@ -19,6 +23,30 @@ int main(int arv, char** argc)
|
|||
if (NULL == CU_ADD_TEST(suite_jwt, test_jwt_get_header_payload_and_sign)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_jwt_get_alg_from_header)) goto exit;
|
||||
|
||||
// multipart/form-data parser
|
||||
|
||||
CU_pSuite suite_multipart = CU_add_suite("MultipartSuit", NULL, NULL);
|
||||
if (NULL == suite_multipart) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_boundary)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_boundary_failure_invalid_header)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_boundary_failure_empty_boundary)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_name)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_name_2)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_name_failure)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_filename)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_filename_2)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_multipart_get_filename_failure)) goto exit;
|
||||
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload_failure_sign_is_missing)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload_failure_data_is_missing)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload_failure_extra_field)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload_failure_unacceptable_content_type)) goto exit;
|
||||
if (NULL == CU_ADD_TEST(suite_jwt, test_parse_detached_sign_payload_failure_invalid_header)) goto exit;
|
||||
|
||||
CU_basic_set_mode(CU_BRM_NORMAL);
|
||||
CU_basic_run_tests();
|
||||
|
||||
|
|
|
|||
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