CertifiEd

Привязка к оборудованию

Лицензия CertifiEd может быть привязана к конкретной машине заказчика. Привязка живёт на самой лицензии — режим hwBinding и отпечаток hwFingerprint — и проверяется на каждой активации. Отпечаток попадает в подписанный токен (hwfp), поэтому подменить его на стороне клиента нельзя.

Тарҷумаи тоҷикии ҳуҷҷатҳо дар ҳоли омодашавӣ аст. Матни зерин ба забони русӣ оварда шудааст.

Реализация: HwBindingMode (CertifiEd.Domain/Enums), HwBindingNames (CertifiEd.Application/Licensing), проверка — ClientLicenseService.CheckBinding, выпуск и перепривязка — LicenseService.

Три режима

Режим (wire)Отпечаток при выпускеКто может активировать
noneзапрещёнлюбая машина; hwFingerprint в activate необязателен
fixedобязателентолько машина с этим отпечатком
firstActivationзапрещёнлюбая машина, но первая активация закрепляет отпечаток; дальше — как fixed

Имена в JSON — ровно none, fixed, firstActivation (разбор регистронезависимый). Неизвестное значение — license.invalid_hw_binding (400). Если hwBinding в запросе опущен, режим выводится из hwFingerprint ради обратной совместимости: отпечаток есть — fixed, нет — none.

Когда какой применять

  • `none` — SaaS-подобные и плавающие лицензии, контейнеры и автоскейлинг, где железо меняется само по себе. Ограничивайте не машиной, а числом мест: limits.maxSeats в config.
  • `fixed` — заранее известный сервер заказчика: отпечаток получен при предпродажной подготовке. Самый строгий вариант — чужая машина не активируется вообще.
  • `firstActivation` — типовая поставка on-prem: отпечаток заранее неизвестен, но лицензия должна «осесть» на одной машине. Заказчик ставит продукт, первая активация закрепляет узел.

Отпечаток

Отпечаток считает клиент. SDK по умолчанию использует HwFingerprint.Get() — SHA-256 по имени машины, описанию ОС, первому стабильному MAC-адресу и platform machine id. Своё значение задаётся через CertifiEdClientOptions.HwFingerprint — см. Клиентский SDK.

Сервер трактует отпечаток как непрозрачную строку: обрезает пробелы по краям и сравнивает побайтово (StringComparison.Ordinal). Регистр значим.

1. Выпуск

bash
# fixed — the fingerprint is mandatorycurl -s -b cj.txt -X POST $BASE/api/v1/panel/licenses \  -H 'Content-Type: application/json' \  -d '{"companyId":"'"$COID"'","templateId":"'"$TPLID"'",       "config":"{\"features\":[\"core\"],\"limits\":{\"maxSeats\":1}}",       "hwBinding":"fixed","hwFingerprint":"fp-machine-A"}'
# firstActivation — the fingerprint is forbidden, it is captured on first usecurl -s -b cj.txt -X POST $BASE/api/v1/panel/licenses \  -H 'Content-Type: application/json' \  -d '{"companyId":"'"$COID"'","templateId":"'"$TPLID"'",       "config":"{\"features\":[\"core\"]}","hwBinding":"firstActivation"}'

Нарушения валидации:

JSON
{ "code": "license.hw_fingerprint_required",  "message": "A 'fixed' hardware binding requires hwFingerprint.",  "status": 400 }
{ "code": "license.hw_fingerprint_not_allowed",  "message": "A 'firstActivation' binding must not carry hwFingerprint — it is captured on the first activation.",  "status": 400 }

Те же правила действуют для массового выпуска POST /api/v1/panel/licenses/bulkhwBinding и hwFingerprint задаются на весь батч.

2. Активация

bash
curl -s -X POST $BASE/api/v1/client/activate \  -H 'Content-Type: application/json' \  -d '{"licenseKey":"'"$KEY"'","hwFingerprint":"fp-machine-A","machineName":"erp-01"}'
  • fixed или уже закреплённая firstActivation: отпечаток сверяется, при расхождении — activation.fingerprint_mismatch (400).
  • Ещё не закреплённая firstActivation: hwFingerprint обязателен, иначе activation.fingerprint_required (400). При успехе он записывается в license.hwFingerprint, в аудит попадает license.hw_bound.
  • none: проверок нет.

3. Замена оборудования: перепривязка

Заказчик поменял сервер. Оператор или Admin компании выполняет перепривязку:

bash
curl -s -b cj.txt -X POST $BASE/api/v1/panel/licenses/$LICID/rebind \  -H 'Content-Type: application/json' \  -d '{"hwFingerprint":"fp-machine-B","reason":"motherboard replaced"}'
  1. Проверяются права (Admin на компании лицензии; оператор проходит всегда) и статус.
  2. hwFingerprint и hwBinding обновляются.
  3. Все активные активации деактивируются — их места освобождаются.
  4. Лицензия переподписывается: currentVersion + 1, новая подписанная версия с новым hwfp в payload.
  5. Публикуется событие license.rebound и пишется одноимённая аудит-запись — со старым и новым отпечатком, режимами, reason и числом снятых активаций.
LicenseResponse
{  "id": "019f7ef0-b274-7b01-8417-8181c1d2facf",  "licenseKey": "CFED-5WEE-KE7C-FP7A-C89M",  "status": "Active",  "currentVersion": 2,  "isTrial": false,  "hwBinding": "fixed",  "hwFingerprint": "fp-machine-B"}

4. Снятие привязки

bash
curl -s -b cj.txt -X POST $BASE/api/v1/panel/licenses/$LICID/rebind \  -H 'Content-Type: application/json' \  -d '{"hwFingerprint":null,"reason":"released"}'
# fixed -> firstActivation (re-pins on the next activation)# none  -> none            (nothing to release)

hwBinding в запросе перекрывает этот вывод: например, {"hwFingerprint":null,"hwBinding":"none"} полностью снимает контроль железа. Переход fixed → firstActivation — ровно то, что нужно при выдаче лицензии «в поле»: заказчик сам закрепит её на новом сервере при первом запуске.

Ограничения

  • Перепривязать нельзя отозванную (license.rebind_revoked, 409) и истёкшую (license.rebind_expired, 409) лицензию — их нужно перевыпустить.
  • Состояние fixed без отпечатка API создать не даёт: и выпуск, и перепривязка отклоняют hwBinding: "fixed" без hwFingerprint. Защитная ветка на активации всё же есть — ответ будет activation.not_bound (409).
  • Привязка не ограничивает число мест — это отдельный механизм limits.maxSeats в подписанном config. hwBinding: "none" плюс maxSeats: 5 — валидная плавающая лицензия на пять машин.
  • Отпечаток вычисляется клиентом, поэтому это защита от случайного копирования лицензии, а не от целенаправленного реверса приложения. Криптографическую гарантию даёт подпись токена, а не привязка.
  • Перепривязка инкрементирует версию лицензии; счётчик currentVersion растёт и от переносов (transfer). Лимита на число перепривязок нет — все они видны в аудите.

Диагностика ошибок

КодHTTPГдеПричинаЧто делать
license.hw_fingerprint_required400issue / bulk / rebindfixed без отпечаткаПередать hwFingerprint или выбрать другой режим
license.hw_fingerprint_not_allowed400issue / bulk / rebindОтпечаток при none или firstActivationУбрать hwFingerprint
license.invalid_hw_binding400issue / bulk / rebindНеизвестный режимТолько none, fixed, firstActivation
activation.fingerprint_mismatch400activateМашина не таПерепривязать лицензию и перевыдать .ced
activation.fingerprint_required400activatefirstActivation, клиент не прислал отпечатокПроверить, что SDK считает отпечаток и он не переопределён пустой строкой
activation.not_bound409activatefixed без отпечаткаОператору — выполнить rebind с отпечатком
activation.seat_limit409activateИсчерпан limits.maxSeatsОсвободить место: deactivate, сброс активации, либо перевыпуск с бо́льшим лимитом
license.rebind_revoked409rebindЛицензия отозванаПеревыпустить
license.rebind_expired409rebindЛицензия истеклаПеревыпустить

Полезные места для расследования:

bash
# current mode, fingerprint and versioncurl -s -b cj.txt $BASE/api/v1/panel/licenses/$LICID
# which machines activated, and when they were releasedcurl -s -b cj.txt $BASE/api/v1/panel/licenses/$LICID/activations
# full history: license.rebound / license.hw_bound / activation.rejectedcurl -s -b cj.txt "$BASE/api/v1/panel/audit?entityId=$LICID"

См. также