Серверное подписание

Примечание

Доступно только в Enterprise версии.

В микросервисе ecos-integrations начиная с версии 2.3.5 добавлен функционал серверного подписания документов электронной подписью без использования плагина КриптоПро ЭЦП Browser plug-in.

Для этого был разработан дополнительный сервис ecos-crypto-sign (репозиторий https://gitlab.citeck.ru/citeck-projects/ecos-crypto-sign)

Общая схема работы:

../../_images/server_signing_1png.png

Описание

  1. Клиент API аутентифицируется в KeyCloak. KeyCloak выдает токен.

  2. Клиент API передает свой запрос в ecos-proxy, указав в заголовке токен.

  3. Компонент ecos-proxy проверяет токен в KeyCloak. Если токен валидный - пропускает запрос дальше. Иначе - ошибка.

  4. Компонент ecos-proxy маршрутизирует запрос на целевой микросервис ecos-gateway. Заголовок с токеном транслируется.

  5. Этот микросервис маршрутизирует запрос на один из доступных микросервисов ecos-integrations. Заголовок с токеном транслируется.

  6. Микросервис интеграций отправляет запрос в микросервис подписания. Заголовок с токеном транслируется.

  7. Микросервис подписания проверяет токен в 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 - алгоритм подписи (в данный момент передаваемый алгоритм не используется, берется из найденного сертификата)

В текущей реализации сертификат ищется:

  1. Во-первых по aliasу (если он не пустой, если пустой, то пропускаем данный шаг)

  2. Если не найден, то по серийному номеру (если он не пустой, если пустой, то пропускаем данный шаг)

  3. Если опять не найден, то по отпечатку (если пустой и сертификат не найден, то возвращаем ошибку подписания)

Ожидаемый ответ:

{
    "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 - алгоритм подписи (в данный момент передаваемый алгоритм не используется, берется из найденного сертификата)

В текущей реализации сертификат ищется:

  1. Во-первых по aliasу (если он не пустой, если пустой, то пропускаем данный шаг)

  2. Если не найден, то по серийному номеру (если он не пустой, если пустой, то пропускаем данный шаг)

  3. Если опять не найден, то по отпечатку (если пустой и сертификат не найден, то возвращаем ошибку подписания)

Ожидаемый ответ:

{
    "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 токена.

  1. Зайти в консоль администратора непосредственно на сервер keycloak

  2. Переключится на нужный realm

  3. Пункт Clients → Добавить нового (Имя клиента - любое) → Cоздать

  4. Пункт Access Type = confidential

  5. Флаг Service Accounts Enabled = true

  6. Valid Redirect URIs - добавит любой uri редиректа на основной сервер alfresco, хотя в данном случае это не обязательно, так как от клиента нам нужен лишь токен и не нужны ресурсы

  7. 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