• Ru

Лицензирование: генерация и проверка

В составе UniBPM реализованы функции проверки цифровой подписи и целостности лицензии с использованием стандартного алгоритма (ECDSA-256), входящего в состав Java Security API. Данные функции применяются только для подтверждения подлинности лицензии и не предназначены для защиты информации, являющейся объектом регулирования. Программное обеспечение UniBPM не является СКЗИ, не подлежит обязательной сертификации и не требует лицензии регуляторных ведомств (ФСБ РФ, КНБ РК) на разработку или распространение средств криптографической защиты информации.

1) Общее

Платформа поддерживает два режима:

  • COMMUNITY — ограничения по функционалу (в базовой поставке: разрешён 1 Workflow на весь инстанс).
  • ENTERPRISE — без указанных ограничений. Включается действующей лицензией.

Лицензия — это JWS-токен (подписанный JSON), который хранится на сервере и проверяется на каждом старте и в режиме онлайн.


2) Криптография и форматы

  • Алгоритм подписи: ES256 (ECDSA).
  • Приватный ключ (у лицензиара): PKCS#8 PEM.

Это стандартный контейнер для закрытого ключа (один ключ — один файл), удобен для библиотек и хранения.

  • Публичный ключ (на сервере для проверки):X.509 SubjectPublicKeyInfo PEM.

Это стандартное представление только открытого ключа, без сертификата. Подходит для быстрой верификации подписи.

  • Идентификатор ключа (kid) - это имя файла публичного ключа без расширения, например 2025-08-e1.pem -> kid = 2025-08-e1.

Структура JWS

Header

{
  "alg": "ES256",
  "kid": "2025-08-e1",
  "typ": "JWT"
}

Payload

{
  "edition": "Enterprise",
  "issuedTo": "nazym.kazizmurat@reunico.com",
  "dueDate": "2025-08-20T15:30:59Z",
  "licenseId": "UUID",
  "licenseVersion": 1
}

Поле dueDate — ISO-8601 UTC. По истечении даты лицензия становится недействительной.


3) Размещение публичных ключей

Публичные ключи поставляются вместе с приложением и кладутся в ресурсы:

src/main/resources/licenses.keys/<kid>.pem

При старте компонент PublicKeyProvider загружает все *.pem из:

classpath:/licenses/keys/*.pem

(наш путь маппится на licenses.keys в ресурсах).


4) Генерация ключей (у лицензодателя)

# 1) Сгенерировать EC ключ (secp256r1)
openssl ecparam -name prime256v1 -genkey -noout -out ec-private.pem

# 2) Преобразовать в PKCS#8 (без пароля)
openssl pkcs8 -topk8 -nocrypt -in ec-private.pem -out ec-private-pkcs8.pem

# 3) Достать публичный ключ
openssl ec -in ec-private.pem -pubout -out ec-public.pem

# 4) Содержимое ec-public.pem сохранить в ресурс:
# src/main/resources/licenses.keys/2025-08-e1.pem

5) Выпуск лицензии (CLI)

В проекте есть утилита GenLicenseCli.

Запуск из IDE — аргументы:

--due 2025-08-20T15:30:59Z \
--to "nazym.kazizmurat@reunico.com " \
--kid 2025-08-e1 \
--priv /path/to/ec-private-pkcs8.pem \
--alg ES256

На выходе — строка JWS, которая используется для загрузки лицензии через Admin UI.


6) Загрузка и управление лицензией через Admin UI

Загрузка и управление лицензией в UniBPM выполняется через Admin UI.

6.1 Загрузка через Admin UI

Путь в интерфейсе:

Admin → License management

В UI доступны следующие действия:

  • Upload — загрузка лицензии
  • View — просмотр информации о лицензии
  • Delete — удаление лицензии

При загрузке лицензии:

  1. Администратор вставляет JWS-токен лицензии в UI.
  2. UI передаёт JWS-токен на сервер через внутренний backend-механизм.
  3. Сервер валидирует лицензию и применяет её без перезапуска приложения.

6.2 Что происходит на сервере при загрузке лицензии

Передаваемые данные:

JWS-токен лицензии

Что делает сервер при загрузке лицензии:

  1. Валидирует JWS (ES256, корректный kid, не истёкший dueDate, edition = ENTERPRISE и т.п.).

  2. Сохраняет сам JWS в БД, таблицу bd_attachment как запись с:

  • entity_type = LICENSE

  • file_name = "license.key"

  • content_type = "text/plain"

  • content = <байты JWS, UTF-8>

  • version_tag = licenseId (UUID)

Поле version_tag используется для хранения licenseId и защиты от повторной загрузки одной и той же лицензии.

  1. Вызывает LicenseManager.reload() — режим переключается без рестарта.

6.3 Удаление лицензии

Удаление лицензии выполняется через Admin UI (кнопка Delete).

При удалении:

  1. Запись лицензии удаляется из таблицы bd_attachment.
  2. Выполняется LicenseManager.reload().
  3. Если активной лицензии больше нет, система автоматически переходит в режим COMMUNITY.

Перезапуск приложения не требуется.

7) Где хранится лицензия

Источник лицензии — компонент LicenseSource, который читает по приоритету:

  1. База данных: берётся последняя запись из bd_attachment с entity_type = LICENSE.
  2. Опциональный config fallback: если в БД нет лицензии, берётся строка из application.yaml:
    app:
      license:
        initial: "${LICENSE_KEY:}" 

Этот fallback только для чтения во время старта и не сохраняется в БД автоматически. Постоянное хранилище лицензии база данных. Загрузка лицензии выполняется исключительно через Admin UI.


8) Как идёт проверка на сервере

Компонент LicenseVerifier делает следующее:

  1. Парсит JWS (JJWT).
  2. По kid берёт публичный ключ из PublicKeyProvider.
  3. Проверяет alg == ES256 (другие отклоняются).
  4. Требует edition == "Enterprise".
  5. Сравнивает dueDate с текущим временем (UTC).
  6. Возвращает LicenseClaims (issuedTo, dueDate, kid, и т.п.).

Если верификация не проходит — платформа работает как COMMUNITY.


9) Лицензионная политика (ограничения)

  • Пространство (Workflow) — всегда самое первое (самое раннее по дате создания).
    • При даунгрейде из ENTERPRISE в COMMUNITY, если в базе уже несколько пространств (Workflow), система автоматически будет работать только с самым первым пространством, остальные считаются «невидимыми/недоступными» для операций.
  • GET list / GET by id: в COMMUNITY показываем и допускаем операции только для самого первого пространства (Workflow); прочие id игнорируются (или дают 404/403 — в зависимости от операции).
  • UPDATE / DELETE: разрешены только для самого первого пространства (Workflow).
  • CREATE:
    • если в системе 0 пространств — можно создать первое пространство (Workflow);
    • если в системе > 1 пространств (наследие после даунгрейда) — запрещено создавать новые, пока база не приведена к 1 пространству (Workflow) (чтобы не усугублять расхождение).

10) Защита от перезаписи лицензии

В приложении предусмотрен защитный механизм от изменения лицензии после её загрузки.

    app:
      license:
        immutable: true   # по умолчанию false

При immutable = false разрешены загрузка и удаление лицензии через Admin UI.

При immutable = true после первой загрузки:

  • повторная загрузка лицензии запрещена;

  • удаление лицензии запрещено;

  • все попытки изменения лицензии возвращают 403 Forbidden.

Флаг immutable не влияет на проверку статуса лицензии и работу endpoint /info.

Механизм используется для защиты лицензии в production-окружениях.

11) Тест-чеклист

  1. Ключи лежат в src/main/resources/licenses.keys/*.pem, имена = kid.
  2. Загрузка валидной лицензии через Admin UI → система переходит в режим ENTERPRISE.
  3. В БД в bd_attachment появилась запись с entity_type = LICENSE, содержимое — исходный JWS (UTF-8).
  4. Удалили лицензию из БД / истёк dueDate → режим падает в COMMUNITY.
  5. Если в БД нет лицензии, а в application.yaml задан app.license.initial, сервер сможет стартовать в ENTERPRISE в памяти, но запись в БД будет создана только после загрузки лицензии через Admin UI.

12) Определение текущего режима лицензирования

Текущий режим лицензирования платформы (ENTERPRISE или COMMUNITY) определяется через сервисный endpoint:

 GET /info
{
 "license": {
   "status": "ENTERPRISE"
 }
}

Endpoint используется для:

  • определения текущего режима лицензирования в UI;

  • диагностики состояния системы;

  • проверки лицензии без доступа к административному интерфейсу.