Enigma.LicenseManager
1.2.0
dotnet add package Enigma.LicenseManager --version 1.2.0
NuGet\Install-Package Enigma.LicenseManager -Version 1.2.0
<PackageReference Include="Enigma.LicenseManager" Version="1.2.0" />
<PackageVersion Include="Enigma.LicenseManager" Version="1.2.0" />
<PackageReference Include="Enigma.LicenseManager" />
paket add Enigma.LicenseManager --version 1.2.0
#r "nuget: Enigma.LicenseManager, 1.2.0"
#:package Enigma.LicenseManager@1.2.0
#addin nuget:?package=Enigma.LicenseManager&version=1.2.0
#tool nuget:?package=Enigma.LicenseManager&version=1.2.0
Enigma.LicenseManager
Enigma.LicenseManager is a comprehensive .NET library designed for secure license management in applications. It provides robust cryptographic protection using both traditional RSA and modern ML-DSA (FIPS 204) digital signature algorithms, ensuring your software licensing is both secure and future-proof.
What's new in 1.2.0 — a production-readiness release:
Enigma.Cryptographyupgraded to 5.0.0, Central Package Management adopted, a non-Avalonia dependency refresh, the test suite migrated to xUnit v3, and build configuration consolidated. No public API or behavioural change — see the release notes.
Features
- Dual Cryptographic Support: Choose between RSA and ML-DSA (post-quantum) signatures
- Flexible License Management: Create, validate, and manage licenses with customizable properties
- Device Binding: Lock licenses to specific machines using hardware-derived device identifiers
- Cross-Platform Compatibility: Supports .NET Standard 2.0 and .NET 8.0+
- JSON Serialization: Easy license storage and distribution in JSON format
- Product Version Matching: Support for wildcard patterns in product IDs (e.g.,
MyApp 1.*) - Expiration Handling: Built-in support for time-based license expiration
- Thread-Safe Service:
LicenseServiceis fully thread-safe for concurrent access
Installation
dotnet add package Enigma.LicenseManager
License Creation
Create an RSA-Signed License
Generate a license with RSA digital signature for traditional cryptographic security:
await using var privateKeyFile = new FileStream("<YourKeyFile.pem>", FileMode.Open, FileAccess.Read);
var privateKey = PemUtils.LoadPrivateKey(privateKeyFile, "<KeyPassword>");
var license = new LicenseBuilder()
.SetProductId("MyApp 1.*")
.SetExpirationDate(DateTime.UtcNow.AddDays(365))
.SetOwner("Acme Corp")
.SignWithRsa(privateKey)
.Build();
Create an ML-DSA-Signed License
Generate a license with ML-DSA (post-quantum) signature for enhanced future security:
await using var privateKeyFile = new FileStream("<YourKeyFile.pem>", FileMode.Open, FileAccess.Read);
var privateKey = PemUtils.LoadPrivateKey(privateKeyFile, "<KeyPassword>");
var license = new LicenseBuilder()
.SetProductId("MyApp")
.SignWithMlDsa(privateKey)
.Build();
Create a Device-Bound License
Bind a license to a specific machine so it cannot be used elsewhere:
// On the target machine, generate the device ID
var deviceId = LicenseUtils.GenerateDeviceId();
// When creating the license, bind it to that device
var license = new LicenseBuilder()
.SetProductId("MyApp 1.*")
.SetDeviceId(deviceId)
.SetExpirationDate(DateTime.UtcNow.AddDays(365))
.SignWithRsa(privateKey)
.Build();
Iddefaults to a ULID andCreationDatedefaults toDateTime.UtcNowwhen not explicitly set.
License Persistence
Save License to JSON
Export your generated license to a JSON file for distribution:
await using var fs = new FileStream("<DestinationPath>", FileMode.Create, FileAccess.Write);
await license.SaveAsync(fs);
Load License from JSON
Import a license from a JSON file for validation:
await using var fs = new FileStream("<LicenseFilePath>", FileMode.Open, FileAccess.Read);
var license = await License.LoadAsync(fs);
Both SaveAsync and LoadAsync accept an optional CancellationToken parameter.
License Validation
Verify a Single License
Validate a license against its public key. IsValid returns a (bool, string?) tuple with an error message on failure:
await using var publicKeyFile = new FileStream("<YourKeyFile.pem>", FileMode.Open, FileAccess.Read);
var publicKey = PemUtils.LoadKey(publicKeyFile);
var service = new LicenseService();
var (isValid, errorMessage) = service.IsValid(license, publicKey, "MyApp 1.0");
if (!isValid)
Console.WriteLine($"License rejected: {errorMessage}");
For device-bound licenses, pass the device ID:
var deviceId = LicenseUtils.GenerateDeviceId();
var (isValid, errorMessage) = service.IsValid(license, publicKey, "MyApp 1.0", deviceId);
Using LicenseService
LicenseService maintains an in-memory, thread-safe collection of licenses. Register licenses once, then check validity by product ID throughout your application:
var service = new LicenseService();
// Register licenses with their public keys
service.AddLicense(license, publicKey);
// Check if any registered license is valid for a product
if (service.HasValidLicense("MyApp 1.0"))
{
// Feature is licensed
}
// For device-bound licenses
var deviceId = LicenseUtils.GenerateDeviceId();
if (service.HasValidLicense("MyApp 1.0", deviceId))
{
// Feature is licensed for this device
}
// Remove a license when no longer needed
service.RemoveLicense(license);
Tooling
Desktop app
Enigma License Manager is an Avalonia desktop application (src/Enigma.LicenseManager.Desktop/) for working with keys and licenses through a GUI, without writing code. It provides three pages:
- Generate Keys — generate an RSA (2048/3072/4096/8192) or ML-DSA (level 87) key pair and save the public / private keys to PEM, optionally encrypting the private key with a password.
- Generate Licenses — build and sign a license (product ID, owner, optional device binding and expiration) with an RSA or ML-DSA private key, and save it as JSON. Reusable profiles save/load the form fields to a
.jsonfile (the signing key password is never stored). - Validate Licenses — validate a license file against a public key, optionally constraining the product ID and device ID.
A runtime light / dark theme toggle is persisted per user. Run it with:
dotnet run --project src/Enigma.LicenseManager.Desktop
The GUI's key/license operations are backed by the shared Enigma.LicenseManager.Tools library (src/Enigma.LicenseManager.Tools/), which wraps the core library's signing/verification with key generation, PEM I/O, and RSA-vs-ML-DSA dispatch behind DI-registered services (AddLicenseTools()).
Command-line tool
enigma-license (src/Enigma.LicenseManager.Cli/) runs the same key and license operations as the desktop app from a terminal or CI pipeline. Run it with:
dotnet run --project src/Enigma.LicenseManager.Cli -- <command>
The examples below use enigma-license as shorthand for dotnet run --project src/Enigma.LicenseManager.Cli --.
Full round-trip — generate a key pair, sign a license, validate it:
mkdir -p keys
# 1. Generate an RSA key pair (private key encrypted with a password)
enigma-license keygen --algorithm rsa --rsa-size 3072 \
--public keys/public.pem --private keys/private.pem --password s3cret
# 2. Sign a license with the private key
enigma-license license generate --product-id "MyApp 1.*" --owner "Acme Corp" \
--expires 2027-01-31 --algorithm rsa --key keys/private.pem --password s3cret \
--out app.license.json
# 3. Validate it with the public key
enigma-license license validate --license app.license.json \
--public-key keys/public.pem --product-id "MyApp 1.2.3"
# License is VALID.
keygen writes to the paths you give it and does not create parent directories, so create the output directory first (mkdir -p keys) or use paths in the current directory.
Generate keys
mkdir -p keys
# RSA key pair (--rsa-size is one of 2048/3072/4096/8192, default 3072; RSA only)
enigma-license keygen --algorithm rsa --rsa-size 4096 \
--public keys/public.pem --private keys/private.pem
# ML-DSA key pair (post-quantum, fixed to level 87)
enigma-license keygen --algorithm ml-dsa \
--public keys/public.pem --private keys/private.pem
# Encrypt the private key with a password
enigma-license keygen --algorithm rsa \
--public keys/public.pem --private keys/private.pem --password s3cret
Generate a license
enigma-license license generate \
--product-id "MyApp 1.*" \
--owner "Acme Corp" \
--device-id "abc123" \
--expires 2027-01-31 \
--algorithm rsa --key keys/private.pem --password s3cret \
--out app.license.json
--owner, --device-id and --expires are optional; omitting --expires means the license never expires. --algorithm must match the private key. Drop --password for an unencrypted key.
Validate a license
# Valid — product and device match → exit 0
enigma-license license validate \
--license app.license.json --public-key keys/public.pem \
--product-id "MyApp 1.2.3" --device-id "abc123"
# License is VALID.
# Invalid — product id mismatch → exit 1
enigma-license license validate \
--license app.license.json --public-key keys/public.pem \
--product-id "OtherApp 1.0"
# License is INVALID: Product id mismatch. (License productId: MyApp 1.*, requested productId: OtherApp 1.0)
--product-id and --device-id are optional; omit --product-id to validate against the license's own product id. A wrong public key or a device-id mismatch is also reported as invalid (exit 1).
Exit codes
| Code | Meaning |
|---|---|
0 |
Success / license valid |
1 |
License invalid |
2 |
Operation error (bad password, missing file — message on stderr) |
Exit codes make the tool scriptable in CI: enigma-license license validate ... && ./deploy.sh.
Every command supports --help; enigma-license --version prints the tool version.
Project Structure
| Path | Description |
|---|---|
src/Enigma.LicenseManager/ |
Core library |
src/Enigma.LicenseManager.Tools/ |
Shared key / license operations (used by the desktop app) |
src/Enigma.LicenseManager.Desktop/ |
Avalonia desktop application |
src/Enigma.LicenseManager.Cli/ |
Command-line tool (enigma-license) |
src/UnitTests/ |
Unit tests |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- DeviceId (>= 6.11.0)
- Enigma.Cryptography (>= 5.0.0)
- Newtonsoft.Json (>= 13.0.4)
- Ulid (>= 1.4.1)
-
net8.0
- DeviceId (>= 6.11.0)
- Enigma.Cryptography (>= 5.0.0)
- Newtonsoft.Json (>= 13.0.4)
- Ulid (>= 1.4.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.2.0 — production-readiness release. Highlights: Enigma.Cryptography upgraded to 5.0.0, Central Package Management adopted, a non-Avalonia dependency refresh, the test suite migrated to xUnit v3, and build settings consolidated into Directory.Build.props + .editorconfig. No public API or behavioural change — a recompile-and-verify upgrade; licenses and keys created with earlier 1.x releases remain compatible. See RELEASENOTES.md for the full details.