SUPPORT-8592. Реализован service_verify, который обрабатывает запросы для проверки подписи
This commit is contained in:
parent
503287af7f
commit
b157ea6a0c
6 changed files with 393 additions and 0 deletions
|
|
@ -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 =
|
||||
|
|
|
|||
338
src/modules/service_verify.c
Normal file
338
src/modules/service_verify.c
Normal 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;
|
||||
}
|
||||
25
src/modules/service_verify.h
Normal file
25
src/modules/service_verify.h
Normal 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
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue