Лицензирование: генерация и проверка
В составе 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": "LIC-000123",
"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. Её передают на сервер (см. ниже) или сохраняют в файл.
6) Загрузка лицензии на сервер
HTTP-API
PUT /api/admin/license— загрузка новой лицензии.GET /api/admin/license— получить текущее состояние.
Тело запроса (PUT)
{ "license": "JWS" }
Что делает сервер при PUT:
Валидирует
JWS(ES256, корректныйkid, не истёкшийdueDate,edition = ENTERPRISEи т.п.).Сохраняет сам
JWSв БД, таблицу bd_attachment как запись с:
entity_type=LICENSEfile_name="license.key"content_type="text/plain"content=<байты JWS, UTF-8>
- Вызывает LicenseManager.reload() — режим переключается без рестарта.
7) Где хранится лицензия
Источник лицензии — компонент LicenseSource, который читает по приоритету:
- База данных: берётся последняя запись из
bd_attachmentсentity_type=LICENSE. - Опциональный config fallback: если в БД нет лицензии, берётся строка из
application.yaml:
app:
license:
initial: "${LICENSE_KEY:}"
Этот fallback только для чтения во время старта и не сохраняется в БД автоматически. Постоянное хранилище — только БД через PUT /api/admin/license.
8) Как идёт проверка на сервере
Компонент LicenseVerifier делает следующее:
- Парсит JWS (JJWT).
- По
kidберёт публичный ключ изPublicKeyProvider. - Проверяет
alg == ES256(другие отклоняются). - Требует
edition == "Enterprise". - Сравнивает
dueDateс текущим временем (UTC). - Возвращает
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 рабочему экземпляру (чтобы не усугублять расхождение).
10) Защита от перезаписи лицензии
app:
license:
immutable: true # по умолчанию false
Если лицензии в БД нет — первый
PUT /api/admin/licenseразрешён (импорт).После импорта и при immutable: true — любые последующие
PUTзапрещены (403 Forbidden).GET /api/admin/licenseне зависит от флага.
11) Тест-чеклист
- Ключи лежат в
src/main/resources/licenses.keys/*.pem, имена =kid. PUT /api/admin/licenseс валидным JWS →200 OK,GETпоказываетENTERPRISE+ поля.- В БД в
bd_attachmentпоявилась запись сentity_type=LICENSE, содержимое — исходный JWS (UTF-8). - Удалили лицензию из БД / истёк
dueDate→ режим падает вCOMMUNITY. - Если в БД нет лицензии, а в
application.yamlзаданapp.license.initial, сервер сможет стартовать вENTERPRISEв памяти, но не будет создавать запись в БД до явногоPUT.