Серверное подписание
Примечание
Доступно только в Enterprise версии.
В микросервисе ecos-integrations начиная с версии 2.3.5 добавлен функционал серверного подписания документов электронной подписью без использования плагина КриптоПро ЭЦП Browser plug-in.
Для этого был разработан дополнительный сервис ecos-crypto-sign (репозиторий https://gitlab.citeck.ru/citeck-projects/ecos-crypto-sign)
Общая схема работы:
Описание
Клиент API аутентифицируется в KeyCloak. KeyCloak выдает токен.
Клиент API передает свой запрос в ecos-proxy, указав в заголовке токен.
Компонент ecos-proxy проверяет токен в KeyCloak. Если токен валидный - пропускает запрос дальше. Иначе - ошибка.
Компонент ecos-proxy маршрутизирует запрос на целевой микросервис ecos-gateway. Заголовок с токеном транслируется.
Этот микросервис маршрутизирует запрос на один из доступных микросервисов ecos-integrations. Заголовок с токеном транслируется.
Микросервис интеграций отправляет запрос в микросервис подписания. Заголовок с токеном транслируется.
Микросервис подписания проверяет токен в KeyCloak. Если токен валидный - происходит подписание. Иначе - ошибка. Возврат ответа клиенту API
Сервис подписания это стандартное spring-boot приложение.
На данный момент доступно 2 REST запроса:
Подписание контента
{host:port}/ecos/crypto/sign
headers:
Authorization: Bearer access_token //keycloak bearer access token
body:
{
"documentContent":"QQMTIzNDU2Nzg5",
"keyStoreInfo":{
"providerType":"JCSP",
"keyStoreType":"FAT12_D"
},
"certificateInfo":{
"pin":"1",
"alias":"null",
"serialNumber":"5927a700f7ac223d9548e8628fe9c2c302",
"thumbprint":"A14BFD6503EFD7116AFCE95A6FAA2097E0883DEAD811",
"signatureAlgorithm":""
}
}
documentContent - Контент для подписания (Base64 encoded byte[])
providerType - Тип провайдера (По умолчанию у нас используется JCSP)
keyStoreType - Тип хранилища сертификата (Например HDD в разделе D, как в текущем примере или registry и т.д.)
pin - пароль от контейнера для получения приватного ключа
alias - alias сертификата для поиска нужного сертификата для подписи
serialNumber - серийный номер сертификата? если alias не известен
thumbprint - отпечаток сертификата, используется alias и serialNumber неизвестны
signatureAlgorithm - алгоритм подписи (в данный момент передаваемый алгоритм не используется, берется из найденного сертификата)
В текущей реализации сертификат ищется:
Во-первых по aliasу (если он не пустой, если пустой, то пропускаем данный шаг)
Если не найден, то по серийному номеру (если он не пустой, если пустой, то пропускаем данный шаг)
Если опять не найден, то по отпечатку (если пустой и сертификат не найден, то возвращаем ошибку подписания)
Ожидаемый ответ:
{
"success": true,
"error": "",
"signatureContent": "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"
}
success - результат выполнения rest запроса (успешно/неуспешно)
error - Строка ошибки (При ошибке подписания)
signatureContent - контент полученной подписи (Base64 encoded byte[])
При подписании создается подпись в формате CMS. Возвращается зашифрованная в Base64 строка подписи (аналогично подписанию через плагин).
Валидация подписи
{host:port}/ecos/crypto/signVerify
headers:
Authorization: Bearer access_token //keycloak bearer access token
body:
{
"documentContent":"QQMTIzNDU2Nzg5",
"signatureContent":"MIIMoQYJKoZIh",
"keyStoreInfo":{
"providerType":"JCSP",
"keyStoreType":"FAT12_D"
},
"certificateInfo":{
"pin":"1",
"alias":null,
"serialNumber":"5927a700f713a4c2d9548e8628fe559c2c302",
"thumbprint":"A14BFD6503E123FD76AFCE95A6FAA2097E0883DEAD811"
"signatureAlgorithm":"GOST3411_2012_256withGOST3410DH_2012_256"
}
}
documentContent - Контент, который был подписан (Base64 encoded byte[])
signatureContent - Непосредственно сама подпись (Base64 encoded byte[])
providerType - Тип провайдера (По умолчанию у нас используется JCSP)
keyStoreType - Тип хранилища сертификата (Например HDD в разделе D, как в текущем примере или registry и т.д.)
pin - пароль от контейнера для получения приватного ключа
alias - alias сертификата для поиска нужного сертификата для подписи
serialNumber - серийный номер сертификата если alias неизвестен
thumbprint - отпечаток сертификата, используется если alias и serialNumber неизвестны
signatureAlgorithm - алгоритм подписи (в данный момент передаваемый алгоритм не используется, берется из найденного сертификата)
В текущей реализации сертификат ищется:
Во-первых по aliasу (если он не пустой, если пустой, то пропускаем данный шаг)
Если не найден, то по серийному номеру (если он не пустой, если пустой, то пропускаем данный шаг)
Если опять не найден, то по отпечатку (если пустой и сертификат не найден, то возвращаем ошибку подписания)
Ожидаемый ответ:
{
"success": true,
"error": "",
"result": true
}
success - результат выполнения rest запроса (успешно/неуспешно)
error - Строка ошибки (При ошибке валидации)
result - результат валидации подписи
Проверку токена можно отключить выставив соответствующие свойства:
В микросервисе интеграции: ecos-integrations.server-sign.keycloak.isTokenCheckEnabled
В сервисе подписания: keycloak.enabled
Полный конфиг настроек keycloak сервиса подписания:
keycloak:
enabled: true
auth-server-url: http://localhost:8484/auth
realm: "master"
resource: "service_client"
bearer-only: true
security-constraints:
- authRoles:
- uma_authorization
securityCollections:
- patterns:
- /ecos/*
enabled - включена/отключена проверка токена перед подписью/валидацией
auth-server-url - адрес эндпоинта auth для keycloak
realm - realm для которого происходит проверка токена.
resource - ID клиента к которому необходим доступ (В данном сервисе неактуально, можно добавить любого валидного клиента в указанном realm)
bearer-only - если выставлено true, то приложение может только проверять токены, и в приложении нельзя будет залогиниться
security-constraints - для описания ролевой политики
authRoles - список ролей Keycloak (uma_authorization по умолчанию, так как она выдается всем клиентам)
securityCollections
patterns - URL-паттерны для методов REST API, которые требуется закрыть соответствующими ролями (в данном случае два метода /ecos/“*“)
Конфиг дополнительных настроек для микросервиса интеграции:
ecos-integrations:
server-sign:
root-uri: http://localhost:8083
keycloak:
host: http://localhost
port: 8484
client-id: service_client
client-secret: 5307e923-570b-4fed-ad18-cc6320056bf9
realm: master
isTokenCheckEnabled: false
isTrustAllEnabled: false
root-uri - адрес сервиса подписания
keycloak.host - host keycloak
keycloak.port - порта keycloak
keycloak.client-id - id сервисного клиента keycloak
keycloak.client-secret - secret сервисного клиента keycloak
keycloak.realm - realm для которого происходит проверка токена.
keycloak.isTokenCheckEnabled - при включенном флаге в сервис подписания передается access токен сервисного клиента
keycloak.isTrustAllEnabled - включение/отключение политики TrustAll при запросе токена у кейклока (выставляется в true если падает ошибка доступа, однако нужно учитывать что в таком случае появляется уязвимость)
Создание сервисного клиента keycloak
Сервисного клиента необходимо создать в соответствующем realm keycloak. Он необходим для получения access токена.
Зайти в консоль администратора непосредственно на сервер keycloak
Переключится на нужный realm
Пункт Clients → Добавить нового (Имя клиента - любое) → Cоздать
Пункт Access Type = confidential
Флаг Service Accounts Enabled = true
Valid Redirect URIs - добавит любой uri редиректа на основной сервер alfresco, хотя в данном случае это не обязательно, так как от клиента нам нужен лишь токен и не нужны ресурсы
Save
После этого на вкладке Credentials данного клиента можно найти его secret, id же является заданное нами имя.
Запрос из ecos
Для того чтобы можно было запрашивать валидацию подписи и подписывание любого контента в микросервисе интеграции были реализованы соотвествующие RecordsDao
Подписание - ServerSignRecords
ID - “server-sign“
Dto - SignRequest
public class SignRequest {
private String documentContent;
private KeyStoreInfo keyStoreInfo;
private CertificateInfo certificateInfo;
}
CertificateInfo:
public class CertificateInfo {
private String pin;
private String alias;
private String serialNumber;
private String thumbprint;
private String signatureAlgorithm;
}
KeyStoreInfo:
public class KeyStoreInfo {
private String providerType;
private String keyStoreType;
}
Пример запроса из консоли браузера:
Citeck.Records.query(
{
sourceId: "integrations/server-sign",
query: {
"documentContent":"MTIzNDU2Nzg5",
"keyStoreInfo":{
"providerType":"JCSP",
"keyStoreType":"FAT12_D"
},
"certificateInfo":{
"pin":"1",
"alias":null,
"serialNumber":"",
"thumbprint":"A1424BFD6503EFD7236AFCE95A1236FAA2097E0883DEAD8123",
"signatureAlgorithm":""
}
}
},{
signatureContent: "signatureContent",
error: "error"
}
).then(res => console.log(res));
Валидация - ServerSignVerificationRecords
ID - “server-sign-verify“
Dto - SignVerifyRequest
public class SignVerifyRequest {
private String documentContent;
private String signatureContent;
private KeyStoreInfo keyStoreInfo;
private CertificateInfo certificateInfo;
}
CertificateInfo:
public class CertificateInfo {
private String pin;
private String alias;
private String serialNumber;
private String thumbprint;
private String signatureAlgorithm;
}
KeyStoreInfo:
public class KeyStoreInfo {
private String providerType;
private String keyStoreType;
}
Пример запроса из консоли браузера:
Citeck.Records.query(
{
sourceId: "integrations/server-sign-verify",
query: {
"documentContent":"MTIzNDU2Nzg5",
"signatureContent":"asdasdfbvhadsfbdhasbfks",
"keyStoreInfo":{
"providerType":"JCSP",
"keyStoreType":"FAT12_D"
},
"certificateInfo":{
"pin":"1",
"alias":null,
"serialNumber":"",
"thumbprint":"A14BFD6503EFD1276AFCE952A6FAA205197E0883DEAD813",
"signatureAlgorithm":""
}
}
},{
result: "result",
error: "error"
}
).then(res => console.log(res));
Для локального запуска функционала необходимо клонировать репозиторий ecos-crypto-sign и запустить приложение CryptoSignApp
Недостающие библиотеки (при возникновении ошибок компиляции и NoClassDefFoundException) можно взять с официально сайта КриптоПро
Необходимо установить в нужную jre функционал JCP + JCSP