Licencly 1.0.1
dotnet add package Licencly --version 1.0.1
NuGet\Install-Package Licencly -Version 1.0.1
<PackageReference Include="Licencly" Version="1.0.1" />
<PackageVersion Include="Licencly" Version="1.0.1" />
<PackageReference Include="Licencly" />
paket add Licencly --version 1.0.1
#r "nuget: Licencly, 1.0.1"
#:package Licencly@1.0.1
#addin nuget:?package=Licencly&version=1.0.1
#tool nuget:?package=Licencly&version=1.0.1
Licencly for .NET
Verify Licencly licenses and check for entitled updates, from .NET.
dotnet add package Licencly
Targets net8.0 and netstandard2.0, the latter deliberately, because a great many Windows desktop ISVs are still on .NET Framework 4.6.1+ with a codebase they are not going to port to run a licensing check.
The idea
The signed license file is the answer; the network is an optimisation. The client reads its cache, refreshes when due, and keeps working through an outage until the grace period is spent, so Licencly being unreachable never stops software you have already sold.
Quickstart
Copy the product UUID and public key from your dashboard and embed them at build time. Fetching keys at runtime would defeat the whole scheme.
using Licencly;
var client = new LicenclyClient(new LicenclyOptions
{
ProductUuid = "fec65576-…",
// Products → your product → Signing key.
PublicKeys = new Dictionary<string, string>
{
["9f2c1a44-…"] = "MWvD7YE6HjI/DQ0kYGJFNG4kXlx4hP6ck1Vr4j77fzk=",
},
Fingerprint = MachineId(), // yours; see below
});
var decision = await client.ValidateAsync(userEnteredKey);
switch (decision.Outcome)
{
case Outcome.Valid:
break; // run
case Outcome.NotActive:
Show("This license has been suspended or revoked.");
break;
case Outcome.Expired:
Show("This license expired. Renew to continue.");
break;
case Outcome.WrongMachine:
Show("This license is in use on another machine.");
break;
case Outcome.Stale:
Show("Please connect to the internet to re-check your license.");
break;
case Outcome.Invalid:
Show("This license file could not be verified.");
break;
}
ValidateAsync makes no network call at all while the cached file is still
fresh.
Offline
| When | Behaviour |
|---|---|
Before rev |
Cache only, no network |
Between rev and rev + grace |
Keeps working, refreshes in the background. NeedsRevalidation is true |
After rev + grace |
Outcome.Stale |
A LicenclyNetworkException inside the grace window is not an error your
users should see. The cached decision is returned instead, and you only see the
exception when there is no cache at all.
Where the cache lives
%APPDATA%\licencly\, not next to your executable. An application installed in
Program Files cannot write beside itself without elevation, and a licensing
check that needs admin rights will not have them when it matters.
Updates
var result = await client.CheckForUpdateAsync(key,
currentVersion: "1.2.0", platform: "windows", arch: "x64");
if (result.Available)
{
var path = await client.DownloadArtifactAsync(key, result.Release!, tempDir, artifactPublicKey);
}
else if (result.RenewalWouldUnlock)
{
// Not an error: a newer version exists and this license is not entitled to
// it. result.Latest names what renewing would unlock.
Show($"Version {result.Latest!.Version} is available with an active plan.");
}
Checking for updates never consumes a seat.
Verifying downloads
artifactPublicKey is your own Ed25519 public key, compiled into your
application, not fetched from Licencly. Licencly stores and serves the
signature but cannot produce one, so a compromise of Licencly cannot push code
to your users. DownloadArtifactAsync verifies the digest and that signature
before the file is moved into place; passing null reduces it to corruption
detection.
Machine fingerprints
The SDK does not compute one, because a good fingerprint is specific to what
you ship. It must stay stable across restarts, app updates and reboots, and
survive minor hardware change. On Windows, MachineGuid and a volume serial are
common starting points; both have failure modes worth reading about. VMs,
containers and cloned disks defeat naive approaches.
Leaving Fingerprint empty disables machine binding, and a license file copied
to another machine will still verify.
Clock tampering
Expiry is checked against a clock the user controls; rolling it back extends an expired license offline. This is unavoidable for anything that must work without a network.
The SDK records the furthest-forward time it has seen and rejects a jump backwards of more than 24 hours. That is a speed bump, not a fix.
Errors worth telling apart
| Type | Meaning |
|---|---|
LicenclyNetworkException |
Could not reach the server. Retryable |
LicenseFileException |
Signature or format failure. Never retry |
LicenclyApiException.SeatLimitReached |
No free activations |
LicenclyApiException.NotFound |
Unknown product or key |
LicenclyApiException.RateLimited |
Carries RetryAfter |
Dependencies
BouncyCastle.Cryptography, because .NET has no Ed25519 in the base class library on any target this package supports. The alternative is implementing field arithmetic by hand, which is the wrong answer for signature verification.
Conformance
dotnet test
Runs data/vectors.json, the same suite every Licencly SDK runs. If this SDK
ever disagrees with the Go, TypeScript or Python ones, that test fails first.
| 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
- BouncyCastle.Cryptography (>= 2.4.0)
- System.Text.Json (>= 8.0.6)
-
net8.0
- BouncyCastle.Cryptography (>= 2.4.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.