Enigma.Licensing
2.0.0
dotnet add package Enigma.Licensing --version 2.0.0
NuGet\Install-Package Enigma.Licensing -Version 2.0.0
<PackageReference Include="Enigma.Licensing" Version="2.0.0" />
<PackageVersion Include="Enigma.Licensing" Version="2.0.0" />
<PackageReference Include="Enigma.Licensing" />
paket add Enigma.Licensing --version 2.0.0
#r "nuget: Enigma.Licensing, 2.0.0"
#:package Enigma.Licensing@2.0.0
#addin nuget:?package=Enigma.Licensing&version=2.0.0
#tool nuget:?package=Enigma.Licensing&version=2.0.0
Enigma.Licensing
Enigma.Licensing generates signed software licenses and checks them. A license is a small JSON
envelope whose signature covers the stored payload bytes exactly — there is no canonicalization
step, no "build a string to sign" helper, and therefore no way for a signer and a verifier to
disagree about what was signed. That single idea shapes the whole surface: you sign a
LicenseRequest into a LicenseEnvelope with a private key, ship the public half inside your
application, and ask the validator for a verdict. Signing is classical RSA or post-quantum ML-DSA,
and all cryptography comes from Enigma.Core — whose
types never reach this library's public surface.
What's new in 2.0 — a breaking release: feature flags and claims are removed, since validation never read them. Every license issued by 1.0 still validates, and no key or license file changes. See RELEASENOTES.md.
Features
- License format —
enigma-license/1, a five-field JSON envelope carrying the format, the algorithm, the signing key's fingerprint, the base64 payload and its signature.ReadLicense()inspects an untrusted document without verifying it;SaveAsync/LoadAsyncmove one over any stream. - Keys —
enigma-key/1, one format for both algorithm families and both halves of a pair.IKeyServicegenerates, saves and loads them, optionally encrypting the private half with Argon2id + AES-256-GCM. Both halves share akeyIdfingerprint, which the generator stamps into every envelope it signs. - Signature schemes — RSA with SHA-256, SHA-384 or SHA-512 (2048–8192-bit moduli, 3072 by
default), and ML-DSA-44/65/87 (FIPS 204) for post-quantum signatures. The verifier is chosen from
the envelope's own
algorithmfield, so rewriting it makes the signature fail rather than downgrade. - Generating licenses —
ILicenseGeneratorsigns aLicenseRequestcarrying a product identifier, an owner, an optional validity window and an optional device binding. Identifier and creation time are defaulted per call; the one-call path overload loads the key, signs and writes the file. - Validating licenses —
ILicenseValidatorruns a fixed, documented check order and reports the first failing step as one of sevenLicenseFailurereasons, so an expired license and a forged one are never confused. Nothing a license can be makes it throw. Product identifiers may carry an anchored*wildcard, so one license can cover a version range.ILicenseStoreholds several licenses and re-validates on every query, caching nothing. - Device identity —
DeviceIdentifier.Generate()derives a best-effort machine identifier from the machine name and OS version, hashed so neither is readable in a license file. Any other scheme plugs in: the library only ever compares device identifiers ordinally.
Registration, trimming and AOT
All four services are sealed classes behind interfaces with a public parameterless constructor, so
new LicenseValidator() works anywhere; one call to AddEnigmaLicensing() registers all four as
singletons instead. Serialization is source-generated rather than reflection-based, and the library
is built with the trim and AOT analyzers enabled on its modern targets — so it stays usable in an
application published with PublishAot or with reflection-based JSON turned off.
Installation
dotnet add package Enigma.Licensing
Targets .NET Standard 2.0, .NET 8.0 and .NET 10.0; built on Enigma.Core 2.0.0.
Quick start
Generate a key pair, sign a license with the private half, and check it against the public half:
using System;
using System.Threading.Tasks;
using Enigma.Licensing;
using Enigma.Licensing.Keys;
// The issuer generates a pair once: the private half signs, the public half ships with the app.
var keyService = new KeyService();
LicensingKeyPair pair = keyService.GenerateKeyPair(LicenseSignatureAlgorithm.MLDsa65);
await keyService.SaveKeyPairAsync(pair, "signing.public.key.json", "signing.private.key.json");
// Sign a license. The path overload loads the key, signs and writes the file in one call.
LicenseEnvelope envelope = await new LicenseGenerator().GenerateAndSaveAsync(
new LicenseRequest { ProductId = "MyApp 2.0", Owner = "Acme GmbH" },
privateKeyPath: "signing.private.key.json",
password: null,
outputPath: "acme.license");
// The application checks it — a verdict, never an exception.
LicenseValidationResult result = await new LicenseValidator().ValidateAsync(
licensePath: "acme.license",
publicKeyPath: "signing.public.key.json",
productId: "MyApp 2.0");
Console.WriteLine(result.IsValid
? $"Licensed to {result.License?.Owner}, key {envelope.KeyId}."
: $"{result.Failure}: {result.Message}");
Documentation
Per-category guides — each with the supported algorithms and operations, the key types, and
copy-pasteable C# samples verified against the public API — live under docs/guides/ in the
repository, indexed by docs/guides/README.md. They cover the license format, keys, generating
licenses, validating licenses, and device identity. Every public member also carries XML
documentation, which ships inside the package.
Upgrading from 1.0? docs/migrations/1.0.0-to-2.0.0.md in the repository says what breaks, what
deliberately does not, and why no existing license needs reissuing.
Desktop application
The repository also contains Enigma.Licensing.Desktop, an Avalonia application for issuers: it generates key pairs, signs licenses from a form (with saveable profiles), and validates a license against a public key, reaching the same verdicts the library does. It is built on the library's public surface — exactly what a third-party consumer gets — and is distributed as a self-contained zip for Windows and Linux rather than through NuGet.
License
Enigma.Licensing is released under the MIT License.
| 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 is compatible. 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.Core (>= 2.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.18)
- System.Buffers (>= 4.6.1)
- System.Text.Json (>= 10.0.12)
- Ulid (>= 1.4.1)
-
net10.0
- DeviceId (>= 6.11.0)
- Enigma.Core (>= 2.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.18)
- Ulid (>= 1.4.1)
-
net8.0
- DeviceId (>= 6.11.0)
- Enigma.Core (>= 2.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.18)
- 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.
Breaking release. Feature flags and claims are removed: License.Features, License.Claims, License.HasFeature, LicenseRequest.Features and LicenseRequest.Claims are deleted outright — validation never read them, so they were signed, shown and documented with nothing ever deciding from them. Nothing else moved: both document formats are byte-identical, all six signature schemes behave as before, the check order and its seven failure reasons are unchanged, the error contract is unchanged, and the target frameworks are unchanged. Every licence issued by 1.0.0 still validates, and no file needs reissuing or converting — a payload member this build does not know is ignored rather than rejected, and nothing re-serializes a licence in order to check one. The break is therefore a compile error with no runtime component. System.Text.Json moves 10.0.10 to 10.0.12 on netstandard2.0 only. See docs/migrations/1.0.0-to-2.0.0.md for the migration, and RELEASENOTES.md for the full details.