ervu-sign-module/src/modules/service_sign.c

415 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, const main_conf_t *main_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,
main_conf->cp_name,
main_conf->cp_type,
&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(&hsign->cryptopro_ctx);
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;
}