Привязка к оборудованию
Лицензия 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. Выпуск
# 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"}'Нарушения валидации:
{ "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/bulk — hwBinding и hwFingerprint задаются на весь батч.
2. Активация
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 компании выполняет перепривязку:
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"}'- Проверяются права (Admin на компании лицензии; оператор проходит всегда) и статус.
hwFingerprintиhwBindingобновляются.- Все активные активации деактивируются — их места освобождаются.
- Лицензия переподписывается:
currentVersion + 1, новая подписанная версия с новымhwfpв payload. - Публикуется событие
license.reboundи пишется одноимённая аудит-запись — со старым и новым отпечатком, режимами,reasonи числом снятых активаций.
{ "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. Снятие привязки
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_required | 400 | issue / bulk / rebind | fixed без отпечатка | Передать hwFingerprint или выбрать другой режим |
license.hw_fingerprint_not_allowed | 400 | issue / bulk / rebind | Отпечаток при none или firstActivation | Убрать hwFingerprint |
license.invalid_hw_binding | 400 | issue / bulk / rebind | Неизвестный режим | Только none, fixed, firstActivation |
activation.fingerprint_mismatch | 400 | activate | Машина не та | Перепривязать лицензию и перевыдать .ced |
activation.fingerprint_required | 400 | activate | firstActivation, клиент не прислал отпечаток | Проверить, что SDK считает отпечаток и он не переопределён пустой строкой |
activation.not_bound | 409 | activate | fixed без отпечатка | Оператору — выполнить rebind с отпечатком |
activation.seat_limit | 409 | activate | Исчерпан limits.maxSeats | Освободить место: deactivate, сброс активации, либо перевыпуск с бо́льшим лимитом |
license.rebind_revoked | 409 | rebind | Лицензия отозвана | Перевыпустить |
license.rebind_expired | 409 | rebind | Лицензия истекла | Перевыпустить |
Полезные места для расследования:
# 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"См. также
- API Reference — полный справочник эндпоинтов, включая
rebindиbulk. - Клиентский SDK — привязка глазами интегратора.
- Протокол лицензий — где
hwfpживёт внутри токена и маркера. - Webhooks — событие
license.rebound.