# Conflicts: # CMakeLists.txt # Dockerfile.micord # entrypoint.sh # src/modules/service_sign.c # src/utils/cryptopro.c # src/utils/cryptopro.h
413 lines
No EOL
10 KiB
C
413 lines
No EOL
10 KiB
C
#include "service_sign.h"
|
|
|
|
#include "fcgisrv/fcgi_utils.h"
|
|
|
|
#include "utils/cryptopro.h"
|
|
#include "utils/json_writer.h"
|
|
#include "utils/timer.h"
|
|
#include "utils/uuid.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";
|
|
|
|
typedef struct sign_service_s {
|
|
const sign_conf_t *conf;
|
|
|
|
cryptopro_context_t cryptopro_ctx;
|
|
|
|
timer_context_t timer_ctx;
|
|
|
|
} sign_service_t;
|
|
|
|
typedef struct fcgi_sign_request_s {
|
|
char *content;
|
|
int content_length;
|
|
|
|
char *response;
|
|
|
|
} 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 int sign_content_with_state(const sign_service_t *hsign, fcgi_sign_request_t *req_info);
|
|
|
|
int
|
|
sign_conf_load(sign_conf_t *conf, const conf_file_context_t conf_file)
|
|
{
|
|
LOG_TRACE("sign_conf_load enter");
|
|
|
|
memset(conf, 0, sizeof(sign_conf_t));
|
|
|
|
conf_file_field_t fields[] = {
|
|
{
|
|
SIGN_CONF_SECTION,
|
|
SIGN_CONF_KEY_LOCATION,
|
|
&conf->location,
|
|
CONF_FILE_VALUE_STRT,
|
|
CONF_FILE_VALUE_LOCATION,
|
|
&SIGN_CONF_DEFAULT_LOCATION
|
|
},
|
|
{
|
|
SIGN_CONF_SECTION,
|
|
SIGN_CONF_KEY_SIGN_CERT_THUMBPRINT,
|
|
&(conf->sign_cert_thumbprint),
|
|
CONF_FILE_VALUE_STRT,
|
|
CONF_FILE_VALUE_NONE,
|
|
NULL
|
|
},
|
|
{
|
|
SIGN_CONF_SECTION,
|
|
SIGN_CONF_KEY_SIGN_CERT_PASSWORD,
|
|
&(conf->sign_cert_password),
|
|
CONF_FILE_VALUE_STRT,
|
|
CONF_FILE_VALUE_PSWD,
|
|
NULL
|
|
}
|
|
};
|
|
|
|
if (conf_file_load_values(conf_file, fields, sizeof(fields) / sizeof(conf_file_field_t))) {
|
|
goto error;
|
|
}
|
|
|
|
if (str_t_is_null(conf->sign_cert_thumbprint)) {
|
|
LOG_ERROR(SIGN_CONF_SECTION ":" SIGN_CONF_KEY_SIGN_CERT_THUMBPRINT " is required");
|
|
goto error;
|
|
}
|
|
|
|
if (str_t_is_null(conf->sign_cert_password)) {
|
|
LOG_ERROR(SIGN_CONF_SECTION ":" SIGN_CONF_KEY_SIGN_CERT_PASSWORD " is required");
|
|
goto error;
|
|
}
|
|
|
|
LOG_TRACE("sign_conf_load exit");
|
|
return 0;
|
|
|
|
error:
|
|
sign_conf_clear(conf);
|
|
LOG_ERROR("sign_conf_load exit with error");
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
sign_conf_clear(sign_conf_t *conf)
|
|
{
|
|
LOG_TRACE("sign_conf_clear");
|
|
|
|
if (conf == NULL) return;
|
|
|
|
if (conf->sign_cert_password.data != 0) {
|
|
explicit_bzero(conf->sign_cert_password.data, conf->sign_cert_password.len);
|
|
}
|
|
str_t_clear(&conf->sign_cert_password);
|
|
|
|
str_t_clear(&conf->location);
|
|
str_t_clear(&conf->sign_cert_thumbprint);
|
|
|
|
memset(conf, 0, sizeof(sign_conf_t));
|
|
}
|
|
|
|
HSign
|
|
sign_service_create(const sign_conf_t *conf)
|
|
{
|
|
LOG_TRACE("sign_service_create enter");
|
|
|
|
sign_service_t *hsign = (sign_service_t*) calloc(1, sizeof(sign_service_t));
|
|
if (hsign == NULL) {
|
|
LOG_ERROR("Could not allocate memory for HSign");
|
|
goto error;
|
|
}
|
|
|
|
hsign->conf = conf;
|
|
|
|
init_timers(&hsign->timer_ctx);
|
|
|
|
cryptopro_context_set(&hsign->cryptopro_ctx,
|
|
&conf->sign_cert_thumbprint,
|
|
&conf->sign_cert_password,
|
|
&hsign->timer_ctx);
|
|
|
|
if (open_signer_cert(&hsign->cryptopro_ctx)) {
|
|
goto error;
|
|
}
|
|
|
|
LOG_TRACE("sign_service_create exit");
|
|
return (HSign)hsign;
|
|
|
|
error:
|
|
sign_service_free((HSign)hsign);
|
|
LOG_ERROR("sign_service_create exit with error");
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
sign_service_free(HSign hsign)
|
|
{
|
|
sign_service_t *ctx = (sign_service_t *) hsign;
|
|
|
|
if (ctx == NULL) return;
|
|
|
|
close_signer_cert(&ctx->cryptopro_ctx);
|
|
|
|
free(ctx);
|
|
}
|
|
|
|
fcgi_handler_status_t
|
|
fcgi_sign_handler(FCGX_Request* request, void* ctx)
|
|
{
|
|
fcgi_handler_status_t status = HANDLER_ERROR;
|
|
sign_service_t *hsign = (sign_service_t *) ctx;
|
|
fcgi_sign_request_t req_info = {0};
|
|
|
|
LOG_TRACE("fcgi_sign_handler enter");
|
|
|
|
timer_on_fcgi_sign_handler_enter(&hsign->timer_ctx);
|
|
|
|
if (request == NULL) {
|
|
LOG_ERROR("fcgi_sign_handler exit with error. Desc: request is NULL");
|
|
goto exit;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
timer_on_sign_content_enter(&hsign->timer_ctx);
|
|
|
|
if (sign_content_with_state(hsign, &req_info)) {
|
|
status = HANDLER_ERROR;
|
|
goto exit;
|
|
}
|
|
|
|
timer_on_sign_content_exit(&hsign->timer_ctx);
|
|
|
|
// status = HANDLER_SUCCESS;
|
|
|
|
exit:
|
|
status = fcgi_request_finalize_handler(status)(request, &req_info);
|
|
|
|
fcgi_sign_request_clear(&req_info);
|
|
|
|
timer_on_fcgi_sign_handler_exit(&hsign->timer_ctx);
|
|
timer_log_sign(&hsign->timer_ctx);
|
|
|
|
LOG_TRACE("fcgi_sign_handler exit");
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
fcgi_sign_request_clear(fcgi_sign_request_t *req_info)
|
|
{
|
|
LOG_TRACE("fcgi_sign_request_clear");
|
|
|
|
free(req_info->content);
|
|
free(req_info->response);
|
|
|
|
memset(req_info, 0, sizeof(fcgi_sign_request_t));
|
|
}
|
|
|
|
static fcgi_handler_status_t
|
|
fcgi_ok_handler(const FCGX_Request* request, void *ctx)
|
|
{
|
|
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;
|
|
|
|
case HANDLER_HTTP_BAD_REQUEST:
|
|
handler = fcgi_400_bad_request_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 int
|
|
add_state_to_content(const fcgi_sign_request_t *req_info, const char *state,
|
|
/*out*/ str_t *content_state)
|
|
{
|
|
LOG_TRACE("add_state_to_content enter");
|
|
|
|
size_t content_state_size = req_info->content_length + strlen(state);
|
|
|
|
content_state->data = malloc(content_state_size);
|
|
if (content_state->data == NULL) {
|
|
LOG_ERROR("Could not allocate memory for content with state (%zd bytes)",
|
|
content_state_size);
|
|
goto error;
|
|
}
|
|
|
|
int len = snprintf(content_state->data, content_state_size, req_info->content, state);
|
|
if (len < 0 || (size_t)len >= content_state_size) {
|
|
LOG_ERROR("Could not concatenate content with state");
|
|
goto error;
|
|
}
|
|
|
|
content_state->len = len;
|
|
|
|
LOG_TRACE("add_state_to_content exit");
|
|
return 0;
|
|
|
|
error:
|
|
str_t_clear(content_state);
|
|
LOG_ERROR("add_state_to_content exit with 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)
|
|
{
|
|
str_t content_state = str_t_null;
|
|
char *state = NULL;
|
|
str_t signature = str_t_null;
|
|
|
|
LOG_TRACE("sign_content_with_state enter");
|
|
|
|
state = generate_uuid4();
|
|
if (state == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
if (add_state_to_content(req_info, state, &content_state)) {
|
|
goto error;
|
|
}
|
|
|
|
if (cryptopro_sign(&hsign->cryptopro_ctx, &content_state, &signature)) {
|
|
goto error;
|
|
}
|
|
|
|
assert(str_t_is_null_terminated(signature));
|
|
|
|
req_info->response = generate_response(signature.data, state);
|
|
|
|
if (req_info->response == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
LOG_DEBUG("state: '%s'", state);
|
|
LOG_DEBUG("content with state: '%.*s'", (int) content_state.len, content_state.data);
|
|
LOG_DEBUG("response: '%s'", req_info->response);
|
|
|
|
str_t_clear(&content_state);
|
|
free(state);
|
|
str_t_clear(&signature);
|
|
|
|
LOG_TRACE("sign_content_with_state exit");
|
|
return 0;
|
|
|
|
error:
|
|
str_t_clear(&content_state);
|
|
free(state);
|
|
str_t_clear(&signature);
|
|
LOG_ERROR("sign_content_with_state exit with error");
|
|
return -1;
|
|
} |