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

  1. В LM зарегистрировать HASP: оба файла — client_lm_payload.hasp.bin + public_rsa_der.aesc.bin.
  2. Выпустить лицензию продукта → файл .lic.
  3. product_code в .lic должен совпасть с ProductCode в ILicensingSecrets.
  4. 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.

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