SUPPORT-8592. Реализован service_verify, который обрабатывает запросы для проверки подписи

This commit is contained in:
alashkova 2024-10-16 11:44:16 +03:00
parent 503287af7f
commit b157ea6a0c
6 changed files with 393 additions and 0 deletions

View file

@ -11,3 +11,7 @@ fcgi_listen_host = 127.0.0.1
#location = /sign
signer_subject = signer@example.ru
pin = ****
[verify]
#location = /verify
esia_cert_thumbprint =

View file

@ -0,0 +1,338 @@
#include "service_verify.h"
#include "fcgisrv/fcgi_utils.h"
#include "utils/base64.h"
#include "utils/cryptopro.h"
#include "utils/json_parser.h"
#define VERIFY_CONF_SECTION "verify"
#define VERIFY_CONF_KEY_LOCATION "location"
#define VERIFY_CONF_KEY_THUMBPRINT "esia_cert_thumbprint"
static const str_t VERIFY_CONF_DEFAULT_LOCATION = str_t_const("/verify");
static const int CLIENT_MAX_BODY_SIZE = 4096;
static const char* ACCEPTABLE_CONTENT_TYPE = "text/plain";
static const char* JWT_ALG_FIELD_NAME = "alg";
typedef struct verify_service_s {
const verify_conf_t *conf;
} verify_service_t;
typedef struct fcgi_verify_request_s {
char *content;
int content_length;
} fcgi_verify_request_t;
static fcgi_request_handler_pt fcgi_request_finalize_handler(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(const fcgi_verify_request_t* req_info,
const verify_service_t *ctx);
int
verify_conf_load(verify_conf_t *conf, const conf_file_context_t conf_file)
{
LOG_TRACE("verify_conf_load enter");
memset(conf, 0, sizeof(verify_conf_t));
conf_file_field_t fields[] = {
{
VERIFY_CONF_SECTION,
VERIFY_CONF_KEY_LOCATION,
&conf->location,
CONF_FILE_VALUE_STRT,
CONF_FILE_VALUE_LOCATION,
&VERIFY_CONF_DEFAULT_LOCATION
},
{
VERIFY_CONF_SECTION,
VERIFY_CONF_KEY_THUMBPRINT,
&conf->esia_cert_thumbprint,
CONF_FILE_VALUE_STRT,
CONF_FILE_VALUE_NONE,
NULL
},
};
if (conf_file_load_values(conf_file, fields, sizeof(fields) / sizeof(conf_file_field_t))) {
goto error;
}
LOG_TRACE("verify_conf_load exit");
return 0;
error:
verify_conf_clear(conf);
LOG_ERROR("verify_conf_load exit with error");
return -1;
}
void
verify_conf_clear(verify_conf_t *conf)
{
LOG_TRACE("verify_conf_clear");
if (conf == NULL) return;
str_t_clear(&conf->location);
str_t_clear(&conf->esia_cert_thumbprint);
memset(conf, 0, sizeof(verify_conf_t));
}
HVerify
verify_service_create(const verify_conf_t *conf)
{
LOG_TRACE("verify_service_create enter");
verify_service_t *hverify = (verify_service_t*) calloc(1, sizeof(verify_service_t));
if (hverify == NULL) {
LOG_ERROR("Could not allocate memory for HVerify");
goto error;
}
hverify->conf = conf;
LOG_TRACE("verify_service_create exit");
return (HVerify)hverify;
error:
LOG_ERROR("verify_service_create exit with error");
return NULL;
}
void
verify_service_free(HVerify hverify)
{
LOG_TRACE("verify_service_free");
verify_service_t *ctx = (verify_service_t *) hverify;
if (ctx == NULL) return;
free(ctx);
}
fcgi_handler_status_t
fcgi_verify_handler(FCGX_Request* request, void* ctx)
{
fcgi_handler_status_t status = HANDLER_ERROR;
fcgi_verify_request_t req_info = {0};
LOG_TRACE("fcgi_verify_handler enter");
if (request == NULL) {
LOG_ERROR("fcgi_verify_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);
if (status != HANDLER_SUCCESS) {
goto exit;
}
status = fcgi_request_load_data(request, req_info.content_length, &req_info.content);
if (status != HANDLER_SUCCESS) {
goto exit;
}
status = verify_jwt_sign(&req_info, ctx);
exit:
status = fcgi_request_finalize_handler(status)(request, ctx);
fcgi_verify_request_clear(&req_info);
LOG_TRACE("fcgi_verify_handler exit");
return status;
}
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;
case HANDLER_HTTP_BAD_REQUEST:
handler = fcgi_400_bad_request_handler;
break;
case HANDLER_HTTP_UNAUTHORIZED:
handler = fcgi_401_unauthorized_handler;
break;
case HANDLER_HTTP_NOT_ACCEPTABLE:
handler = fcgi_406_not_acceptable_handler;
break;
case HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE:
handler = fcgi_413_request_entity_too_large_handler;
break;
case HANDLER_ERROR:
default:
handler = fcgi_500_internal_server_error_handler;
break;
}
return handler;
}
static void
fcgi_verify_request_clear(fcgi_verify_request_t *req_info)
{
LOG_TRACE("fcgi_verify_request_clear");
free(req_info->content);
}
static int
get_jwt_header_payload_and_sign(const str_t *jwt,
/*out*/ str_t *header,
/*out*/ str_t *header_payload,
/*out*/ str_t *sign)
{
LOG_TRACE("get_jwt_header_payload_and_sign enter");
size_t p_start = 0, p_end = 0;
for (size_t i = 0; i < jwt->len; i++) {
if (jwt->data[i] == '.') {
if (p_start == 0) {
p_start = i + 1;
} else {
p_end = i;
break;
}
}
}
if (p_start == 0 || p_end == 0) {
LOG_ERROR("Could not parse jwt. p_start = %zd, p_end = %zd", p_start, p_end);
return -1;
}
header->data = (char*)jwt->data;
header->len = p_start - 1;
header_payload->data = (char*)jwt->data;
header_payload->len = p_end;
sign->data = (char*)jwt->data + p_end + 1;
sign->len = jwt->len - p_end - 1;
LOG_DEBUG("header: %.*s", (int) header->len, header->data);
LOG_DEBUG("header_payload: %.*s", (int) header_payload->len, header_payload->data);
LOG_DEBUG("sign: %.*s", (int) sign->len, sign->data);
LOG_TRACE("get_jwt_header_payload_and_sign exit");
return 0;
}
static int
get_alg_from_header(const str_t *header, /*out*/ str_t *alg)
{
LOG_TRACE("get_alg_from_header");
Hjson_parser parser = json_parser_create(header->data, header->len);
if (parser == NULL) {
goto error;
}
if (json_get_string_member(parser, JWT_ALG_FIELD_NAME, alg)) {
goto error;
}
json_parser_free(parser);
return 0;
error:
json_parser_free(parser);
LOG_ERROR("get_alg_from_header exit with error");
return -1;
}
static fcgi_handler_status_t
verify_jwt_sign(const fcgi_verify_request_t* req_info, const verify_service_t *ctx)
{
LOG_TRACE("verify_jwt_sign enter");
bool is_verified = false;
str_t jwt = {
.data = req_info->content,
.len = req_info->content_length
};
str_t sign = str_t_null;
str_t sign_base64 = str_t_null;
str_t header = str_t_null;
str_t header_base64 = str_t_null;
str_t header_payload = str_t_null;
str_t alg = str_t_null;
if (get_jwt_header_payload_and_sign(&jwt, &header_base64, &header_payload, &sign_base64)) {
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;
}
decode_base64_url(&sign, &sign_base64);
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;
}
decode_base64_url(&header, &header_base64);
if (get_alg_from_header(&header, &alg)) {
goto error;
}
if (cryptopro_verify(&ctx->conf->esia_cert_thumbprint, &alg, &header_payload, &sign,
&is_verified)) {
goto error;
}
str_t_clear(&sign);
str_t_clear(&header);
str_t_clear(&alg);
LOG_TRACE("verify_jwt_sign exit");
return is_verified ? HANDLER_SUCCESS : HANDLER_HTTP_UNAUTHORIZED;
error:
str_t_clear(&sign);
str_t_clear(&header);
str_t_clear(&alg);
LOG_TRACE("verify_jwt_sign exit with error");
return HANDLER_ERROR;
}

View file

@ -0,0 +1,25 @@
#ifndef SERVICE_VERIFY_H_INCLUDED
#define SERVICE_VERIFY_H_INCLUDED
#include "fcgisrv/fcgi_server.h"
#include "utils/conf_file_context.h"
#include "utils/str_t.h"
typedef struct verify_service_t* HVerify;
typedef struct verify_conf_s {
str_t location;
str_t esia_cert_thumbprint;
} verify_conf_t;
int verify_conf_load(verify_conf_t *conf, const conf_file_context_t conf_file);
void verify_conf_clear(verify_conf_t *conf);
HVerify verify_service_create(const verify_conf_t *conf);
void verify_service_free(HVerify hverify);
fcgi_handler_status_t fcgi_verify_handler(FCGX_Request* request, void* ctx);
#endif // SERVICE_VERIFY_H_INCLUDED

View file

@ -160,6 +160,11 @@ service_manager_load_conf(const char* config_file_name, service_manager_conf_t*
LOG_ERROR("Sign service configuraton loading failed");
goto error;
}
if (verify_conf_load(&services_cf->verify_cf, conf_file)) {
LOG_ERROR("Verify service configuraton loading failed");
goto error;
}
conf_file_close_file(conf_file);
@ -181,6 +186,7 @@ service_manager_clear_conf(service_manager_conf_t* services_cf)
assert(services_cf != NULL);
sign_conf_clear(&services_cf->sign_cf);
verify_conf_clear(&services_cf->verify_cf);
fcgi_clear_conf(&services_cf->fcgi_cf);
main_conf_clear(&services_cf->main_cf);
}
@ -198,6 +204,8 @@ deinit_services(service_manager_t* services)
sign_service_free(services->hsign);
verify_service_free(services->hverify);
LOG_TRACE("deinit_services exit");
return 0;
@ -224,6 +232,7 @@ init_services(service_manager_t* services, const service_manager_conf_t* service
goto error;
}
/* sign service */
services->hsign = sign_service_create(&services_cf->sign_cf);
if (services->hsign == NULL) {
goto error;
@ -234,6 +243,18 @@ init_services(service_manager_t* services, const service_manager_conf_t* service
LOG_ERROR("Could not register 'sign service'");
goto error;
}
/* verify service */
services->hverify = verify_service_create(&services_cf->verify_cf);
if (services->hverify == NULL) {
goto error;
}
if (register_service(services, services_cf, fcgi_verify_handler, services->hverify,
&services_cf->verify_cf.location)) {
LOG_ERROR("Could not register 'verify service'");
goto error;
}
/* run fastcgi server */
if (fcgi_run_server(services->hfcgi) != 0) {

View file

@ -8,6 +8,7 @@
#include "fcgisrv/fcgi_server.h"
#include "modules/service_sign.h"
#include "modules/service_verify.h"
struct service_s;
@ -46,12 +47,14 @@ typedef struct service_manager_conf_s {
fcgi_conf_t fcgi_cf;
main_conf_t main_cf;
sign_conf_t sign_cf;
verify_conf_t verify_cf;
} service_manager_conf_t;
typedef struct service_manager_s {
HFcgi hfcgi;
HSign hsign;
HVerify hverify;
} service_manager_t;

View file

@ -299,6 +299,8 @@ get_cert_by_thumbprint(HCERTSTORE hStoreHandle, const str_t* thumbprint)
{
str_t thumbprint_bin = str_t_null;
LOG_TRACE("get_cert_by_thumbprint");
if (hex_to_bin(thumbprint, &thumbprint_bin)) {
goto error;
}