Enigma.Licensing 2.0.0

dotnet add package Enigma.Licensing --version 2.0.0
                    
NuGet\Install-Package Enigma.Licensing -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Enigma.Licensing" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Enigma.Licensing" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Enigma.Licensing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Enigma.Licensing --version 2.0.0
                    
#r "nuget: Enigma.Licensing, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Enigma.Licensing@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Enigma.Licensing&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Enigma.Licensing&version=2.0.0
                    
Install as a Cake Tool

Enigma.Licensing

NuGet License: MIT

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 formatenigma-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/LoadAsync move one over any stream.
  • Keysenigma-key/1, one format for both algorithm families and both halves of a pair. IKeyService generates, saves and loads them, optionally encrypting the private half with Argon2id + AES-256-GCM. Both halves share a keyId fingerprint, 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 algorithm field, so rewriting it makes the signature fail rather than downgrade.
  • Generating licensesILicenseGenerator signs a LicenseRequest carrying 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 licensesILicenseValidator runs a fixed, documented check order and reports the first failing step as one of seven LicenseFailure reasons, 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. ILicenseStore holds several licenses and re-validates on every query, caching nothing.
  • Device identityDeviceIdentifier.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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 98 9/11/2026
1.0.0 95 9/10/2026

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.