Merge branch 'release/1.1.0'
This commit is contained in:
commit
5b35e154fc
46 changed files with 1976 additions and 251 deletions
|
|
@ -1,9 +1,9 @@
|
|||
CMAKE_MINIMUM_REQUIRED (VERSION 3.0)
|
||||
|
||||
PROJECT (ervu-sign-module VERSION 1.0.0 LANGUAGES C)
|
||||
|
||||
SET (CMAKE_C_COMPILER "gcc")
|
||||
|
||||
PROJECT (ervu-sign-module VERSION 1.1.0 LANGUAGES C)
|
||||
|
||||
IF (CMAKE_VERBOSE)
|
||||
SET (CMAKE_VERBOSE_MAKEFILE 1)
|
||||
ENDIF ()
|
||||
|
|
@ -49,8 +49,30 @@ SET (DEP_LIBS
|
|||
-lfcgi
|
||||
-lglib-2.0
|
||||
-ldl
|
||||
-ljson-glib-1.0
|
||||
-lgobject-2.0
|
||||
)
|
||||
|
||||
# JSON-GLIB
|
||||
MESSAGE ("")
|
||||
MESSAGE ("Try to find the JSON-glib-1.0..")
|
||||
pkg_check_modules (JSONGLIB REQUIRED json-glib-1.0)
|
||||
|
||||
IF (NOT JSONGLIB_FOUND)
|
||||
MESSAGE(SEND_ERROR "Can not find json-glib-1.0")
|
||||
ELSE ()
|
||||
MESSAGE ("json-glib-1.0 is found: ")
|
||||
MESSAGE ("JSONGLIB_LIB_INCLUDE_DIR : " ${JSONGLIB_LIB_INCLUDE_DIR})
|
||||
MESSAGE ("JSONGLIB_INCLUDE_DIR : " ${JSONGLIB_INCLUDE_DIR})
|
||||
MESSAGE ("JSONGLIB_LIBRARY_DIR : " ${JSONGLIB_LIBRARY_DIR})
|
||||
MESSAGE ("JSONGLIB_INCLUDE_DIRS : " ${JSONGLIB_INCLUDE_DIRS})
|
||||
MESSAGE ("JSONGLIB_LIBRARY_DIRS : " ${JSONGLIB_LIBRARY_DIRS})
|
||||
MESSAGE ("JSONGLIB_LIBRARIES : " ${JSONGLIB_LIBRARIES})
|
||||
ENDIF (NOT JSONGLIB_FOUND)
|
||||
MESSAGE ("")
|
||||
|
||||
INCLUDE_DIRECTORIES ("${JSONGLIB_INCLUDE_DIRS}")
|
||||
|
||||
# version.h
|
||||
CONFIGURE_FILE (${SOURCE_DIR}/version.h.in ${SOURCE_DIR}/version.h)
|
||||
|
||||
|
|
@ -76,6 +98,14 @@ INCLUDE_DIRECTORIES ("${CRYPTOPRO_INCLUDE_DIRS}")
|
|||
|
||||
FILE (GLOB_RECURSE HEADERS "${SOURCE_DIR}/*.h")
|
||||
|
||||
# tests
|
||||
option(WITH_TESTS "Build with tests" OFF)
|
||||
IF (WITH_TESTS)
|
||||
MESSAGE ("Build with tests: ON")
|
||||
enable_testing()
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/tests/)
|
||||
ENDIF ()
|
||||
|
||||
ADD_EXECUTABLE (${PROJECT_NAME}
|
||||
${HEADERS}
|
||||
${SOURCE_DIR}/main.c
|
||||
|
|
@ -88,6 +118,9 @@ ADD_EXECUTABLE (${PROJECT_NAME}
|
|||
${UTILS_DIR}/conf_file_context.c
|
||||
${UTILS_DIR}/cryptopro.c
|
||||
${UTILS_DIR}/gconf_file.c
|
||||
${UTILS_DIR}/glib_utils.c
|
||||
${UTILS_DIR}/json_parser.c
|
||||
${UTILS_DIR}/jwt.c
|
||||
${UTILS_DIR}/library.c
|
||||
${UTILS_DIR}/logger.c
|
||||
${UTILS_DIR}/str_t.c
|
||||
|
|
@ -97,6 +130,8 @@ ADD_EXECUTABLE (${PROJECT_NAME}
|
|||
${FCGISRV_DIR}/fcgi_utils.c
|
||||
${FCGISRV_DIR}/fcgi_worker.c
|
||||
${MODULES_DIR}/service_sign.c
|
||||
${MODULES_DIR}/service_verify.c
|
||||
${MODULES_DIR}/service_version.c
|
||||
)
|
||||
|
||||
TARGET_LINK_LIBRARIES (${PROJECT_NAME} ${DEP_LIBS})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ARG BUILDER_IMAGE=repo.micord.ru/alt/alt-cprocsp:c10f1-5.0.13000-20240827
|
||||
ARG BUILDER_IMAGE=repo.micord.ru/alt/alt-cprocsp:c10f1-5.0.13000-20241129
|
||||
ARG RUNTIME_IMAGE=registry.altlinux.org/basealt/altsp:c10f1
|
||||
|
||||
FROM ${BUILDER_IMAGE} AS builder
|
||||
|
|
@ -13,7 +13,8 @@ RUN apt-get update \
|
|||
cmake \
|
||||
make \
|
||||
gcc \
|
||||
gcc10
|
||||
gcc10 \
|
||||
libjson-glib libjson-glib-devel
|
||||
|
||||
WORKDIR /build
|
||||
COPY src src
|
||||
|
|
@ -32,7 +33,7 @@ ENV TZ=Europe/Moscow
|
|||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install glib2 libfcgi \
|
||||
&& apt-get -y install glib2 libfcgi libjson-glib \
|
||||
&& apt-get clean \
|
||||
&& rm -f /var/cache/apt/*.bin \
|
||||
&& rm -f /var/lib/apt/lists/update* \
|
||||
|
|
@ -50,6 +51,18 @@ COPY --from=builder /build/.build/ervu-sign-module /opt/ervu-sign-module/ervu-si
|
|||
|
||||
EXPOSE 9009
|
||||
|
||||
ARG ESIA_CA_CERT=test_ca_rtk3.cer
|
||||
COPY conf/${ESIA_CA_CERT} ${ESIA_CA_CERT}
|
||||
RUN /opt/cprocsp/bin/amd64/certmgr -install -store mRoot -file "${ESIA_CA_CERT}"
|
||||
|
||||
USER ervu
|
||||
|
||||
ARG ESIA_CERT="TESIA GOST 2012 new.cer"
|
||||
ARG ESIA_CA_CRL=b0fd8eb959d9489d5b7b4c143a06cad7952a0744.crl
|
||||
COPY --chown=ervu:ervu conf/${ESIA_CERT} ${ESIA_CERT}
|
||||
COPY --chown=ervu:ervu conf/${ESIA_CA_CRL} ${ESIA_CA_CRL}
|
||||
|
||||
RUN /opt/cprocsp/bin/amd64/certmgr -install -file "${ESIA_CERT}" \
|
||||
&& /opt/cprocsp/bin/amd64/certmgr -install -store uCA -crl -file "${ESIA_CA_CRL}"
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
|
|
|||
50
README.md
50
README.md
|
|
@ -1,6 +1,10 @@
|
|||
## Краткое описание
|
||||
|
||||
Модуль предназначен для подписи данных.
|
||||
В модуле реализованы следующие функции:
|
||||
- подпись данных
|
||||
- проверка подписи маркера доступа
|
||||
|
||||
### Подпись данных
|
||||
|
||||
Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain).
|
||||
Подписывает строку, полученную в теле запроса.
|
||||
|
|
@ -28,6 +32,35 @@ $ curl -v http://127.0.0.1:8080/sign -H "Content-Type: text/plain" -d "test"
|
|||
urlSafeBase64_of_signed_string_test
|
||||
```
|
||||
|
||||
### Проверка подписи маркера доступа
|
||||
|
||||
Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain).
|
||||
Проверяет подпись маркера доступа, полученного в теле запроса.
|
||||
В ответе возвращает один из следующих статус-кодов:
|
||||
- 200 OK - подпись валидна
|
||||
- 401 Unauthorized - подпись невалидна (в теле ответа возвращается код ошибки от криптопровайдера)
|
||||
- 500 Internal Server Error - внутренняя ошибка сервера
|
||||
|
||||
Пример выполнения запроса:
|
||||
```
|
||||
$ curl -v http://127.0.0.1:8080/verify -H "Content-Type: text/plain" -d "some_valid_access_token"
|
||||
* Trying 127.0.0.1:8080...
|
||||
* Connected to 127.0.0.1 (127.0.0.1) port 8080
|
||||
> POST /sign HTTP/1.1
|
||||
> Host: 127.0.0.1:8080
|
||||
> User-Agent: curl/8.4.0
|
||||
> Accept: */*
|
||||
> Content-Type: text/plain
|
||||
> Content-Length: 4
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Server: nginx/1.24.0
|
||||
< Date: Wed, 16 Oct 2024 09:55:46 GMT
|
||||
< Transfer-Encoding: chunked
|
||||
< Connection: keep-alive
|
||||
<
|
||||
```
|
||||
|
||||
## Сборка из исходников
|
||||
|
||||
Инструкции по сборке из исходников см. "Инструкция по сборке.md"
|
||||
|
|
@ -46,8 +79,9 @@ cmake -DCONFIG_NAME=/opt/ervu-sign-module.conf ..
|
|||
|
||||
Приложение настраивается в конфигурационном файле, заданном на этапе сборки (по умолчанию, /etc/ervu-sign-module.conf).
|
||||
|
||||
- В секции **\[main\]** задать количество воркеров
|
||||
worker_processes = 10 *\# значение по умолчанию: 10*
|
||||
- В секции **\[main\]** задать общие настройки:
|
||||
worker_processes = 10 *\# количество воркеров (значение по умолчанию: 10)*
|
||||
cp_file = libcapi20.so *\# путь до файла библиотеки криптопровайдера*
|
||||
|
||||
- В секции **\[fcgi\]** задать настройки fcgi-сервера:
|
||||
fcgi_listen_port = 9009 *\# значение по умолчанию: 9009, должно совпадать со значением в nginx.conf*
|
||||
|
|
@ -55,8 +89,10 @@ fcgi_listen_host = 127.0.0.1 *\# значение по умолчанию: 127.0
|
|||
fcgi_thread_pool_size = 1 *\# значение по умолчанию: 1*
|
||||
|
||||
- В секции **\[sign\]** задать настройки модуля подписания:
|
||||
location = /sign *\# значение по умолчанию: /sign, должно совпадать со значением в nginx.conf*
|
||||
cp_file = libcapi20.so *\# путь до файла библиотеки криптопровайдера*
|
||||
signer_subject = signer@example.ru *\# email, ИНН, СНИЛС или любая другая строка из свойства контейнера «Субъект»*
|
||||
pin = \*\*\*\* *\# пароль от контейнера*
|
||||
location = /sign *\# значение по умолчанию: /sign*
|
||||
sign_cert_thumbprint = sha1_thumbprint_of_signer_cert *\# SHA1 отпечаток сертификата, которым ИС подписывает секрет*
|
||||
sign_cert_password = \*\*\*\* *\# пароль от контейнера*
|
||||
|
||||
- В секции **\[verify\]** задать настройки проверки подписи маркера доступа:
|
||||
location = /verify *\# значение по умолчанию: /verify*
|
||||
esia_cert_thumbprint = sha1_thumbprint_of_esia_cert *\# SHA1 отпечаток сертификата ЕСИА*
|
||||
|
|
|
|||
BIN
conf/TESIA GOST 2012 new.cer
Normal file
BIN
conf/TESIA GOST 2012 new.cer
Normal file
Binary file not shown.
BIN
conf/b0fd8eb959d9489d5b7b4c143a06cad7952a0744.crl
Normal file
BIN
conf/b0fd8eb959d9489d5b7b4c143a06cad7952a0744.crl
Normal file
Binary file not shown.
|
|
@ -1,5 +1,6 @@
|
|||
[main]
|
||||
#worker_processes = 10
|
||||
cp_file = /opt/cprocsp/lib/amd64/libcapi20.so
|
||||
|
||||
[fcgi]
|
||||
fcgi_listen_port = 9009
|
||||
|
|
@ -8,6 +9,9 @@ fcgi_listen_host = 127.0.0.1
|
|||
|
||||
[sign]
|
||||
#location = /sign
|
||||
cp_file = /opt/cprocsp/lib/amd64/libcapi20.so
|
||||
signer_subject = signer@example.ru
|
||||
pin = ****
|
||||
sign_cert_thumbprint =
|
||||
sign_cert_password = ****
|
||||
|
||||
[verify]
|
||||
#location = /verify
|
||||
esia_cert_thumbprint =
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ server {
|
|||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location = /sign {
|
||||
location / {
|
||||
fastcgi_pass ervu-sign-module:9009;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ http {
|
|||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location = /sign {
|
||||
location / {
|
||||
fastcgi_pass localhost:9009;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
|
|
|||
36
conf/test_ca_rtk3.cer
Normal file
36
conf/test_ca_rtk3.cer
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIGLTCCBdqgAwIBAgIRAtGQqQCIsFiWS3yUT9DfFhcwCgYIKoUDBwEBAwIwggGB
|
||||
MRUwEwYFKoUDZAQSCjc3MDcwNDkzODgxGDAWBgUqhQNkARINMTAyNzcwMDE5ODc2
|
||||
NzELMAkGA1UEBhMCUlUxKTAnBgNVBAgMIDc4INCh0LDQvdC60YIt0J/QtdGC0LXR
|
||||
gNCx0YPRgNCzMSYwJAYDVQQHDB3QodCw0L3QutGCLdCf0LXRgtC10YDQsdGD0YDQ
|
||||
szGBnjCBmwYDVQQJDIGT0LzRg9C90LjRhtC40L/QsNC70YzQvdGL0Lkg0L7QutGA
|
||||
0YPQsyDQodC80L7Qu9GM0L3QuNC90YHQutC+0LUg0JLQnS7QotCV0KAu0JMuLCDQ
|
||||
odC40L3QvtC/0YHQutCw0Y8g0L3QsNCx0LXRgNC10LbQvdCw0Y8sINC00L7QvCAx
|
||||
NCwg0LvQuNGC0LXRgNCwINCQMSYwJAYDVQQKDB3Qn9CQ0J4gItCg0L7RgdGC0LXQ
|
||||
u9C10LrQvtC8IjElMCMGA1UEAwwc0KLQtdGB0YLQvtCy0YvQuSDQo9CmINCg0KLQ
|
||||
mjAeFw0yMzA5MjUxMDA3MjJaFw0zODA5MjUxMDA3MjJaMIIBgTEVMBMGBSqFA2QE
|
||||
Ego3NzA3MDQ5Mzg4MRgwFgYFKoUDZAESDTEwMjc3MDAxOTg3NjcxCzAJBgNVBAYT
|
||||
AlJVMSkwJwYDVQQIDCA3OCDQodCw0L3QutGCLdCf0LXRgtC10YDQsdGD0YDQszEm
|
||||
MCQGA1UEBwwd0KHQsNC90LrRgi3Qn9C10YLQtdGA0LHRg9GA0LMxgZ4wgZsGA1UE
|
||||
CQyBk9C80YPQvdC40YbQuNC/0LDQu9GM0L3Ri9C5INC+0LrRgNGD0LMg0KHQvNC+
|
||||
0LvRjNC90LjQvdGB0LrQvtC1INCS0J0u0KLQldCgLtCTLiwg0KHQuNC90L7Qv9GB
|
||||
0LrQsNGPINC90LDQsdC10YDQtdC20L3QsNGPLCDQtNC+0LwgMTQsINC70LjRgtC1
|
||||
0YDQsCDQkDEmMCQGA1UECgwd0J/QkNCeICLQoNC+0YHRgtC10LvQtdC60L7QvCIx
|
||||
JTAjBgNVBAMMHNCi0LXRgdGC0L7QstGL0Lkg0KPQpiDQoNCi0JowZjAfBggqhQMH
|
||||
AQEBATATBgcqhQMCAiMBBggqhQMHAQECAgNDAARASD1IrLJ64NR5ZKV3lu00YpyS
|
||||
A7W9tOU/DUjlpjKHcqAsDml/B/XX26zG7KocnXLuTaJLR3c7j2FJkRVTTI20JaOC
|
||||
AiAwggIcMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUsP2OuVnZSJ1be0wUOgbK
|
||||
15UqB0QwEgYDVR0TAQH/BAgwBgEB/wIBADAlBgNVHSAEHjAcMAYGBFUdIAAwCAYG
|
||||
KoUDZHEBMAgGBiqFA2RxAjCCAToGBSqFA2RwBIIBLzCCASsMMiLQmtGA0LjQv9GC
|
||||
0L7Qn9GA0L4gQ1NQIDQuMCBSNCIgKNCy0LXRgNGB0LjRjyA0LjApDFMi0KPQtNC+
|
||||
0YHRgtC+0LLQtdGA0Y/RjtGJ0LjQuSDRhtC10L3RgtGAICLQmtGA0LjQv9GC0L7Q
|
||||
n9GA0L4g0KPQpiIg0LLQtdGA0YHQuNC4IDIuMAxP0KHQtdGA0YLQuNGE0LjQutCw
|
||||
0YIg0YHQvtC+0YLQstC10YLRgdGC0LLQuNGPIOKEliDQodCkLzEyNC0zOTcxINC+
|
||||
0YIgMTUuMDEuMjAyMQxP0KHQtdGA0YLQuNGE0LjQutCw0YIg0YHQvtC+0YLQstC1
|
||||
0YLRgdGC0LLQuNGPIOKEliDQodCkLzEyOC00Mzc2INC+0YIgMjguMTAuMjAyMjA9
|
||||
BgUqhQNkbwQ0DDIi0JrRgNC40L/RgtC+0J/RgNC+IENTUCA0LjAgUjQiICjQstC1
|
||||
0YDRgdC40Y8gNC4wKTARBgkrBgEEAYI3FAIEBAwCQ0EwEgYJKwYBBAGCNxUBBAUC
|
||||
AwIAAjAMBgUqhQNkcgQDAgEAMAoGCCqFAwcBAQMCA0EAg7WnK+Nxo9wQGY2ZtVGA
|
||||
EP/KUJNPKWUYOOXk3FHJju/fntsd1kqcs3GkWEfFlrOtg309uRvLwaNc0C/AkEam
|
||||
Ng==
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -101,3 +101,14 @@ fcgi_handler_map_get(fcgi_handler_map_t* map, char* path)
|
|||
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
fcgi_handler_map_clear(fcgi_handler_map_t* map)
|
||||
{
|
||||
if (map == NULL) return;
|
||||
|
||||
for (size_t i = 0; i < map->num; ++i) {
|
||||
fcgi_map_item_clear(&map->locations[i]);
|
||||
}
|
||||
}
|
||||
|
|
@ -230,6 +230,15 @@ fcgi_register_handler(HFcgi hfcgi, char* path, fcgi_handler_t* handler)
|
|||
}
|
||||
|
||||
|
||||
static void
|
||||
fcgi_unregister_handlers(HFcgi hfcgi)
|
||||
{
|
||||
if (hfcgi == NULL) return;
|
||||
|
||||
fcgi_handler_map_clear(&hfcgi->handler_map);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
fcgi_run_server(HFcgi hfcgi)
|
||||
{
|
||||
|
|
@ -299,6 +308,7 @@ fcgi_dispose_server(HFcgi hfcgi)
|
|||
{
|
||||
LOG_TRACE("fcgi_dispose_server enter");
|
||||
|
||||
fcgi_unregister_handlers(hfcgi);
|
||||
free(hfcgi);
|
||||
|
||||
LOG_TRACE("fcgi_dispose_server exit");
|
||||
|
|
|
|||
|
|
@ -66,5 +66,6 @@ void fcgi_server_thread_pool_dispose(fcgi_thread_pool_t* thread_pool);
|
|||
|
||||
int fcgi_handler_map_set(fcgi_handler_map_t* map, char* path, fcgi_handler_t* handler);
|
||||
fcgi_handler_t* fcgi_handler_map_get(fcgi_handler_map_t* map, char* path);
|
||||
void fcgi_handler_map_clear(fcgi_handler_map_t* map);
|
||||
|
||||
#endif // FCGI_SERVER_INTERNAL_H_INCLUDED
|
||||
|
|
|
|||
|
|
@ -17,6 +17,57 @@ fcgi_get_content_length(const FCGX_Request* request)
|
|||
}
|
||||
|
||||
|
||||
fcgi_handler_status_t
|
||||
check_content_length(int content_length, int client_max_body_size)
|
||||
{
|
||||
LOG_TRACE("check_content_length");
|
||||
|
||||
if (content_length > 0 && content_length <= client_max_body_size) {
|
||||
LOG_DEBUG("Content-Length is ok");
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
if (content_length == 0) {
|
||||
LOG_DEBUG("Content-Length == 0, so there is no body");
|
||||
return HANDLER_HTTP_OK;
|
||||
}
|
||||
|
||||
if (content_length < 0) {
|
||||
LOG_WARN("Content-Length < 0 (%d)", content_length);
|
||||
return HANDLER_HTTP_BAD_REQUEST;
|
||||
}
|
||||
|
||||
/* (content_length > client_max_body_size) */
|
||||
LOG_WARN("Content-Length (%d) is too large (> %d)",
|
||||
content_length,
|
||||
client_max_body_size);
|
||||
return HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE;
|
||||
}
|
||||
|
||||
|
||||
fcgi_handler_status_t
|
||||
check_content_type(const FCGX_Request* request, const char* acceptable_content_type)
|
||||
{
|
||||
LOG_TRACE("check_content_type");
|
||||
|
||||
const char* content_type = FCGX_GetParam(FCGI_PARAM_NAME_CONTENT_TYPE, request->envp);
|
||||
if (content_type == NULL) {
|
||||
LOG_ERROR("Could not get FCGI param: "FCGI_PARAM_NAME_CONTENT_TYPE);
|
||||
return HANDLER_HTTP_BAD_REQUEST;
|
||||
}
|
||||
|
||||
if (strcmp(content_type, acceptable_content_type) == 0) {
|
||||
LOG_DEBUG("Content-Type is ok");
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
LOG_WARN("Content-Type is not acceptable, '%s' (!= '%s')",
|
||||
content_type, acceptable_content_type);
|
||||
|
||||
return HANDLER_HTTP_NOT_ACCEPTABLE;
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
fcgi_request_get_param(const FCGX_Request* request, const char* name, /*out*/ bool* err)
|
||||
{
|
||||
|
|
@ -45,6 +96,40 @@ error:
|
|||
}
|
||||
|
||||
|
||||
fcgi_handler_status_t
|
||||
fcgi_request_load_data(const FCGX_Request* request, int content_length, char** content)
|
||||
{
|
||||
LOG_TRACE("fcgi_request_load_data enter");
|
||||
|
||||
char* data = malloc(content_length + 1);
|
||||
if (data == NULL) {
|
||||
LOG_ERROR("Could not allocate memory for request body (%d bytes)", content_length + 1);
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
int actual_content_length = FCGX_GetStr(data, content_length, request->in);
|
||||
|
||||
if (actual_content_length != content_length) {
|
||||
LOG_ERROR("fcgi_request_load_data: "
|
||||
"actual_content_length(%d) != content_length(%d); content: '%.*s'",
|
||||
actual_content_length, content_length,
|
||||
actual_content_length, data);
|
||||
free(data);
|
||||
return HANDLER_HTTP_BAD_REQUEST;
|
||||
}
|
||||
|
||||
data[content_length] = '\0';
|
||||
*content = data;
|
||||
|
||||
LOG_DEBUG("content_length: %d", content_length);
|
||||
LOG_DEBUG("content: '%s'", *content);
|
||||
|
||||
LOG_TRACE("fcgi_request_load_data exit");
|
||||
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
fcgi_handler_status_t
|
||||
fcgi_printf_str(const FCGX_Request* request, const char* response, int response_len)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,6 +56,25 @@ char* fcgi_request_get_param(const FCGX_Request* request, const char* name, bool
|
|||
int
|
||||
fcgi_get_content_length(const FCGX_Request* request);
|
||||
|
||||
/**
|
||||
* Returns: HANDLER_SUCCESS, HANDLER_HTTP_OK (no body), HANDLER_HTTP_BAD_REQUEST,
|
||||
* HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE, HANDLER_ERROR
|
||||
*/
|
||||
fcgi_handler_status_t
|
||||
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);
|
||||
|
||||
/**
|
||||
* Returns: HANDLER_SUCCESS, HANDLER_HTTP_BAD_REQUEST, HANDLER_ERROR
|
||||
*/
|
||||
fcgi_handler_status_t
|
||||
fcgi_request_load_data(const FCGX_Request* request, int content_length, char** content);
|
||||
|
||||
|
||||
fcgi_handler_status_t
|
||||
fcgi_printf_str(const FCGX_Request* request, const char* response, int response_len);
|
||||
|
|
@ -70,6 +89,10 @@ fcgi_printf_header(const FCGX_Request* request, const char* name, const char* va
|
|||
|
||||
#define CRLF "\r\n"
|
||||
|
||||
#define FCGI_200_RESPONSE_FORMAT \
|
||||
"Status: 200 OK" CRLF\
|
||||
CRLF
|
||||
|
||||
#define FCGI_400_RESPONSE_FORMAT \
|
||||
"Status: 400 Bad Request" CRLF\
|
||||
"Content-type: text/plain" CRLF\
|
||||
|
|
@ -123,6 +146,13 @@ typedef fcgi_handler_status_t (*fcgi_request_handler_pt)(const FCGX_Request* req
|
|||
typedef fcgi_request_handler_pt (*FCGI_REQUEST_FINALIZE_HANDLER)(int rc);
|
||||
|
||||
|
||||
static inline fcgi_handler_status_t
|
||||
fcgi_200_ok_handler(const FCGX_Request* request, void* ctx __attribute__((unused)))
|
||||
{
|
||||
LOG_INFO("response status: '200 OK'");
|
||||
return FCGI_CHECK_PRINTF_STATUS(request, FCGI_200_RESPONSE_FORMAT, 200);
|
||||
}
|
||||
|
||||
static inline fcgi_handler_status_t
|
||||
fcgi_400_bad_request_handler(const FCGX_Request* request, void* ctx __attribute__((unused)))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#define MAIN_CONF_SECTION "main"
|
||||
#define MAIN_CONF_KEY_WORKER_PROCESSES "worker_processes"
|
||||
#define MAIN_CONF_KEY_CP_FILE "cp_file"
|
||||
|
||||
/* default configuration values: */
|
||||
static const int FCGI_CONF_DEFAULT_WORKER_PROCESSES = 10;
|
||||
|
|
@ -40,6 +41,14 @@ main_conf_load(main_conf_t* conf, const char *filename, const conf_file_context_
|
|||
CONF_FILE_VALUE_POSITIVE_INT,
|
||||
&FCGI_CONF_DEFAULT_WORKER_PROCESSES
|
||||
},
|
||||
{
|
||||
MAIN_CONF_SECTION,
|
||||
MAIN_CONF_KEY_CP_FILE,
|
||||
&(conf->cp_file),
|
||||
CONF_FILE_VALUE_STRING,
|
||||
CONF_FILE_VALUE_NONE,
|
||||
NULL
|
||||
},
|
||||
};
|
||||
|
||||
if (conf_file_load_values(conf_file, fields, sizeof(fields) / sizeof(conf_file_field_t))) {
|
||||
|
|
@ -70,6 +79,7 @@ main_conf_clear(main_conf_t* conf)
|
|||
return;
|
||||
}
|
||||
|
||||
free(conf->cp_file);
|
||||
free(conf->conf_file);
|
||||
memset(conf, 0, sizeof(main_conf_t));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
typedef struct main_conf_s {
|
||||
int worker_processes;
|
||||
char *cp_file; /* файл криптопровайдера */
|
||||
char *conf_file;
|
||||
} main_conf_t;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@
|
|||
|
||||
#define SIGN_CONF_SECTION "sign"
|
||||
#define SIGN_CONF_KEY_LOCATION "location"
|
||||
#define SIGN_CONF_KEY_CP_FILE "cp_file"
|
||||
#define SIGN_CONF_KEY_SIGNER "signer_subject"
|
||||
#define SIGN_CONF_KEY_PIN "pin"
|
||||
#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: text/plain" CRLF\
|
||||
|
|
@ -18,7 +17,7 @@
|
|||
static const str_t SIGN_CONF_DEFAULT_LOCATION = str_t_const("/sign");
|
||||
|
||||
static const int CLIENT_MAX_BODY_SIZE = 4096;
|
||||
static const str_t ACCEPTABLE_CONTENT_TYPE = str_t_const("text/plain");
|
||||
static const char* ACCEPTABLE_CONTENT_TYPE = "text/plain";
|
||||
|
||||
typedef struct sign_service_s {
|
||||
const sign_conf_t *conf;
|
||||
|
|
@ -39,13 +38,6 @@ 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 check_content_length(int content_length);
|
||||
|
||||
static fcgi_handler_status_t check_content_type(const FCGX_Request* request);
|
||||
|
||||
static fcgi_handler_status_t fcgi_request_load_data(const FCGX_Request* request,
|
||||
int content_length, char** content);
|
||||
|
||||
static int sign_content(const sign_service_t *hsign, fcgi_sign_request_t *req_info);
|
||||
|
||||
int
|
||||
|
|
@ -66,24 +58,16 @@ sign_conf_load(sign_conf_t *conf, const conf_file_context_t conf_file)
|
|||
},
|
||||
{
|
||||
SIGN_CONF_SECTION,
|
||||
SIGN_CONF_KEY_CP_FILE,
|
||||
&(conf->cp_file),
|
||||
CONF_FILE_VALUE_STRING,
|
||||
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_SIGNER,
|
||||
&(conf->signer),
|
||||
CONF_FILE_VALUE_STRING,
|
||||
CONF_FILE_VALUE_NONE,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
SIGN_CONF_SECTION,
|
||||
SIGN_CONF_KEY_PIN,
|
||||
&(conf->pin),
|
||||
SIGN_CONF_KEY_SIGN_CERT_PASSWORD,
|
||||
&(conf->sign_cert_password),
|
||||
CONF_FILE_VALUE_STRT,
|
||||
CONF_FILE_VALUE_PSWD,
|
||||
NULL
|
||||
|
|
@ -110,14 +94,13 @@ sign_conf_clear(sign_conf_t *conf)
|
|||
|
||||
if (conf == NULL) return;
|
||||
|
||||
if (conf->pin.data != 0) {
|
||||
explicit_bzero(conf->pin.data, conf->pin.len);
|
||||
if (conf->sign_cert_password.data != 0) {
|
||||
explicit_bzero(conf->sign_cert_password.data, conf->sign_cert_password.len);
|
||||
}
|
||||
str_t_clear(&conf->pin);
|
||||
str_t_clear(&conf->sign_cert_password);
|
||||
|
||||
str_t_clear(&conf->location);
|
||||
free(conf->cp_file);
|
||||
free(conf->signer);
|
||||
str_t_clear(&conf->sign_cert_thumbprint);
|
||||
|
||||
memset(conf, 0, sizeof(sign_conf_t));
|
||||
}
|
||||
|
|
@ -135,12 +118,9 @@ sign_service_create(const sign_conf_t *conf)
|
|||
|
||||
hsign->conf = conf;
|
||||
|
||||
if (!cryptopro_init(conf->cp_file)) {
|
||||
LOG_ERROR("Could not init Cryptographic Provider");
|
||||
goto error;
|
||||
}
|
||||
|
||||
cryptopro_context_set(&hsign->cryptopro_ctx, conf->signer, &conf->pin);
|
||||
cryptopro_context_set(&hsign->cryptopro_ctx,
|
||||
&conf->sign_cert_thumbprint,
|
||||
&conf->sign_cert_password);
|
||||
|
||||
LOG_TRACE("sign_service_create exit");
|
||||
return (HSign)hsign;
|
||||
|
|
@ -177,12 +157,12 @@ fcgi_sign_handler(FCGX_Request* request, void* ctx)
|
|||
|
||||
req_info.content_length = fcgi_get_content_length(request);
|
||||
|
||||
status = check_content_length(req_info.content_length);
|
||||
status = check_content_length(req_info.content_length, CLIENT_MAX_BODY_SIZE);
|
||||
if (status != HANDLER_SUCCESS) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
status = check_content_type(request);
|
||||
status = check_content_type(request, ACCEPTABLE_CONTENT_TYPE);
|
||||
if (status != HANDLER_SUCCESS) {
|
||||
goto exit;
|
||||
}
|
||||
|
|
@ -268,90 +248,6 @@ fcgi_request_finalize_handler(fcgi_handler_status_t status)
|
|||
return handler;
|
||||
}
|
||||
|
||||
static fcgi_handler_status_t
|
||||
check_content_length(int content_length)
|
||||
{
|
||||
LOG_TRACE("check_content_length");
|
||||
|
||||
if (content_length > 0 && content_length <= CLIENT_MAX_BODY_SIZE) {
|
||||
LOG_DEBUG("Content-Length is ok");
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
if (content_length == 0) {
|
||||
LOG_DEBUG("Content-Length == 0, so there is nothing to sign");
|
||||
return HANDLER_HTTP_OK;
|
||||
}
|
||||
|
||||
if (content_length < 0) {
|
||||
LOG_WARN("Content-Length < 0 (%d)", content_length);
|
||||
return HANDLER_HTTP_BAD_REQUEST;
|
||||
}
|
||||
|
||||
if (content_length > CLIENT_MAX_BODY_SIZE) {
|
||||
LOG_WARN("Content-Length (%d) is too large (> %d)",
|
||||
content_length,
|
||||
CLIENT_MAX_BODY_SIZE);
|
||||
return HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE;
|
||||
}
|
||||
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
static fcgi_handler_status_t
|
||||
check_content_type(const FCGX_Request* request)
|
||||
{
|
||||
LOG_TRACE("check_content_type");
|
||||
|
||||
const char* content_type = FCGX_GetParam(FCGI_PARAM_NAME_CONTENT_TYPE, request->envp);
|
||||
if (content_type == NULL) {
|
||||
LOG_ERROR("Could not get FCGI param: "FCGI_PARAM_NAME_CONTENT_TYPE);
|
||||
return HANDLER_HTTP_BAD_REQUEST;
|
||||
}
|
||||
|
||||
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'", content_type);
|
||||
|
||||
return HANDLER_HTTP_NOT_ACCEPTABLE;
|
||||
}
|
||||
|
||||
static fcgi_handler_status_t
|
||||
fcgi_request_load_data(const FCGX_Request* request, int content_length, char** content)
|
||||
{
|
||||
LOG_TRACE("fcgi_request_load_data enter");
|
||||
|
||||
char* data = malloc(content_length + 1);
|
||||
if (data == NULL) {
|
||||
LOG_ERROR("Could not allocate memory for request body (%d bytes)", content_length + 1);
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
int actual_content_length = FCGX_GetStr(data, content_length, request->in);
|
||||
|
||||
if (actual_content_length != content_length) {
|
||||
LOG_ERROR("fcgi_request_load_data: "
|
||||
"actual_content_length(%d) != content_length(%d); content: '%.*s'",
|
||||
actual_content_length, content_length,
|
||||
actual_content_length, data);
|
||||
free(data);
|
||||
return HANDLER_HTTP_BAD_REQUEST;
|
||||
}
|
||||
|
||||
data[content_length] = '\0';
|
||||
*content = data;
|
||||
|
||||
LOG_DEBUG("content_length: %d", content_length);
|
||||
LOG_DEBUG("content: '%s'", *content);
|
||||
|
||||
LOG_TRACE("fcgi_request_load_data exit");
|
||||
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
sign_content(const sign_service_t *hsign, fcgi_sign_request_t *req_info)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ typedef struct sign_service_t* HSign;
|
|||
typedef struct sign_conf_s {
|
||||
str_t location;
|
||||
|
||||
char *cp_file; /* файл криптопровайдера */
|
||||
|
||||
char *signer; /* субъект контейнера */
|
||||
str_t pin; /* pin-код от контейнера */
|
||||
str_t sign_cert_thumbprint; /* SHA1 отпечаток сертификата ИС */
|
||||
str_t sign_cert_password; /* пароль от контейнера */
|
||||
|
||||
} sign_conf_t;
|
||||
|
||||
|
|
|
|||
307
src/modules/service_verify.c
Normal file
307
src/modules/service_verify.c
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
#include "service_verify.h"
|
||||
|
||||
#include "fcgisrv/fcgi_utils.h"
|
||||
|
||||
#include "utils/base64.h"
|
||||
#include "utils/cryptopro.h"
|
||||
#include "utils/jwt.h"
|
||||
|
||||
|
||||
#define VERIFY_CONF_SECTION "verify"
|
||||
#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";
|
||||
|
||||
|
||||
typedef struct verify_service_s {
|
||||
const verify_conf_t *conf;
|
||||
|
||||
} verify_service_t;
|
||||
|
||||
|
||||
typedef struct fcgi_verify_request_s {
|
||||
char *content;
|
||||
int content_length;
|
||||
|
||||
char *verify_error;
|
||||
|
||||
} 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(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, &req_info);
|
||||
|
||||
fcgi_verify_request_clear(&req_info);
|
||||
|
||||
LOG_TRACE("fcgi_verify_handler exit");
|
||||
return status;
|
||||
}
|
||||
|
||||
static fcgi_handler_status_t
|
||||
fcgi_401_bad_signature_handler(const FCGX_Request* request, void *ctx)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
case HANDLER_HTTP_BAD_REQUEST:
|
||||
handler = fcgi_400_bad_request_handler;
|
||||
break;
|
||||
|
||||
case HANDLER_HTTP_UNAUTHORIZED:
|
||||
handler = fcgi_401_bad_signature_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);
|
||||
free(req_info->verify_error);
|
||||
|
||||
memset(req_info, 0, sizeof(fcgi_verify_request_t));
|
||||
}
|
||||
|
||||
static fcgi_handler_status_t
|
||||
verify_jwt_sign(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 (jwt_get_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;
|
||||
}
|
||||
if (decode_base64_url(&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)) {
|
||||
LOG_ERROR("Could not decode jwt header");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (jwt_get_alg_from_header(&header, &alg)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (cryptopro_verify(&ctx->conf->esia_cert_thumbprint, &alg, &header_payload, &sign,
|
||||
&is_verified, &req_info->verify_error)) {
|
||||
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
|
||||
58
src/modules/service_version.c
Normal file
58
src/modules/service_version.c
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#include "service_version.h"
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#include "fcgisrv/fcgi_utils.h"
|
||||
|
||||
#include "utils/logger.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#define VERSION_TEXT \
|
||||
"Content-type: text/plain" CRLF CRLF \
|
||||
APP_VERSION CRLF
|
||||
|
||||
static const str_t VERSION_CONF_DEFAULT_LOCATION = str_t_const("/version");
|
||||
|
||||
|
||||
int
|
||||
version_conf_load(version_conf_t *conf)
|
||||
{
|
||||
LOG_TRACE("version_conf_load enter");
|
||||
|
||||
assert(conf != NULL);
|
||||
|
||||
conf->location = &VERSION_CONF_DEFAULT_LOCATION;
|
||||
|
||||
LOG_TRACE("version_conf_load exit");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
version_conf_clear(version_conf_t *conf)
|
||||
{
|
||||
LOG_TRACE("version_conf_clear enter");
|
||||
|
||||
if (conf != NULL) {
|
||||
conf->location = NULL;
|
||||
}
|
||||
|
||||
LOG_TRACE("version_conf_clear exit");
|
||||
}
|
||||
|
||||
fcgi_handler_status_t
|
||||
fcgi_version_handler(FCGX_Request* request, void* ctx)
|
||||
{
|
||||
LOG_TRACE("fcgi_version_handler enter");
|
||||
|
||||
if (request == NULL) {
|
||||
LOG_ERROR("fcgi_version_handler failed: request is NULL");
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
FCGX_FPrintF(request->out, VERSION_TEXT);
|
||||
|
||||
LOG_TRACE("fcgi_version_handler exit");
|
||||
return HANDLER_SUCCESS;
|
||||
}
|
||||
22
src/modules/service_version.h
Normal file
22
src/modules/service_version.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef SERVICE_VERSION_H_INCLUDED
|
||||
#define SERVICE_VERSION_H_INCLUDED
|
||||
|
||||
|
||||
#include "fcgisrv/fcgi_server.h"
|
||||
|
||||
#include "utils/str_t.h"
|
||||
|
||||
|
||||
typedef struct version_conf_s {
|
||||
const str_t *location;
|
||||
|
||||
} version_conf_t;
|
||||
|
||||
|
||||
int version_conf_load(version_conf_t *conf);
|
||||
void version_conf_clear(version_conf_t *conf);
|
||||
|
||||
fcgi_handler_status_t fcgi_version_handler(FCGX_Request* request, void* ctx);
|
||||
|
||||
|
||||
#endif // SERVICE_VERSION_H_INCLUDED
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
#include "service_manager.h"
|
||||
|
||||
#include "utils/cryptopro.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
|
|
@ -159,6 +161,16 @@ service_manager_load_conf(const char* config_file_name, service_manager_conf_t*
|
|||
goto error;
|
||||
}
|
||||
|
||||
if (verify_conf_load(&services_cf->verify_cf, conf_file)) {
|
||||
LOG_ERROR("Verify service configuraton loading failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (version_conf_load(&services_cf->version_cf)) {
|
||||
LOG_ERROR("Show version service configuraton loading failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
conf_file_close_file(conf_file);
|
||||
|
||||
LOG_TRACE("service_manager_load_conf exit with success");
|
||||
|
|
@ -178,7 +190,9 @@ service_manager_clear_conf(service_manager_conf_t* services_cf)
|
|||
{
|
||||
assert(services_cf != NULL);
|
||||
|
||||
version_conf_clear(&services_cf->version_cf);
|
||||
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);
|
||||
}
|
||||
|
|
@ -196,6 +210,8 @@ deinit_services(service_manager_t* services)
|
|||
|
||||
sign_service_free(services->hsign);
|
||||
|
||||
verify_service_free(services->hverify);
|
||||
|
||||
LOG_TRACE("deinit_services exit");
|
||||
|
||||
return 0;
|
||||
|
|
@ -217,6 +233,12 @@ init_services(service_manager_t* services, const service_manager_conf_t* service
|
|||
goto error;
|
||||
}
|
||||
|
||||
if (!cryptopro_init(services_cf->main_cf.cp_file)) {
|
||||
LOG_ERROR("Could not init Cryptographic Provider");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* sign service */
|
||||
services->hsign = sign_service_create(&services_cf->sign_cf);
|
||||
if (services->hsign == NULL) {
|
||||
goto error;
|
||||
|
|
@ -228,6 +250,25 @@ init_services(service_manager_t* services, const service_manager_conf_t* 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;
|
||||
}
|
||||
|
||||
/* show version service */
|
||||
if (register_service(services, services_cf, fcgi_version_handler, NULL,
|
||||
services_cf->version_cf.location)) {
|
||||
LOG_ERROR("Could not register 'show version service'");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* run fastcgi server */
|
||||
if (fcgi_run_server(services->hfcgi) != 0) {
|
||||
LOG_ERROR("Could not run FastCGI server");
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#include "fcgisrv/fcgi_server.h"
|
||||
|
||||
#include "modules/service_sign.h"
|
||||
#include "modules/service_verify.h"
|
||||
#include "modules/service_version.h"
|
||||
|
||||
struct service_s;
|
||||
|
||||
|
|
@ -46,12 +48,15 @@ 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;
|
||||
version_conf_t version_cf;
|
||||
|
||||
} service_manager_conf_t;
|
||||
|
||||
typedef struct service_manager_s {
|
||||
HFcgi hfcgi;
|
||||
HSign hsign;
|
||||
HVerify hverify;
|
||||
|
||||
} service_manager_t;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "base64.h"
|
||||
|
||||
#include "logger.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
|
@ -80,3 +82,88 @@ encode_base64_url(str_t *dst, const str_t *src)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
init_decode_base64_url(unsigned char **table)
|
||||
{
|
||||
static unsigned char basis64_url[] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF,
|
||||
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F,
|
||||
0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
||||
};
|
||||
|
||||
assert(table != NULL);
|
||||
|
||||
*table = basis64_url;
|
||||
}
|
||||
|
||||
int
|
||||
decode_base64_url(str_t *dst, const str_t *src)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
unsigned int c;
|
||||
size_t i;
|
||||
size_t n = 0;
|
||||
uint8_t *p = NULL;
|
||||
|
||||
assert(dst != NULL && !str_t_is_null(*dst));
|
||||
assert(src != NULL && !str_t_is_null(*src));
|
||||
|
||||
if ((src->len % 4) == 1) {
|
||||
LOG_ERROR("decode_base64_url failed. Desc: invalid src length (%zd)", src->len);
|
||||
goto error;
|
||||
}
|
||||
|
||||
unsigned char *basis64_url;
|
||||
init_decode_base64_url(&basis64_url);
|
||||
|
||||
p = (uint8_t *) dst->data;
|
||||
|
||||
for (i = 0; i < src->len; i++) {
|
||||
c = (unsigned int) src->data[i];
|
||||
|
||||
if (c < 128 && basis64_url[c] < 64) {
|
||||
value = (value << 6) | basis64_url[c];
|
||||
|
||||
if ((i % 4) == 3) {
|
||||
if (p != NULL) {
|
||||
p[n] = (value >> 16) & 0xFF;
|
||||
p[n + 1] = (value >> 8) & 0xFF;
|
||||
p[n + 2] = value & 0xFF;
|
||||
}
|
||||
|
||||
n += 3;
|
||||
value = 0;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("decode_base64_url failed. Desc: invalid character: %c", src->data[i]);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if ((src->len % 4) == 2) {
|
||||
if (p != NULL) {
|
||||
p[n] = (value >> 4) & 0xFF;
|
||||
}
|
||||
n++;
|
||||
} else if ((src->len % 4) == 3) {
|
||||
if (p != NULL) {
|
||||
p[n] = (value >> 10) & 0xFF;
|
||||
p[n + 1] = (value >> 2) & 0xFF;
|
||||
}
|
||||
n += 2;
|
||||
}
|
||||
|
||||
dst->len = n;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
LOG_ERROR("decode_base64_url exit with error");
|
||||
return -1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
#include "str_t.h"
|
||||
|
||||
#define base64_encoded_length(len) (((len + 2) / 3) * 4)
|
||||
#define base64_decoded_length(len) (((len + 3) / 4) * 3)
|
||||
|
||||
void encode_base64_url(str_t *dst, const str_t *src);
|
||||
int decode_base64_url(str_t *dst, const str_t *src);
|
||||
|
||||
#endif // BASE64_H
|
||||
|
|
@ -26,11 +26,17 @@ capi_function_list_init(library_t *lib, capi_function_list_t *fl)
|
|||
LIBRARY_RESOLVE(fl->CryptReleaseContext, lib, "CryptReleaseContext");
|
||||
LIBRARY_RESOLVE(fl->CryptSignMessage, lib, "CryptSignMessage");
|
||||
LIBRARY_RESOLVE(fl->GetLastError, lib, "GetLastError");
|
||||
LIBRARY_RESOLVE(fl->CryptImportPublicKeyInfo, lib, "CryptImportPublicKeyInfo");
|
||||
LIBRARY_RESOLVE(fl->CryptDestroyKey, lib, "CryptDestroyKey");
|
||||
|
||||
#ifdef UNICODE
|
||||
LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashW");
|
||||
LIBRARY_RESOLVE(fl->CryptVerifySignature, lib, "CryptVerifySignatureW");
|
||||
LIBRARY_RESOLVE(fl->CryptAcquireContext, lib, "CryptAcquireContextW");
|
||||
#else
|
||||
LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashA");
|
||||
LIBRARY_RESOLVE(fl->CryptVerifySignature, lib, "CryptVerifySignatureA");
|
||||
LIBRARY_RESOLVE(fl->CryptAcquireContext, lib, "CryptAcquireContextA");
|
||||
#endif // !UNICODE
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -124,10 +124,65 @@ DECLARE_FN(WINADVAPI,
|
|||
BYTE *pbSignature,
|
||||
DWORD *pdwSigLen));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
BOOL,
|
||||
CRYPT_VERIFY_SIGNATURE_A,
|
||||
(HCRYPTHASH hHash,
|
||||
CONST BYTE *pbSignature,
|
||||
DWORD dwSigLen,
|
||||
HCRYPTKEY hPubKey,
|
||||
LPCSTR szDescription,
|
||||
DWORD dwFlags));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
BOOL,
|
||||
CRYPT_VERIFY_SIGNATURE_W,
|
||||
(HCRYPTHASH hHash,
|
||||
CONST BYTE *pbSignature,
|
||||
DWORD dwSigLen,
|
||||
HCRYPTKEY hPubKey,
|
||||
LPCWSTR szDescription,
|
||||
DWORD dwFlags));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
BOOL,
|
||||
CRYPT_ACQUIRE_CONTEXT_A,
|
||||
(HCRYPTPROV *phProv,
|
||||
LPCSTR pszContainer,
|
||||
LPCSTR pszProvider,
|
||||
DWORD dwProvType,
|
||||
DWORD dwFlags));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
BOOL,
|
||||
CRYPT_ACQUIRE_CONTEXT_W,
|
||||
(HCRYPTPROV *phProv,
|
||||
LPCSTR pszContainer,
|
||||
LPCSTR pszProvider,
|
||||
DWORD dwProvType,
|
||||
DWORD dwFlags));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
BOOL,
|
||||
CRYPT_IMPORT_PUBLIC_KEY_INFO,
|
||||
(HCRYPTPROV hCryptProv,
|
||||
DWORD dwCertEncodingType,
|
||||
PCERT_PUBLIC_KEY_INFO pInfo,
|
||||
HCRYPTKEY *phKey));
|
||||
|
||||
DECLARE_FN(WINADVAPI,
|
||||
BOOL,
|
||||
CRYPT_DESTROY_KEY,
|
||||
(HCRYPTKEY hKey));
|
||||
|
||||
#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
|
||||
#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
|
||||
#endif // !UNICODE
|
||||
|
||||
|
||||
|
|
@ -146,6 +201,11 @@ typedef struct {
|
|||
CERT_FIND_CERTIFICATE_IN_STORE_FN CertFindCertificateInStore;
|
||||
CERT_GET_CERTIFICATE_CONTEXT_PROPERTY_FN CertGetCertificateContextProperty;
|
||||
CERT_SET_CERTIFICATE_CONTEXT_PROPERTY_FN CertSetCertificateContextProperty;
|
||||
CRYPT_VERIFY_SIGNATURE_FN CryptVerifySignature;
|
||||
CRYPT_ACQUIRE_CONTEXT_FN CryptAcquireContext;
|
||||
CRYPT_IMPORT_PUBLIC_KEY_INFO_FN CryptImportPublicKeyInfo;
|
||||
CRYPT_DESTROY_KEY_FN CryptDestroyKey;
|
||||
|
||||
} capi_function_list_t;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ static HCERTSTORE cert_open_store();
|
|||
|
||||
static PCCERT_CONTEXT get_signer_cert(const cryptopro_context_t *ctx, HCERTSTORE hStoreHandle);
|
||||
|
||||
static int set_pin(PCCERT_CONTEXT pSignerCert, const str_t *pin);
|
||||
static int set_password(PCCERT_CONTEXT cert_ctx, const str_t *password);
|
||||
|
||||
bool
|
||||
cryptopro_init(const char* cp_file)
|
||||
|
|
@ -44,8 +44,8 @@ cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t
|
|||
LOG_TRACE("cryptopro_sign enter");
|
||||
|
||||
assert(ctx != NULL);
|
||||
assert(ctx->signer != NULL);
|
||||
assert(ctx->pin != NULL);
|
||||
assert(!str_t_is_null(*ctx->cert_thumbprint));
|
||||
assert(ctx->password != NULL);
|
||||
assert(data != NULL && !str_t_is_null(*data));
|
||||
assert(sign != NULL);
|
||||
|
||||
|
|
@ -209,47 +209,78 @@ cert_open_store()
|
|||
}
|
||||
|
||||
static PCCERT_CONTEXT
|
||||
get_signer_cert(const cryptopro_context_t *ctx, HCERTSTORE hStoreHandle)
|
||||
get_cert_by_thumbprint(HCERTSTORE hStoreHandle, const str_t* thumbprint)
|
||||
{
|
||||
PCCERT_CONTEXT pSignerCert;
|
||||
str_t thumbprint_bin = str_t_null;
|
||||
|
||||
pSignerCert = cp_function_list.CertFindCertificateInStore(hStoreHandle,
|
||||
LOG_TRACE("get_cert_by_thumbprint enter");
|
||||
|
||||
if (hex_to_bin(thumbprint, &thumbprint_bin)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
CRYPT_HASH_BLOB hash = {
|
||||
.pbData = (BYTE*)thumbprint_bin.data,
|
||||
.cbData = thumbprint_bin.len
|
||||
};
|
||||
|
||||
PCCERT_CONTEXT cert = cp_function_list.CertFindCertificateInStore(hStoreHandle,
|
||||
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
||||
0,
|
||||
CERT_FIND_SUBJECT_STR,
|
||||
ctx->signer,
|
||||
CERT_FIND_HASH,
|
||||
&hash,
|
||||
NULL);
|
||||
if (pSignerCert == NULL) {
|
||||
if (cert == NULL) {
|
||||
LOG_ERROR("Could not find certificate in store");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (set_pin(pSignerCert, ctx->pin)) {
|
||||
str_t_clear(&thumbprint_bin);
|
||||
|
||||
LOG_TRACE("get_cert_by_thumbprint exit");
|
||||
|
||||
return cert;
|
||||
|
||||
error:
|
||||
str_t_clear(&thumbprint_bin);
|
||||
LOG_ERROR("get_cert_by_thumbprint exit with error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PCCERT_CONTEXT
|
||||
get_signer_cert(const cryptopro_context_t *ctx, HCERTSTORE hStoreHandle)
|
||||
{
|
||||
PCCERT_CONTEXT cert_ctx = get_cert_by_thumbprint(hStoreHandle, ctx->cert_thumbprint);
|
||||
if (cert_ctx == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (set_password(cert_ctx, ctx->password)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
LOG_DEBUG("The signer's certificate was found");
|
||||
return pSignerCert;
|
||||
return cert_ctx;
|
||||
|
||||
error:
|
||||
if (pSignerCert) {
|
||||
cp_function_list.CertFreeCertificateContext(pSignerCert);
|
||||
if (cert_ctx) {
|
||||
cp_function_list.CertFreeCertificateContext(cert_ctx);
|
||||
}
|
||||
LOG_ERROR("get_signer_cert exit with error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
set_pin(PCCERT_CONTEXT pSignerCert, const str_t *pin)
|
||||
set_password(PCCERT_CONTEXT cert_ctx, const str_t *password)
|
||||
{
|
||||
DWORD dwSize;
|
||||
if (!cp_function_list.CertGetCertificateContextProperty(
|
||||
pSignerCert,
|
||||
cert_ctx,
|
||||
CERT_KEY_PROV_INFO_PROP_ID,
|
||||
NULL,
|
||||
&dwSize)) {
|
||||
LOG_ERROR("Error getting key property");
|
||||
LOG_ERROR("set_pin exit with error. Last error code: 0x%08x", cp_function_list.GetLastError());
|
||||
LOG_ERROR("set_password exit with error. Last error code: 0x%08x", cp_function_list.GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -257,7 +288,7 @@ set_pin(PCCERT_CONTEXT pSignerCert, const str_t *pin)
|
|||
CRYPT_KEY_PROV_INFO* pKeyInfo = (CRYPT_KEY_PROV_INFO*) pKeyInfoBuffer;
|
||||
|
||||
if (!cp_function_list.CertGetCertificateContextProperty(
|
||||
pSignerCert,
|
||||
cert_ctx,
|
||||
CERT_KEY_PROV_INFO_PROP_ID,
|
||||
pKeyInfo,
|
||||
&dwSize)){
|
||||
|
|
@ -272,14 +303,14 @@ set_pin(PCCERT_CONTEXT pSignerCert, const str_t *pin)
|
|||
memset(&keyProvParam, 0, sizeof(CRYPT_KEY_PROV_PARAM));
|
||||
keyProvParam.dwFlags = 0;
|
||||
keyProvParam.dwParam = keyType;
|
||||
keyProvParam.cbData = (DWORD)(pin->len + 1);
|
||||
keyProvParam.pbData = (BYTE*) pin->data;
|
||||
keyProvParam.cbData = (DWORD)(password->len + 1);
|
||||
keyProvParam.pbData = (BYTE*) password->data;
|
||||
|
||||
pKeyInfo->cProvParam = 1;
|
||||
pKeyInfo->rgProvParam = &keyProvParam;
|
||||
|
||||
if (!cp_function_list.CertSetCertificateContextProperty(
|
||||
pSignerCert,
|
||||
cert_ctx,
|
||||
CERT_KEY_PROV_INFO_PROP_ID,
|
||||
0,
|
||||
pKeyInfo)) {
|
||||
|
|
@ -290,6 +321,182 @@ set_pin(PCCERT_CONTEXT pSignerCert, const str_t *pin)
|
|||
return 0;
|
||||
|
||||
error:
|
||||
LOG_ERROR("set_pin exit with error. Last error code: 0x%08x", cp_function_list.GetLastError());
|
||||
LOG_ERROR("set_password exit with error. Last error code: 0x%08x", cp_function_list.GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
alg_id_from_str(const str_t* alg, /*out*/ ALG_ID* alg_id)
|
||||
{
|
||||
LOG_TRACE("alg_id_from_str enter");
|
||||
|
||||
if (strncmp(alg->data, "GOST3410_2012_256", alg->len) == 0) {
|
||||
*alg_id = CALG_GR3411_2012_256;
|
||||
} else if (strncmp(alg->data, "GOST3410_2012_512", alg->len) == 0) {
|
||||
*alg_id = CALG_GR3411_2012_512;
|
||||
} else if (strncmp(alg->data, "RS256", alg->len) == 0) {
|
||||
*alg_id = CALG_SHA_256;
|
||||
} else {
|
||||
LOG_ERROR("Unknown alg: '%.*s'", (int) alg->len, alg->data);
|
||||
LOG_ERROR("alg_id_from_str exit with error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_TRACE("alg_id_from_str exit");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
get_verify_error(char** verify_error)
|
||||
{
|
||||
LOG_TRACE("get_verify_error enter");
|
||||
|
||||
DWORD err = cp_function_list.GetLastError();
|
||||
|
||||
const char* err_string;
|
||||
|
||||
switch (err) {
|
||||
case NTE_BAD_SIGNATURE:
|
||||
err_string = "sign is invalid: bad signature (0x%08x)";
|
||||
break;
|
||||
|
||||
case NTE_BAD_ALGID:
|
||||
err_string = "sign is invalid: bad alg id (0x%08x)";
|
||||
break;
|
||||
|
||||
default:
|
||||
err_string = "sign is invalid. Last error code: 0x%08x";
|
||||
break;
|
||||
}
|
||||
|
||||
size_t size = strlen(err_string) + 4 /*error code*/ + 1 /*terminating null*/;
|
||||
|
||||
*verify_error = malloc(size);
|
||||
if (*verify_error == NULL) {
|
||||
LOG_ERROR("get_verify_error failed. Could not allocate memory for error string "
|
||||
"(%zd bytes)", size);
|
||||
return;
|
||||
}
|
||||
|
||||
int n = snprintf(*verify_error, size, err_string, err);
|
||||
|
||||
if (n < 0 || (size_t)n >= size) {
|
||||
LOG_ERROR("get_verify_error failed. Error occurred in concatenation err_string");
|
||||
free(*verify_error);
|
||||
*verify_error = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_TRACE("get_verify_error exit");
|
||||
}
|
||||
|
||||
int
|
||||
cryptopro_verify(const str_t* cert_thumbprint, const str_t* alg, const str_t* data,
|
||||
const str_t* sign, bool* is_verified, char** verify_error)
|
||||
{
|
||||
int rc = -1;
|
||||
HCERTSTORE hStoreHandle = NULL;
|
||||
PCCERT_CONTEXT certificate = NULL;
|
||||
HCRYPTPROV hCryptProv = 0;
|
||||
HCRYPTHASH hash = 0;
|
||||
HCRYPTKEY hPubKey = 0;
|
||||
str_t sign_reversed = str_t_null;
|
||||
ALG_ID alg_id;
|
||||
|
||||
LOG_TRACE("cryptopro_verify enter");
|
||||
|
||||
*is_verified = false;
|
||||
|
||||
if (alg_id_from_str(alg, &alg_id)) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (reverse_sign(sign, &sign_reversed)) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
hStoreHandle = cert_open_store();
|
||||
if (hStoreHandle == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
certificate = get_cert_by_thumbprint(hStoreHandle, cert_thumbprint);
|
||||
if (certificate == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!cp_function_list.CryptAcquireContext(&hCryptProv, NULL, CP_KC1_GR3410_2001_PROV,
|
||||
PROV_GOST_2001_DH, CRYPT_VERIFYCONTEXT)) {
|
||||
LOG_ERROR("CryptAcquireContext() failed");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!cp_function_list.CryptImportPublicKeyInfo(hCryptProv, X509_ASN_ENCODING,
|
||||
&certificate->pCertInfo->SubjectPublicKeyInfo,
|
||||
&hPubKey)) {
|
||||
LOG_ERROR("CryptImportPublicKeyInfo() failed");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!cp_function_list.CryptCreateHash(hCryptProv, alg_id, 0, 0, &hash)) {
|
||||
LOG_ERROR("CryptCreateHash() failed");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!cp_function_list.CryptHashData(hash, (CONST BYTE *) data->data, data->len, 0)) {
|
||||
LOG_ERROR("CryptHashData() failed");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (cp_function_list.CryptVerifySignature(hash,
|
||||
(CONST BYTE *) sign_reversed.data,
|
||||
sign_reversed.len,
|
||||
hPubKey,
|
||||
NULL,
|
||||
0)) {
|
||||
LOG_DEBUG("sign is valid");
|
||||
*is_verified = true;
|
||||
} else {
|
||||
get_verify_error(verify_error);
|
||||
if (*verify_error == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
LOG_WARN("%s", *verify_error);
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
|
||||
exit:
|
||||
str_t_clear(&sign_reversed);
|
||||
|
||||
if (hash) {
|
||||
cp_function_list.CryptDestroyHash(hash);
|
||||
}
|
||||
|
||||
if (hPubKey) {
|
||||
cp_function_list.CryptDestroyKey(hPubKey);
|
||||
}
|
||||
|
||||
if (hCryptProv) {
|
||||
cp_function_list.CryptReleaseContext(hCryptProv, 0);
|
||||
}
|
||||
|
||||
if (certificate) {
|
||||
cp_function_list.CertFreeCertificateContext(certificate);
|
||||
}
|
||||
|
||||
if (hStoreHandle) {
|
||||
if (!cp_function_list.CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG)) {
|
||||
LOG_ERROR("CertCloseStore() failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (rc == 0) {
|
||||
LOG_TRACE("cryptopro_verify exit");
|
||||
} else {
|
||||
LOG_ERROR("cryptopro_verify exit with error. Last error code: 0x%08x",
|
||||
cp_function_list.GetLastError());
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,22 +8,25 @@
|
|||
|
||||
|
||||
typedef struct cryptopro_context_s {
|
||||
const char *signer;
|
||||
const str_t *pin;
|
||||
const str_t *cert_thumbprint;
|
||||
const str_t *password;
|
||||
} cryptopro_context_t;
|
||||
|
||||
|
||||
static inline void
|
||||
cryptopro_context_set(cryptopro_context_t *ctx, const char *signer, const str_t *pin)
|
||||
cryptopro_context_set(cryptopro_context_t *ctx, const str_t *cert_thumbprint, const str_t *password)
|
||||
{
|
||||
assert(ctx != NULL);
|
||||
|
||||
ctx->signer = signer;
|
||||
ctx->pin = pin;
|
||||
ctx->cert_thumbprint = cert_thumbprint;
|
||||
ctx->password = password;
|
||||
}
|
||||
|
||||
bool cryptopro_init(const char* cp_file);
|
||||
|
||||
int cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign);
|
||||
|
||||
int cryptopro_verify(const str_t* cert_thumbprint, const str_t* alg, const str_t *data,
|
||||
const str_t *sign, bool* is_verified, char** verify_error);
|
||||
|
||||
#endif // CRYPTOPRO_H_INCLUDED
|
||||
|
|
|
|||
|
|
@ -1,69 +1,9 @@
|
|||
#include "gconf_file.h"
|
||||
#include "glib_utils.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/** Returns the length of the gchar string @str
|
||||
*/
|
||||
static inline size_t gstr_byte_len(const gchar* str)
|
||||
{
|
||||
return strlen((char*) str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copies the gchar string pointed by src into the content pointed by dst, including
|
||||
* the terminating null character (and stopping at that point).
|
||||
* Uses to avoid dependence from glib.
|
||||
* @src: gchar string to be copied
|
||||
* @dst: pointer to destination of type str_t where content is to be copied
|
||||
* Return value: 0 - success, -1 - error
|
||||
*/
|
||||
static int
|
||||
copy_gchar2strt(const gchar* src, str_t* dst)
|
||||
{
|
||||
assert(src != NULL);
|
||||
assert(dst != NULL);
|
||||
|
||||
size_t len = gstr_byte_len(src);
|
||||
char* data = (char*) malloc(len + 1);
|
||||
if (data == NULL) {
|
||||
LOG_ERROR("gchar2strt exit with error: could not allocate memory for dst");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dst->data = data;
|
||||
dst->len = len;
|
||||
memcpy(dst->data, src, dst->len + 1);
|
||||
dst->data[dst->len] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Copies the gchar string pointed by src into the content pointed by return value,
|
||||
* including the terminating null character (and stopping at that point).
|
||||
* Uses to avoid dependence from glib.
|
||||
* @src: gchar string to be copied
|
||||
* Return value: pointer to destination where content is to be copied
|
||||
*/
|
||||
static char*
|
||||
copy_gchar2char(const gchar* src)
|
||||
{
|
||||
assert(src != NULL);
|
||||
|
||||
// char* tmp_end = strchr(src, '\0');
|
||||
char* dst = NULL;
|
||||
size_t len = gstr_byte_len(src);
|
||||
dst = (char*) malloc(len + 1);
|
||||
if (dst == NULL) {
|
||||
LOG_ERROR("gchar2char exit with error: could not allocate memory for dst");
|
||||
return NULL;
|
||||
}
|
||||
memcpy(dst, src, len + 1);
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
/* Returns the string value from GKeyFile associated with key under section and converted to str_t
|
||||
* @ini_file: GKeyFile
|
||||
* @section: section name in config file
|
||||
|
|
|
|||
49
src/utils/glib_utils.c
Normal file
49
src/utils/glib_utils.c
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#include "glib_utils.h"
|
||||
|
||||
#include "logger.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/** Returns the length of the gchar string @str
|
||||
*/
|
||||
static inline size_t
|
||||
gstr_byte_len(const gchar* str)
|
||||
{
|
||||
return strlen((char*) str);
|
||||
}
|
||||
|
||||
int
|
||||
copy_gchar2strt(const gchar* src, str_t* dst)
|
||||
{
|
||||
assert(src != NULL);
|
||||
assert(dst != NULL);
|
||||
|
||||
size_t len = gstr_byte_len(src);
|
||||
char* data = (char*) malloc(len + 1);
|
||||
if (data == NULL) {
|
||||
LOG_ERROR("gchar2strt exit with error: could not allocate memory for dst");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dst->data = data;
|
||||
dst->len = len;
|
||||
memcpy(dst->data, src, dst->len + 1);
|
||||
dst->data[dst->len] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
char*
|
||||
copy_gchar2char(const gchar* src)
|
||||
{
|
||||
assert(src != NULL);
|
||||
|
||||
char* dst = NULL;
|
||||
size_t len = gstr_byte_len(src);
|
||||
dst = (char*) malloc(len + 1);
|
||||
if (dst == NULL) {
|
||||
LOG_ERROR("gchar2char exit with error: could not allocate memory for dst");
|
||||
return NULL;
|
||||
}
|
||||
memcpy(dst, src, len + 1);
|
||||
return dst;
|
||||
}
|
||||
32
src/utils/glib_utils.h
Normal file
32
src/utils/glib_utils.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef GLIB_UTULS_H_INCLUDED
|
||||
#define GLIB_UTULS_H_INCLUDED
|
||||
|
||||
#include "str_t.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
|
||||
/*
|
||||
* Copies the gchar string pointed by src into the content pointed by dst, including
|
||||
* the terminating null character (and stopping at that point).
|
||||
* Uses to avoid dependence from glib.
|
||||
* @src: gchar string to be copied
|
||||
* @dst: pointer to destination of type str_t where content is to be copied
|
||||
* Return value: 0 - success, -1 - error
|
||||
*/
|
||||
int
|
||||
copy_gchar2strt(const gchar* src, str_t* dst);
|
||||
|
||||
|
||||
/*
|
||||
* Copies the gchar string pointed by src into the content pointed by return value,
|
||||
* including the terminating null character (and stopping at that point).
|
||||
* Uses to avoid dependence from glib.
|
||||
* @src: gchar string to be copied
|
||||
* Return value: pointer to destination where content is to be copied
|
||||
*/
|
||||
char*
|
||||
copy_gchar2char(const gchar* src);
|
||||
|
||||
|
||||
#endif // GLIB_UTULS_H_INCLUDED
|
||||
239
src/utils/json_parser.c
Normal file
239
src/utils/json_parser.c
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
#include "json_parser.h"
|
||||
|
||||
#include "glib_utils.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <json-glib/json-glib.h>
|
||||
|
||||
|
||||
typedef struct json_parser_s {
|
||||
JsonParser *parser;
|
||||
void *root;
|
||||
json_parser_type root_type;
|
||||
|
||||
} json_parser_t;
|
||||
|
||||
|
||||
static JsonObject* get_object_from_node(JsonNode *node);
|
||||
static int json_get_object_member_as_string(Hjson_parser hparser, const char* field_name,
|
||||
/*out*/ str_t* field_value);
|
||||
|
||||
|
||||
Hjson_parser
|
||||
json_parser_create(const char* data, size_t data_len)
|
||||
{
|
||||
GError* gerror = NULL;
|
||||
JsonNode* json_node = NULL;
|
||||
JsonNodeType json_node_type;
|
||||
const char *desc = "";
|
||||
|
||||
LOG_TRACE("json_parser_create enter");
|
||||
|
||||
assert(data != NULL && data_len != 0 && "json data is empty");
|
||||
|
||||
json_parser_t* parser = calloc (sizeof(json_parser_t), 1);
|
||||
if (parser == NULL) {
|
||||
desc = "Could not allocate memory for json_parser_t";
|
||||
goto error;
|
||||
}
|
||||
|
||||
parser->parser = json_parser_new();
|
||||
if (parser->parser == NULL) {
|
||||
desc = "Could not create JsonParser";
|
||||
goto error;
|
||||
}
|
||||
|
||||
LOG_DEBUG("json_parser_create. json data: %.*s", (int)data_len, data);
|
||||
|
||||
if (json_parser_load_from_data(parser->parser, data, data_len, &gerror) != TRUE) {
|
||||
LOG_ERROR("json_parser_create. json_parser_load_from_data failed: %s", gerror->message);
|
||||
g_error_free(gerror);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* get root node */
|
||||
json_node = json_parser_get_root(parser->parser);
|
||||
if (json_node == NULL) {
|
||||
desc = "Could not parse json object: json_parser_get_root failed";
|
||||
goto error;
|
||||
}
|
||||
|
||||
json_node_type = json_node_get_node_type(json_node);
|
||||
if (json_node_type != JSON_NODE_OBJECT) {
|
||||
desc = "Could not parse json object: unexpected root type";
|
||||
goto error;
|
||||
}
|
||||
|
||||
parser->root_type = JSON_PARSER_TYPE_OBJECT;
|
||||
|
||||
/* try to get root as object */
|
||||
parser->root = get_object_from_node(json_node);
|
||||
if (parser->root == NULL) {
|
||||
desc = "Could not parse json object: json_node_get_object failed";
|
||||
goto error;
|
||||
}
|
||||
|
||||
return (Hjson_parser)parser;
|
||||
|
||||
error:
|
||||
json_parser_free((Hjson_parser)parser);
|
||||
LOG_ERROR("json_parser_create exit with error. Desc: %s", desc);
|
||||
return NULL;;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
json_parser_free(Hjson_parser hparser)
|
||||
{
|
||||
json_parser_t* parser = (json_parser_t*) hparser;
|
||||
|
||||
if (parser == NULL) return;
|
||||
|
||||
if (parser->parser != NULL) {
|
||||
g_object_unref(parser->parser);
|
||||
}
|
||||
|
||||
free(parser);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
json_get_string_member(Hjson_parser hparser, const char* field_name,
|
||||
/*out*/ str_t* field_value)
|
||||
{
|
||||
LOG_TRACE("json_get_string_member enter");
|
||||
|
||||
switch (json_get_object_member_as_string(hparser, field_name, field_value)) {
|
||||
case 0:
|
||||
LOG_TRACE("json_get_string_member exit successfully. field_value: %.*s",
|
||||
(int) field_value->len,
|
||||
field_value->data);
|
||||
return 0;
|
||||
case 1:
|
||||
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);
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("json_get_object_member_as_string failed, member: '%.*s'",
|
||||
(int) field_value->len, field_value->data);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
LOG_ERROR("json_get_string_member exit with error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static JsonObject*
|
||||
get_object_from_node(JsonNode *node)
|
||||
{
|
||||
JsonObject *ret = NULL;
|
||||
|
||||
if (node == NULL) {
|
||||
LOG_WARN("get_object_from_node. node is NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!JSON_NODE_HOLDS_OBJECT(node)) {
|
||||
LOG_ERROR("Bad json scheme. Expected node: object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = json_node_get_object(node);
|
||||
assert(ret != NULL && "Could not extract the node of correct type");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
get_string_from_node(JsonNode *node, /*out*/ const gchar **value)
|
||||
{
|
||||
assert(value != NULL);
|
||||
|
||||
*value = NULL;
|
||||
|
||||
if (node == NULL) {
|
||||
LOG_WARN("get_string_from_node. node is NULL");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* если нод содержит нулевое значение, оставляем наш указатель так же нулевым */
|
||||
if (JSON_NODE_HOLDS_NULL(node)) {
|
||||
LOG_DEBUG("get_string_from_node. node holds NULL");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!JSON_NODE_HOLDS_VALUE(node) || G_TYPE_STRING != json_node_get_value_type(node)) {
|
||||
LOG_ERROR("Bad json scheme. Expected node: string value. Actual node type: %s",
|
||||
json_node_type_name(node));
|
||||
return -1;
|
||||
}
|
||||
|
||||
*value = json_node_get_string(node);
|
||||
if (*value == NULL) {
|
||||
LOG_ERROR("Bad json node. Expected node: string value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
json_object_get_string(JsonObject *object, const gchar *key, /*out*/const gchar** value)
|
||||
{
|
||||
JsonNode *node = NULL;
|
||||
|
||||
LOG_TRACE("json_object_get_string");
|
||||
|
||||
assert(object != NULL);
|
||||
assert(key != NULL);
|
||||
assert(value != NULL);
|
||||
|
||||
*value = NULL;
|
||||
|
||||
node = json_object_get_member(object, key);
|
||||
|
||||
if (node == NULL) {
|
||||
LOG_DEBUG("object member by key '%s' is not exists", key);
|
||||
/* ret = NULL; */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return get_string_from_node(node, value);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
json_get_object_member_as_string(Hjson_parser hparser, const char* field_name,
|
||||
/*out*/ str_t* field_value)
|
||||
{
|
||||
json_parser_t* parser = (json_parser_t*) hparser;
|
||||
const gchar* value = NULL;
|
||||
|
||||
assert(parser != NULL);
|
||||
assert(parser->root_type == JSON_PARSER_TYPE_OBJECT);
|
||||
assert(parser->root != NULL);
|
||||
assert(field_name != NULL);
|
||||
assert(field_value != NULL && str_t_is_null(*field_value));
|
||||
|
||||
LOG_DEBUG("json_get_object_member_as_string. field name: %s", field_name);
|
||||
|
||||
if (json_object_get_string((JsonObject*) (parser->root), field_name, &value)) {
|
||||
return -1;
|
||||
}
|
||||
if (value == NULL) {
|
||||
return 1;
|
||||
}
|
||||
if (copy_gchar2strt(value, field_value) != 0) {
|
||||
LOG_ERROR("json_get_object_member_as_string. Could not copy field_value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
31
src/utils/json_parser.h
Normal file
31
src/utils/json_parser.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef JSON_PARSER_H_INCLUDED
|
||||
#define JSON_PARSER_H_INCLUDED
|
||||
|
||||
#include "str_t.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
typedef struct json_parser_t *Hjson_parser;
|
||||
|
||||
|
||||
typedef enum json_parser_type_e {
|
||||
JSON_PARSER_TYPE_UNKNOWN,
|
||||
JSON_PARSER_TYPE_STRING,
|
||||
JSON_PARSER_TYPE_OBJECT,
|
||||
JSON_PARSER_TYPE_ARRAY
|
||||
|
||||
} json_parser_type;
|
||||
|
||||
|
||||
Hjson_parser json_parser_create(const char* data, size_t data_len);
|
||||
|
||||
void json_parser_free(Hjson_parser hparser);
|
||||
|
||||
|
||||
int json_get_string_member(Hjson_parser hparser, const char* field_name,
|
||||
/*out*/ str_t* field_value);
|
||||
|
||||
|
||||
#endif // JSON_PARSER_H_INCLUDED
|
||||
75
src/utils/jwt.c
Normal file
75
src/utils/jwt.c
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#include "jwt.h"
|
||||
|
||||
#include "json_parser.h"
|
||||
#include "logger.h"
|
||||
|
||||
#define JWT_ALG_FIELD_NAME "alg"
|
||||
|
||||
int
|
||||
jwt_get_header_payload_and_sign(const str_t *jwt,
|
||||
/*out*/ str_t *header,
|
||||
/*out*/ str_t *header_payload,
|
||||
/*out*/ str_t *sign)
|
||||
{
|
||||
LOG_TRACE("jwt_get_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("jwt_get_header_payload_and_sign exit");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
jwt_get_alg_from_header(const str_t *header, /*out*/ str_t *alg)
|
||||
{
|
||||
LOG_TRACE("jwt_get_alg_from_header enter");
|
||||
|
||||
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);
|
||||
|
||||
LOG_TRACE("jwt_get_alg_from_header exit");
|
||||
return 0;
|
||||
|
||||
error:
|
||||
json_parser_free(parser);
|
||||
LOG_ERROR("jwt_get_alg_from_header exit with error");
|
||||
return -1;
|
||||
}
|
||||
13
src/utils/jwt.h
Normal file
13
src/utils/jwt.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef JWT_H_INCLUDED
|
||||
#define JWT_H_INCLUDED
|
||||
|
||||
#include "str_t.h"
|
||||
|
||||
int jwt_get_header_payload_and_sign(const str_t *jwt,
|
||||
/*out*/ str_t *header,
|
||||
/*out*/ str_t *header_payload,
|
||||
/*out*/ str_t *sign);
|
||||
|
||||
int jwt_get_alg_from_header(const str_t *header, /*out*/ str_t *alg);
|
||||
|
||||
#endif // JWT_H_INCLUDED
|
||||
|
|
@ -49,3 +49,51 @@ str_t_to_string(const str_t* str)
|
|||
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
char2int(char input)
|
||||
{
|
||||
if (input >= '0' && input <= '9') {
|
||||
return input - '0';
|
||||
}
|
||||
|
||||
if (input >= 'A' && input <= 'F') {
|
||||
return input - 'A' + 10;
|
||||
}
|
||||
|
||||
if (input >= 'a' && input <= 'f') {
|
||||
return input - 'a' + 10;
|
||||
}
|
||||
|
||||
LOG_ERROR("char2int exit with error: invalid argument, '%c'", input);
|
||||
assert("char2int exit with error: invalid argument");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
hex_to_bin(const str_t* hex, /*out*/ str_t* bin)
|
||||
{
|
||||
bin->len = hex->len / 2;
|
||||
bin->data = malloc(bin->len + 1);
|
||||
if (bin->data == NULL) {
|
||||
LOG_ERROR("Could not allocate memory for bin value (%zd bytes)", bin->len + 1);
|
||||
goto error;
|
||||
}
|
||||
|
||||
const char* src = hex->data;
|
||||
char* dst = bin->data;
|
||||
|
||||
while (*src && src[1]) {
|
||||
*(dst++) = char2int(*src)*16 + char2int(src[1]);
|
||||
src += 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
str_t_clear(bin);
|
||||
LOG_ERROR("hex_to_bin exit with error");
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -92,4 +92,6 @@ int str_t_copy(str_t* dst, const str_t* src);
|
|||
|
||||
char* str_t_to_string(const str_t* str);
|
||||
|
||||
int hex_to_bin(const str_t* hex, /*out*/ str_t* bin);
|
||||
|
||||
#endif // STR_T_H_INCLUDED
|
||||
|
|
|
|||
30
tests/CMakeLists.txt
Normal file
30
tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
project (test-ervu-sign-module LANGUAGES C)
|
||||
|
||||
include_directories (
|
||||
../src
|
||||
${GLIB2_INCLUDE_DIRS}
|
||||
${JSONGLIB_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
set (ESM_SOURCES
|
||||
../src/utils/glib_utils.c
|
||||
../src/utils/json_parser.c
|
||||
../src/utils/jwt.c
|
||||
../src/utils/logger.c
|
||||
)
|
||||
|
||||
set (HEADERS
|
||||
utils/test_jwt.h
|
||||
)
|
||||
|
||||
set (SOURCES
|
||||
main.c
|
||||
utils/test_jwt.c
|
||||
"${ESM_SOURCES}"
|
||||
)
|
||||
|
||||
add_executable(test-ervu-sign-module ${HEADERS} ${SOURCES})
|
||||
|
||||
target_link_libraries(test-ervu-sign-module cunit glib-2.0 json-glib-1.0 gobject-2.0)
|
||||
|
||||
add_test(NAME test-ervu-sign-module COMMAND test-ervu-sign-module)
|
||||
30
tests/main.c
Normal file
30
tests/main.c
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#include "utils/test_jwt.h"
|
||||
|
||||
#include <CUnit/Basic.h>
|
||||
|
||||
int main(int arv, char** argc)
|
||||
{
|
||||
int failures = -1;
|
||||
(void)arv;
|
||||
(void)argc;
|
||||
|
||||
if (CUE_SUCCESS != CU_initialize_registry())
|
||||
return CU_get_error();
|
||||
|
||||
CU_pSuite suite_jwt = CU_add_suite("JWTSuit", NULL, NULL);
|
||||
if (NULL == suite_jwt) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
CU_basic_set_mode(CU_BRM_NORMAL);
|
||||
CU_basic_run_tests();
|
||||
|
||||
exit:
|
||||
failures = CU_get_number_of_failures();
|
||||
CU_cleanup_registry();
|
||||
|
||||
return (failures == 0) ? 0 : -1;
|
||||
}
|
||||
68
tests/utils/test_jwt.c
Normal file
68
tests/utils/test_jwt.c
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include "test_jwt.h"
|
||||
#include "utils/jwt.h"
|
||||
|
||||
#include <CUnit/Basic.h>
|
||||
|
||||
#define JWT "eyJ2ZXIiOjEsInR5cCI6IkpXVCIsInNidCI6ImF1dGhvcml6YXRpb25fY29kZSIsImFsZyI6IkdP" \
|
||||
"U1QzNDEwXzIwMTJfMjU2In0.eyJuYmYiOjE2ODE3MzI4ODQsInNjb3BlIjoib3BlbmlkIiwiYXV0" \
|
||||
"aF90aW1lIjoxNjgxNzMyODc2LCJpc3MiOiJodHRwOlwvXC9lc2lhLXBvcnRhbDEudGVzdC5nb3N1" \
|
||||
"c2x1Z2kucnVcLyIsInVybjplc2lhOnNpZCI6Ijg3NWQzNDZhLTgzMTktMmM1MS05ZDYwLTYwZmRk" \
|
||||
"MzE4MGVhOSIsInVybjplc2lhOmNsaWVudDpzdGF0ZSI6Ijg5MzgzNDdhLTI0MmMtNGVhYy05NTdk" \
|
||||
"LTk4MDgzNTAzYzUwNyIsImF1dGhfbXRoZCI6IlBXRCIsInVybjplc2lhOnNiaiI6eyJ1cm46ZXNp" \
|
||||
"YTpzYmo6bHZsIjoiUCIsInVybjplc2lhOnNiajp0eXAiOiJQIiwidXJuOmVzaWE6c2JqOm9pZCI6" \
|
||||
"MTAwMDQwMDgyMywidXJuOmVzaWE6c2JqOm5hbSI6Ik9JRC4xMDAwNDAwODIzIn0sImV4cCI6MTY4" \
|
||||
"MTczMzEyNCwicGFyYW1zIjp7fSwiaWF0IjoxNjgxNzMyODg0LCJjbGllbnRfaWQiOiI1MDAyMDEi" \
|
||||
"fQ.8EBVu5Cizok66n1sIrVmS07N_4j9TuaKHlQffr98hwv9amUN5zpckH4dyJtO6IA-8UkhZ19ww" \
|
||||
"Of0F_VCmhOqmQ"
|
||||
|
||||
#define JWT_HEADER "eyJ2ZXIiOjEsInR5cCI6IkpXVCIsInNidCI6ImF1dGhvcml6YXRpb25fY29kZSIsImFsZyI6IkdP" \
|
||||
"U1QzNDEwXzIwMTJfMjU2In0"
|
||||
|
||||
#define JWT_HEADER_PAYLOAD "eyJ2ZXIiOjEsInR5cCI6IkpXVCIsInNidCI6ImF1dGhvcml6YXRpb25fY29kZSIsImFsZ" \
|
||||
"yI6IkdPU1QzNDEwXzIwMTJfMjU2In0.eyJuYmYiOjE2ODE3MzI4ODQsInNjb3BlIjoib3" \
|
||||
"BlbmlkIiwiYXV0aF90aW1lIjoxNjgxNzMyODc2LCJpc3MiOiJodHRwOlwvXC9lc2lhLXB" \
|
||||
"vcnRhbDEudGVzdC5nb3N1c2x1Z2kucnVcLyIsInVybjplc2lhOnNpZCI6Ijg3NWQzNDZh" \
|
||||
"LTgzMTktMmM1MS05ZDYwLTYwZmRkMzE4MGVhOSIsInVybjplc2lhOmNsaWVudDpzdGF0Z" \
|
||||
"SI6Ijg5MzgzNDdhLTI0MmMtNGVhYy05NTdkLTk4MDgzNTAzYzUwNyIsImF1dGhfbXRoZC" \
|
||||
"I6IlBXRCIsInVybjplc2lhOnNiaiI6eyJ1cm46ZXNpYTpzYmo6bHZsIjoiUCIsInVybjp" \
|
||||
"lc2lhOnNiajp0eXAiOiJQIiwidXJuOmVzaWE6c2JqOm9pZCI6MTAwMDQwMDgyMywidXJu" \
|
||||
"OmVzaWE6c2JqOm5hbSI6Ik9JRC4xMDAwNDAwODIzIn0sImV4cCI6MTY4MTczMzEyNCwic" \
|
||||
"GFyYW1zIjp7fSwiaWF0IjoxNjgxNzMyODg0LCJjbGllbnRfaWQiOiI1MDAyMDEifQ"
|
||||
|
||||
#define JWT_SIGN "8EBVu5Cizok66n1sIrVmS07N_4j9TuaKHlQffr98hwv9amUN5zpckH4dyJtO6IA-8UkhZ19ww" \
|
||||
"Of0F_VCmhOqmQ"
|
||||
|
||||
#define JWT_HEADER_DECODED "{\"ver\":1,\"typ\":\"JWT\",\"sbt\":\"authorization_code\"," \
|
||||
"\"alg\":\"GOST3410_2012_256\"}"
|
||||
|
||||
#define JWT_ALG "GOST3410_2012_256"
|
||||
|
||||
void test_jwt_get_header_payload_and_sign()
|
||||
{
|
||||
int rc;
|
||||
str_t jwt = str_t_const(JWT);
|
||||
str_t sign = str_t_null;
|
||||
str_t header = str_t_null;
|
||||
str_t header_payload = str_t_null;
|
||||
|
||||
rc = jwt_get_header_payload_and_sign(&jwt, &header, &header_payload, &sign);
|
||||
CU_ASSERT_EQUAL(rc, 0);
|
||||
|
||||
CU_ASSERT_NSTRING_EQUAL(header.data, JWT_HEADER, header.len);
|
||||
CU_ASSERT_NSTRING_EQUAL(header_payload.data, JWT_HEADER_PAYLOAD, header_payload.len);
|
||||
CU_ASSERT_NSTRING_EQUAL(sign.data, JWT_SIGN, sign.len);
|
||||
}
|
||||
|
||||
void test_jwt_get_alg_from_header()
|
||||
{
|
||||
int rc;
|
||||
str_t header = str_t_const(JWT_HEADER_DECODED);
|
||||
str_t alg = str_t_null;
|
||||
|
||||
rc = jwt_get_alg_from_header(&header, &alg);
|
||||
CU_ASSERT_EQUAL(rc, 0);
|
||||
|
||||
CU_ASSERT_NSTRING_EQUAL(alg.data, JWT_ALG, alg.len);
|
||||
|
||||
str_t_clear(&alg);
|
||||
}
|
||||
8
tests/utils/test_jwt.h
Normal file
8
tests/utils/test_jwt.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef TEST_JWT_H_INCLUDED
|
||||
#define TEST_JWT_H_INCLUDED
|
||||
|
||||
void test_jwt_get_header_payload_and_sign();
|
||||
|
||||
void test_jwt_get_alg_from_header();
|
||||
|
||||
#endif // TEST_JWT_H_INCLUDED
|
||||
116
Инструкция по обновлению с версии 1.0.0 до 1.1.0.txt
Normal file
116
Инструкция по обновлению с версии 1.0.0 до 1.1.0.txt
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
В версии 1.1.0 появились следующие функции:
|
||||
- Проверка подписи маркера доступа
|
||||
- Вывод номера версии приложения
|
||||
|
||||
Для обновления приложения необходимо выполнить следующие шаги (команды выполняются от имени root):
|
||||
|
||||
0. Установить новые зависимости:
|
||||
```
|
||||
apt-get install libjson-glib
|
||||
```
|
||||
|
||||
1. Остановить службу:
|
||||
```
|
||||
systemctl stop ervu-sign-module
|
||||
```
|
||||
|
||||
2. Обновить приложение:
|
||||
```
|
||||
cp ./ervu-sign-module /opt/ervu-sign-module/
|
||||
```
|
||||
|
||||
3. Обновить конфигурационный файл /etc/ervu-sign-module.conf. Для этого:
|
||||
|
||||
3.1. Перенести настройку cp_file из секции [sign] в секцию [main]
|
||||
|
||||
3.2. Задать SHA1 отпечаток сертификата, которым ИС подписывает секрет, для этого:
|
||||
|
||||
3.2.1. В конфигурационном файле получить значение <signer_subject>
|
||||
|
||||
3.2.2. Получить перечень свойств сертификата (команда выполняется от имени ervu):
|
||||
```
|
||||
/opt/cprocsp/bin/amd64/certmgr -list -dn <signer_subject>
|
||||
```
|
||||
|
||||
Из полученного перечня свойств сертификата необходимо получить поле "SHA1 отпечаток".
|
||||
|
||||
3.2.3. В конфигурационном файле в секции [sign] задать полученное значение полю sign_cert_thumbprint:
|
||||
[sign]
|
||||
sign_cert_thumbprint = <значение поля "SHA1 отпечаток", полученное на предыдущем шаге>
|
||||
|
||||
3.2.4. Настройку signer_subject нужно удалить.
|
||||
|
||||
3.3. Переименовать настройку с паролем от контейнера (было - pin, стало - sign_cert_password)
|
||||
[sign]
|
||||
sign_cert_password = <пароль, который раньше задавался в поле pin>
|
||||
|
||||
4. Установить сертификат ЕСИА. Для этого:
|
||||
|
||||
4.1. Скачать сертификаты тестовой и продуктивной сред ЕСИА, используемые для формирования электронных подписей ответов как поставщика, по ссылке: https://esia.gosuslugi.ru/public/esia.zip
|
||||
|
||||
4.2. Из списка сертификатов выбрать тот, который подходит под конфигурацию стенда (в зависимости от алгоритма подписи и от того, является ли среда тестовой или продуктивной):
|
||||
|
||||
| Среда \ Алгоритм подписания | RS256 | GOST3410_2012_256 |
|
||||
|-----------------------------|-----------------------------------------------------|-----------------------------------------------------|
|
||||
| | RSA_TESIA_2024.cer | TESIA GOST 2012 new.cer |
|
||||
| Тестовая | Отпечаток: 6a3813240f6cc285e6d4e6d9a4bfeb26038195d8 | Отпечаток: e654f6114dc19bae84c5748794a4fd7797726e71 |
|
||||
| | Срок действия: по 06.08.2034 | Срок действия: по 24.04.2025 |
|
||||
|-----------------------------|-----------------------------------------------------|-----------------------------------------------------|
|
||||
| | RSA_PROD_2024.cer | ГОСТ+ПРОД+24-25.cer |
|
||||
| Продуктивная | Отпечаток: 29c2f9c8e064d73509eadd685a5508712735e303 | Отпечаток: 20ad21785b9161ef46f075d4dc23c34e1e349228 |
|
||||
| | Срок действия: по 12.05.2025 | Срок действия: по 08.06.2025 |
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
4.3. Установить выбранный сертификат (команда выполняется от имени ervu):
|
||||
```
|
||||
/opt/cprocsp/bin/amd64/certmgr -install -file '<esia_cert>.cer'
|
||||
```
|
||||
|
||||
Результатом команды будет список полей сертификата. Из него необходимо получить следующие значения:
|
||||
- Поле "SHA1 отпечаток": это значение нужно будет указать в конфигурационном файле в качестве настройки <esia_cert_thumbprint>.
|
||||
- Поле "Алгоритм подписи": проверить, что значение совпадает с настройками ИС в ЕСИА.
|
||||
- Поле "Истекает": проверить, что срок действия сертификата не истёк.
|
||||
|
||||
4.4. В конфигурационном файле добавить секцию [verify]:
|
||||
[verify]
|
||||
#location = /verify
|
||||
esia_cert_thumbprint =
|
||||
|
||||
4.5. Прописать SHA1 отпечаток сертификата ЕСИА:
|
||||
[verify]
|
||||
esia_cert_thumbprint = <значение, полученное на шаге 4.3.>
|
||||
|
||||
Примечание. Пример конфигурационного файла можно посмотреть в conf/ervu-sign-module.conf.example
|
||||
|
||||
5. Запустить службу:
|
||||
```
|
||||
systemctl start ervu-sign-module
|
||||
```
|
||||
|
||||
6. Обновить конфигурационный файл /etc/nginx/nginx.conf:
|
||||
заменить строку:
|
||||
|
||||
```
|
||||
location = /sign {
|
||||
```
|
||||
|
||||
на:
|
||||
|
||||
```
|
||||
location / {
|
||||
```
|
||||
|
||||
Примечание. Пример конфигурационного файла можно посмотреть в conf/nginx.conf
|
||||
|
||||
7. Перезапустить nginx:
|
||||
```
|
||||
systemctl reload nginx
|
||||
```
|
||||
|
||||
8. Проверить версию приложения:
|
||||
```
|
||||
curl -v http://127.0.0.1/version
|
||||
```
|
||||
|
||||
Статус-код ответа должен быть равен 200 OK.
|
||||
В ответе должна быть возвращена строчка "1.1.0".
|
||||
|
|
@ -18,7 +18,7 @@ cd -
|
|||
1. Установить зависимости для сборки приложения:
|
||||
``` bash
|
||||
apt-get install cmake gcc gcc10
|
||||
apt-get install glib2-devel libfcgi-devel
|
||||
apt-get install glib2-devel libfcgi-devel libjson-glib libjson-glib-devel
|
||||
```
|
||||
|
||||
2. Собрать приложение из исходников:
|
||||
|
|
|
|||
|
|
@ -11,11 +11,21 @@ adduser --system --no-create-home --user-group ervu
|
|||
|
||||
1. Скачать КриптоПро 5 R3 КС3.
|
||||
|
||||
2. Установить КриптоПро. Для этого выполнить команды от имени root:
|
||||
2. Установить КриптоПро.
|
||||
2.1. Для продуктивного контура:
|
||||
2.1.1. Установить КриптоПро c конфигурацией kc2. Для этого выполнить команды от имени root:
|
||||
``` bash
|
||||
tar -zxvf linux-amd64.tgz && cd ./linux-amd64/
|
||||
./install.sh
|
||||
apt-get install ./cprocsp-pki-cades-64-2.0.14815-1.amd64.rpm
|
||||
./install.sh kc2 cprocsp-pki-cades
|
||||
cd -
|
||||
```
|
||||
2.1.2. Установить ПАК "Соболь" по инструкции: https://support.cryptopro.ru/index.php?/Knowledgebase/Article/View/285/2/ispolzovnie-pak-sobol-v-kchestve-dsch-v-kriptopro-csp-n-linux
|
||||
|
||||
2.2. Для тестового контура:
|
||||
2.2.1. Установить КриптоПро c конфигурацией kc1. Для этого выполнить команды от имени root:
|
||||
``` bash
|
||||
tar -zxvf linux-amd64.tgz && cd ./linux-amd64/
|
||||
./install.sh kc1 cprocsp-pki-cades
|
||||
cd -
|
||||
```
|
||||
|
||||
|
|
@ -24,9 +34,11 @@ cd -
|
|||
/opt/cprocsp/sbin/amd64/cpconfig -license -set "Номер"
|
||||
```
|
||||
|
||||
4. Запросить у руководителя проекта реестра повесток контейнер с ключом ИС, зарегистрированной в ЕСИА.
|
||||
### Установка ключевой пары ИС, зарегистрированной в ЕСИА
|
||||
|
||||
5. Установить полученную ключевую пару для пользователя, от которого запускается модуль подписания (команды выполняются от имени **ervu**).
|
||||
1. Запросить у руководителя проекта реестра повесток контейнер с ключом ИС, зарегистрированной в ЕСИА.
|
||||
|
||||
2. Установить полученную ключевую пару для пользователя, от которого запускается модуль подписания (команды выполняются от имени **ervu**).
|
||||
Внимание! В следующих командах необходимо заменить ***key_cont.000*** на название контейнера, полученного на предыдущем шаге.
|
||||
|
||||
``` bash
|
||||
|
|
@ -38,22 +50,48 @@ chown -R ervu:ervu /var/opt/cprocsp/keys/ervu/key_cont.000/
|
|||
/opt/cprocsp/bin/amd64/csptest -absorb -certs -autoprov
|
||||
```
|
||||
|
||||
6. Получить перечень сертификатов:
|
||||
3. Получить перечень сертификатов:
|
||||
``` bash
|
||||
sudo -u ervu /opt/cprocsp/bin/amd64/certmgr -list
|
||||
/opt/cprocsp/bin/amd64/certmgr -list
|
||||
```
|
||||
|
||||
Получить следующие значения:
|
||||
- Поле "Субъект": любая подстрока из этого поля является signer_subject (нужно будет указать в конфигурационном файле модуля подписания)
|
||||
- Поле "Ссылка на ключ": убедиться, что значение равно "Есть" (иначе проверьте выполнение предыдущих шагов)
|
||||
- Поле "SHA1 отпечаток": это значение нужно будет указать в конфигурационном файле модуля подписания в качестве настройки \<sign_cert_thumbprint\>
|
||||
- Поле "Ссылка на ключ": убедиться, что значение равно "Есть" (иначе необходимо проверить выполнение предыдущих шагов)
|
||||
|
||||
7. Сделать тестовую подпись с помощью установленного контейнера и полученных значений \<signer_subject\> и \<pin\>:
|
||||
4. (опционально) Сделать тестовую подпись с помощью установленного контейнера и полученных значений \<sign_cert_thumbprint\> и \<sign_cert_password\>:
|
||||
``` bash
|
||||
touch test.txt
|
||||
sudo -u ervu /opt/cprocsp/bin/amd64/cryptcp -signf -dn <signer_subject> ./test.txt
|
||||
/opt/cprocsp/bin/amd64/cryptcp -signf -thumbprint <sign_cert_thumbprint> ./test.txt
|
||||
rm -f test.txt test.txt.sgn
|
||||
```
|
||||
|
||||
### Установка сертификата ЕСИА
|
||||
|
||||
1. Скачать сертификаты тестовой и продуктивной сред ЕСИА, используемые для формирования электронных подписей ответов как поставщика, по ссылке: https://esia.gosuslugi.ru/public/esia.zip
|
||||
|
||||
2. Из списка сертификатов выбрать тот, который подходит под конфигурацию стенда (в зависимости от алгоритма подписи и от того, является ли среда тестовой или продуктивной):
|
||||
|
||||
| Среда \ Алгоритм подписания | RS256 | GOST3410_2012_256 |
|
||||
|-----------------------------|-----------------------------------------------------|-----------------------------------------------------|
|
||||
| | RSA_TESIA_2024.cer | TESIA GOST 2012 new.cer |
|
||||
| Тестовая | Отпечаток: 6a3813240f6cc285e6d4e6d9a4bfeb26038195d8 | Отпечаток: e654f6114dc19bae84c5748794a4fd7797726e71 |
|
||||
| | Срок действия: по 06.08.2034 | Срок действия: по 24.04.2025 |
|
||||
|-----------------------------|-----------------------------------------------------|-----------------------------------------------------|
|
||||
| | RSA_PROD_2024.cer | ГОСТ+ПРОД+24-25.cer |
|
||||
| Продуктивная | Отпечаток: 29c2f9c8e064d73509eadd685a5508712735e303 | Отпечаток: 20ad21785b9161ef46f075d4dc23c34e1e349228 |
|
||||
| | Срок действия: по 12.05.2025 | Срок действия: по 08.06.2025 |
|
||||
|
||||
3. Установить выбранный сертификат для пользователя, от которого запускается модуль подписания (команды выполняются от имени **ervu**).
|
||||
``` bash
|
||||
/opt/cprocsp/bin/amd64/certmgr -install -file '<esia_cert>.cer'
|
||||
```
|
||||
|
||||
Результатом команды будет список полей сертификата. Из него необходимо получить следующие значения:
|
||||
- Поле "SHA1 отпечаток": это значение нужно будет указать в конфигурационном файле модуля подписания в качестве настройки \<esia_cert_thumbprint\>.
|
||||
- Поле "Алгоритм подписи": проверить, что значение совпадает с настройками ИС в ЕСИА.
|
||||
- Поле "Истекает": проверить, что срок действия сертификата не истёк.
|
||||
|
||||
### Установка и настройка nginx
|
||||
|
||||
Дальнейшие шаги выполняются от имени root:
|
||||
|
|
@ -77,7 +115,7 @@ systemctl start nginx
|
|||
|
||||
1. Установить зависимости для запуска приложения:
|
||||
``` bash
|
||||
apt-get install glib2 libfcgi
|
||||
apt-get install glib2 libfcgi libjson-glib
|
||||
```
|
||||
|
||||
2. Скопировать исполняемый файл:
|
||||
|
|
@ -134,3 +172,20 @@ curl -v http://127.0.0.1/sign -H "Content-Type: text/plain" -d "test"
|
|||
REFlyzGQrCjX9DvA7hWwN9vf5kPqBxcG4TLYnXUHnAS9_G-sLAFvaJei2OhxpaWNraHbOv_mMsM_bcDsXWiC0Q
|
||||
* Connection #0 to host 127.0.0.1 left intact
|
||||
```
|
||||
|
||||
### Версия приложения
|
||||
|
||||
Для того, чтобы узнать версию приложения, введите команду:
|
||||
|
||||
``` bash
|
||||
curl -v http://127.0.0.1/version
|
||||
```
|
||||
|
||||
В ответе будет получена версия приложения (статус-код ответа должен быть равен 200 OK).
|
||||
|
||||
|
||||
### Примечания
|
||||
|
||||
Подробнее о работе с КриптоПро CSP здесь:
|
||||
- https://www.altlinux.org/%D0%9A%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%9F%D1%80%D0%BE
|
||||
- https://wiki.astralinux.ru/pages/viewpage.action?pageId=32833902&ysclid=m2si404crb648040570
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue