LicenseManager.LicensingClient 1.0.13
LicenseManager.LicensingClient
Клиентская библиотека лицензирования (.NET 9). Один внутренний NuGet-пакет: LicensingClient + LicensingCore + зависимости и нативы Sentinel (win-x64, linux-x64).
dotnet add package LicenseManager.LicensingClient --version 1.0.13
| Режим | Hasp:Runtime |
Локальный HASP | .lic |
|---|---|---|---|
| Local | Local |
да | да |
| Net | Net |
нет | нет (лицензия на Server_LM) |
Во всех примерах продукта нужен свой ILicensingSecrets (ProductCode; VendorCode — из EmbeddedLicensingSecrets):
using LicensingClient.Secrets;
sealed class MyProductSecrets : ILicensingSecrets
{
private const string ProductCodeValue = "my_product_code";
private static readonly EmbeddedLicensingSecrets Embedded = new();
public string ProductCode
{
get
{
var code = ProductCodeValue.Trim();
if (string.IsNullOrEmpty(code))
throw new InvalidOperationException("ProductCode is not set.");
return code;
}
}
public string VendorCode => Embedded.VendorCode;
}
1. HaspManager (консоль, без кода)
Утилита вендора / сервисной прогрузки ключа. Рядом с exe при --force нужен LicensingServer.dll.
HaspManager
HaspManager show --id <hasp_id>
HaspManager first-setup --id <hasp_id> [--out <корень>]
HaspManager first-setup --id <hasp_id> --out <корень> --force --password <секрет>
HaspManager calchash <пароль>
| Команда | Назначение |
|---|---|
без аргументов / show |
список ключей / якорь |
first-setup |
прогрузка якоря RW + файлы license/client_lm_payload.hasp.bin, data/public_rsa_der.aesc.bin |
first-setup --force |
перезапись якоря (только с LicensingServer.dll + пароль) |
calchash |
SHA-256 hex пароля для константы force в LicensingServer |
Без LicensingServer.dll обычный first-setup работает, --force — нет.
2. Local: своя программа (прогрузка якоря)
2.0. appsettings
{
"Hasp": { "Runtime": "Local" }
}
В продукте force не используется. Для ручной сборки first-setup без DI:
using LicensingCore.FirstRun;
sealed class DenyForceFirstRunAuthorizer : IForceFirstRunAuthorizer
{
public bool CanUseForceMode => false;
public void Authorize(string? password) =>
throw new InvalidOperationException("Force is not available in the product application.");
}
2.1. Команда list-hasp
Консоль:
MyApp list-hasp
Без DI:
using LicensingClient.Hasp;
using LicensingClient.Secrets;
using Microsoft.Extensions.Configuration;
if (args.Length > 0 && string.Equals(args[0], "list-hasp", StringComparison.OrdinalIgnoreCase))
{
ILicensingSecrets secrets = new MyProductSecrets();
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false)
.Build();
var manager = new LocalHaspKeyManager(secrets, configuration);
var ids = await manager.ListHaspIdsAsync();
if (ids.Count == 0)
{
Console.WriteLine("No HASP keys found.");
return 0;
}
Console.WriteLine("Connected HASP keys:");
foreach (var id in ids)
Console.WriteLine(id);
return 0;
}
Через DI:
using LicensingClient.Extensions;
using LicensingClient.Hasp;
using LicensingClient.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
if (args.Length > 0 && string.Equals(args[0], "list-hasp", StringComparison.OrdinalIgnoreCase))
{
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false)
.Build();
var services = new ServiceCollection();
services.AddHaspRuntime(configuration); // или AddHaspFirstRun
services.AddSingleton<ILicensingSecrets, MyProductSecrets>();
await using var sp = services.BuildServiceProvider();
var ids = await sp.GetRequiredService<IHaspKeyManager>().ListHaspIdsAsync();
foreach (var id in ids)
Console.WriteLine(id);
return 0;
}
2.2. Команда first-setup (прогрузка якоря)
Пишет якорь на HASP и создаёт:
| Файл | Куда |
|---|---|
<out>/license/client_lm_payload.hasp.bin |
в LM (регистрация HASP). Клиенту лучше его не оставлять |
<out>/data/public_rsa_der.aesc.bin |
в продукт и в LM (вместе с payload) |
Консоль:
MyApp first-setup --id <hasp_id> [--out <корень>]
Без DI:
using LicensingClient.Hasp;
using LicensingClient.Secrets;
using LicensingClient.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
if (args.Length > 0 && string.Equals(args[0], "first-setup", StringComparison.OrdinalIgnoreCase))
{
string? haspId = null;
var outRoot = AppContext.BaseDirectory;
for (var i = 1; i < args.Length; i++)
{
if (args[i] is "--id" && i + 1 < args.Length) { haspId = args[++i]; continue; }
if (args[i] is "--out" && i + 1 < args.Length) { outRoot = args[++i]; continue; }
}
if (string.IsNullOrWhiteSpace(haspId))
{
Console.Error.WriteLine("Usage: first-setup --id <hasp_id> [--out <dir>]");
return 1;
}
ILicensingSecrets secrets = new MyProductSecrets();
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false)
.Build();
using var loggerFactory = LoggerFactory.Create(b => b.AddConsole());
using var hasp = new LocalSentinelHaspRuntime(
secrets, configuration, loggerFactory.CreateLogger<LocalSentinelHaspRuntime>());
var firstLaunch = new LocalHaspFirstLaunchService(hasp, new DenyForceFirstRunAuthorizer());
await firstLaunch.ExecuteAsync(outRoot, new FirstRunExecutionOptions(ExpectedHaspId: haspId));
return 0;
}
Через DI:
using LicensingClient.Extensions;
using LicensingClient.Secrets;
using LicensingClient.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
if (/* разобрали first-setup --id … --out … как выше */)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false)
.Build();
var services = new ServiceCollection();
services.AddLogging(b => b.AddConsole());
services.AddHaspFirstRun(configuration);
services.AddSingleton<ILicensingSecrets, MyProductSecrets>();
await using var sp = services.BuildServiceProvider();
await sp.GetRequiredService<IFirstLaunchService>().ExecuteAsync(
outRoot,
new FirstRunExecutionOptions(ExpectedHaspId: haspId));
return 0;
}
Якорь на ключе один. Для нового продукта на том же HASP не делайте повторный first-setup / force — сломаете уже выданные лицензии.
3. Local: выпуск лицензии в LM
- В LM зарегистрировать HASP: оба файла —
client_lm_payload.hasp.bin+public_rsa_der.aesc.bin. - Выпустить лицензию продукта → файл
.lic. product_codeв.licдолжен совпасть сProductCodeвILicensingSecrets.client_idиз лицензии →Licensing:ClientCodeв продукте.
Старые профили HASP без publicKey в БД: в UI LM — догрузить файл или восстановить из CRT + AesC.
4. Local: подключение лицензии в приложении
4.1. appsettings
{
"Hasp": { "Runtime": "Local" },
"Licensing": {
"LicenseFilePath": "licenses/product.lic",
"WrappedPublicRsaPath": "data/public_rsa_der.aesc.bin",
"ClientCode": "<client_id из лицензии>"
}
}
ClientCode обязателен (сверяется с client_id в .lic).
4.2. Проверка без DI
using LicensingClient.Hasp;
using LicensingClient.Licensing;
using LicensingClient.Secrets;
using LicensingClient.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
ILicensingSecrets secrets = new MyProductSecrets();
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false)
.Build();
var licensingOptions = Options.Create(new LicensingOptions
{
LicenseFilePath = configuration["Licensing:LicenseFilePath"] ?? "",
WrappedPublicRsaPath = configuration["Licensing:WrappedPublicRsaPath"] ?? "",
ClientCode = configuration["Licensing:ClientCode"] ?? ""
});
ILicenseVerificationPolicy policy = ProductLicenseVerificationPolicy.CreateDefaultFromEmbedded();
var snapshot = new LicenseRuntimeSnapshot();
using var loggerFactory = LoggerFactory.Create(b => b.AddConsole());
using var hasp = new LocalSentinelHaspRuntime(
secrets, configuration, loggerFactory.CreateLogger<LocalSentinelHaspRuntime>());
var verifier = new LicenseVerificationService(
hasp,
secrets,
licensingOptions,
policy,
snapshot,
loggerFactory.CreateLogger<LicenseVerificationService>());
var report = await ((IManualLicenseVerificationService)verifier).VerifyWithReportAsync();
if (!report.IsValid) { /* DEMO / отказ */ return; }
if (report.Parameters.TryGetValue("max_users", out var maxUsersText)
&& int.TryParse(maxUsersText, out var maxUsers))
{
// применить
}
4.3. Регистрация в DI и проверка
Подготовка (Program / startup):
using LicensingClient.Extensions;
using LicensingClient.Secrets;
using Microsoft.Extensions.DependencyInjection;
services.AddLicenseVerification(configuration);
services.AddSingleton<ILicensingSecrets, MyProductSecrets>();
Вручную (кнопка):
var verifier = sp.GetRequiredService<IManualLicenseVerificationService>();
var report = await verifier.VerifyWithReportAsync();
if (!report.IsValid) { /* DEMO */ return; }
foreach (var (key, value) in report.Parameters) { /* ... */ }
Фон (hosted snapshot): AddLicenseVerification уже регистрирует LicenseReverificationHostedService. После старта host:
var snapshot = sp.GetRequiredService<ILicenseRuntimeSnapshot>();
if (!snapshot.IsLicenseValid || snapshot.Payload is null) { /* DEMO */ return; }
var parameters = snapshot.Payload.parameters;
RemoveThisProductAsync() в Local — no-op (только лог).
5. Net (Server_LM)
Продукт без локального HASP и без .lic. Слоты и entitlement — на Server_LM.
5.1. appsettings
{
"Hasp": {
"Runtime": "Net",
"Server": "http://192.168.1.10:5088"
},
"Licensing": {
}
}
| Поле | Нужно? |
|---|---|
Hasp:Server |
да — base URL Server_LM (без / в конце) |
Licensing:ClientCode |
не нужен в Net |
LicenseFilePath / WrappedPublicRsaPath |
не используются |
ProductCode по-прежнему из ILicensingSecrets (должен совпасть с entitlement на Server_LM).
Перед register entitlement продукта должен уже быть импортирован на Server_LM.
5.2. Общая подготовка DI для Net CLI и проверки
NetLicenseCli работает через IServiceProvider (AddLicenseVerification).
using LicensingClient.Extensions;
using LicensingClient.Net;
using LicensingClient.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false)
.Build();
var services = new ServiceCollection();
services.AddLogging(b => b.AddConsole());
services.AddLicenseVerification(configuration);
services.AddSingleton<ILicensingSecrets, MyProductSecrets>();
await using var sp = services.BuildServiceProvider();
Ниже для каждой команды: консоль → ветка в Main через NetLicenseCli → прямой вызов INetRegistrationService.
Net-путь рассчитан на контейнер (AddLicenseVerification). «Без DI» в смысле без Host/Generic Host — это тот же короткий ServiceCollection в Main (см. §5.2), не ручная сборка HttpClient + fingerprint.
5.3. register <comment>
Консоль:
MyApp register "Офис 1"
Ветка в Main (NetLicenseCli):
// args уже ["register", "Офис 1"] или весь argv
var (handled, exit) = await NetLicenseCli.TryHandleAsync(args, sp);
if (handled) return exit;
Прямой вызов сервиса:
var reg = sp.GetRequiredService<INetRegistrationService>();
var created = await reg.RegisterAsync("Офис 1");
Console.WriteLine($"OK slotId={created.SlotId}");
5.4. register info
Консоль:
MyApp register info
NetLicenseCli: TryHandleAsync с args = ["register", "info"].
Сервис:
var tree = await sp.GetRequiredService<INetRegistrationService>().InfoAsync();
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(tree, new() { WriteIndented = true }));
5.5. register force <slotId> <comment>
Замена fingerprint / снятие block (с cooldown из entitlement). Выполнять с новой машины, которая займёт слот.
Консоль:
MyApp register force 09828fe745784a919d5b48433884f31e "Офис 1 (новый ПК)"
Сервис:
var forced = await sp.GetRequiredService<INetRegistrationService>()
.ForceAsync(slotId, "Офис 1 (новый ПК)");
5.6. unregister
Снимает слот только текущего экземпляра (fingerprint этой машины). Удалённо по slotId — нельзя.
Консоль:
MyApp unregister
Сервис:
await sp.GetRequiredService<INetRegistrationService>().UnregisterAsync();
5.7. Проверка лицензии (Net)
Тот же AddLicenseVerification + ILicensingSecrets. При Runtime=Net внутри — auth/refresh/validate к Server_LM; refresh-файл lm_net_refresh.bin рядом с exe.
Вручную:
var verifier = sp.GetRequiredService<IManualLicenseVerificationService>();
var report = await verifier.VerifyWithReportAsync();
Snapshot / hosted: как в §4.3 (ILicenseRuntimeSnapshot).
5.8. RemoveThisProductAsync (Net)
Удаляет entitlement + слоты этого продукта на Server_LM. Нужен живой access после verify.
Proof: SHA256(productCode|clientUtc|accessToken) — без ClientCode. Случайный вызов без сессии не пройдёт: нужен Bearer + совпадение proof с тем же access token (±5 мин по clientUtc).
await sp.GetRequiredService<ILicenseVerificationService>().RemoveThisProductAsync();
Local: no-op.
Краткая схема
Local:
HaspManager | MyApp list-hasp / first-setup
→ LM: payload + publicKey → .lic
→ MyApp: LicenseFilePath + ClientCode + Verify
Net:
Server_LM + entitlement
→ MyApp: Runtime=Net, Hasp:Server
→ register / force / unregister / Verify / RemoveThisProductAsync
No packages depend on LicenseManager.LicensingClient.
.NET 9.0
- BouncyCastle.Cryptography (>= 2.6.1)
- Microsoft.Extensions.Configuration (>= 9.0.0)
- Microsoft.Extensions.Configuration.Json (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection (>= 9.0.0)
- Microsoft.Extensions.Hosting (>= 9.0.0)
- Microsoft.Extensions.Http (>= 9.0.0)
- Microsoft.Extensions.Logging (>= 9.0.0)
- Microsoft.Extensions.Logging.Console (>= 9.0.0)
- Microsoft.Win32.Registry (>= 5.0.0)
| Version | Downloads | Last updated |
|---|---|---|
| 1.1.0 | 41 | 03.09.2026 |
| 1.0.15 | 24 | 06.08.2026 |
| 1.0.14 | 3 | 06.08.2026 |
| 1.0.13 | 12 | 24.07.2026 |
| 1.0.11 | 4 | 24.07.2026 |
| 1.0.10 | 4 | 24.07.2026 |
| 1.0.9 | 5 | 24.07.2026 |
| 1.0.8 | 5 | 23.07.2026 |
| 1.0.7 | 4 | 23.07.2026 |
| 1.0.6 | 13 | 16.07.2026 |
| 1.0.5 | 8 | 16.07.2026 |
| 1.0.4 | 12 | 15.07.2026 |
| 1.0.3 | 5 | 15.07.2026 |
| 1.0.2 | 5 | 15.07.2026 |