DEV Community

Zero Heartbeat
Zero Heartbeat

Posted on Edited on Originally published at delta1labs.com

Offline and air-gapped licensing in .NET: validating without a network

Plenty of licensing guides assume a happy path: the app starts, calls the licensing service, gets a yes or no, and moves on. That path does not exist for a large slice of real customers. A bank runs your tool on a segment with no outbound internet. A defence contractor runs it on an air-gapped network by policy. A factory line runs it on a box that is offline more often than not. If your licensing breaks the moment the network does, you have not shipped licensing — you have shipped an outage with a EULA.

Keyright is built so the common case — validation — needs no network at all. Here is how offline validation actually works, what makes it safe, and where the honest limits are.

The core idea: the license is the proof

An online check asks a server "is this key still valid?" and trusts the answer. An offline check cannot ask anyone, so the license itself has to carry everything needed to verify it — and it has to be impossible to forge. Keyright issues a license as a signed document: a compact payload of claims plus a signature produced with the issuer's private key. Your application ships only the matching public key and verifies the signature locally.

The payload is a small, versioned claim set — who it is for, what it unlocks, and when it stops being valid:

{
  "lic": "3f6a…",
  "product": "contoso-cad",
  "edition": "Pro",
  "entitlements": ["export.step", "batch", "api"],
  "machine": "9c1e…b40",
  "issuedUtc": "2026-11-01T00:00:00Z",
  "notAfterUtc": "2027-11-01T00:00:00Z",
  "v": 2
}
Enter fullscreen mode Exit fullscreen mode

That JSON is canonicalised, signed with Ed25519, and handed to you as a single opaque string (payload and signature, base64url, joined by a dot). Nothing in it is secret — it is the signature that makes it trustworthy, not obscurity.

Verifying it: a few milliseconds, zero sockets

The SDK verifies in three steps, none of which touch the network:

var client = new KeyrightClient(new KeyrightOptions
{
    Product        = "contoso-cad",
    PublicKeyBase64 = "MCowBQYDK2VwAyEA…",   // your issuer's public key, shipped in-app
    // No ServiceUrl needed for offline validation.
});

// licenseText came from a file, a USB stick, an email — it does not matter.
LicenseResult result = client.ValidateOffline(licenseText);

if (!result.IsValid)
    throw new LicenseException(result.Reason);   // BadSignature, Expired, WrongProduct, WrongMachine

if (!result.Has("export.step"))
    DisableStepExport();
Enter fullscreen mode Exit fullscreen mode

Under the hood ValidateOffline does exactly what you would do by hand, in order:

  1. Split and decode the token into payload bytes and signature bytes.
  2. Verify the signature over the canonical payload with the embedded public key. Ed25519 verification is a single call and is constant-time in the library; a wrong signature fails here and nothing else runs.
  3. Check the claims — product matches, notAfterUtc is in the future, the machine binding matches this host, and the schema version v is one this SDK understands.
// Step 2, conceptually — the part that cannot be faked without the private key.
static bool VerifySignature(ReadOnlySpan<byte> payload, ReadOnlySpan<byte> sig, byte[] pubKey)
{
    using var algorithm = SignatureAlgorithm.Ed25519;      // libsodium-backed
    var key = PublicKey.Import(algorithm, pubKey, KeyBlobFormat.PkixPublicKey);
    return algorithm.Verify(key, payload, sig);
}
Enter fullscreen mode Exit fullscreen mode

Because the private key never leaves the issuing service, a customer with the public key can verify a license but can never mint one. Editing the payload — bumping notAfterUtc, adding an entitlement — invalidates the signature, and step 2 rejects it before any claim is read.

Binding to a machine without phoning home

Offline licensing is only meaningful if a license cannot be copied to a thousand machines. Keyright binds a license to a host fingerprint computed from stable hardware and OS identifiers, hashed so the raw values never travel:

string fingerprint = MachineIdentity.Compute();   // salted SHA-256 of stable identifiers
// Issued license carries machine = that hash; ValidateOffline recomputes and compares.
Enter fullscreen mode Exit fullscreen mode

The fingerprint is deliberately tolerant — it survives a RAM upgrade or a new monitor, but not being cloned onto a different box. For air-gapped activation the flow is a clean two-step handshake with no live connection: the machine emits a request file containing its fingerprint, that file crosses the air gap on removable media, the issuer produces a license bound to exactly that fingerprint, and the license file comes back the same way. No socket is ever opened on the secured network.

The two problems offline licensing has to answer

Going offline removes the server, and with it two things the server used to provide. Pretending otherwise is how offline licensing gets a bad name.

The clock. With no server, expiry is enforced against the local clock — and the local clock is attacker-controlled. Rolling it back could extend an expired license forever. Keyright's SDK keeps a small tamper-evident high-water mark: it records the latest timestamp it has trustworthily seen (from signed artifacts and its own monotonic store) and treats a system clock that jumps backward across that mark as tampering, failing closed rather than silently granting time. It is not a perfect clock — nothing on the client is — but it turns "set the date back" from a trivial bypass into a real effort.

Revocation. An online model can kill a leaked license in seconds. A fully offline model cannot, by definition — you cannot push a revocation to a machine you cannot reach. The honest mitigation is short-lived offline licenses: issue them with a bounded notAfterUtc so a leak self-limits, and re-issue on each air-gap cycle. Customers who can tolerate occasional connectivity get the best of both worlds with a lease — a license that validates offline for days but refreshes online when it can, so revocation still lands within the lease window.

Entitlements travel with the license

Because entitlements are claims inside the signed payload, feature-gating works identically offline and online — there is no second call to "load permissions". A Pro license and an Enterprise license differ only in the entitlements array and whatever edition-scoped limits you read from it; the verification path is the same bytes either way. That is what lets the same SDK call gate a feature on a developer's laptop and on a sealed industrial controller without a line of difference in your code.

When to reach for it

Fully offline validation is the right default for anything a customer might run disconnected, and the only option for air-gapped and regulated environments. If your customers are always online, prefer a lease: you keep fast revocation and still survive a flaky network. Most real deployments want both, and Keyright lets you set the policy per product rather than baking it into your app.

The guiding principle is simple: validation should degrade gracefully, enforcement should fail closed. A dropped network is not a licensing decision, and an expired or forged license is not a reason to crash — it is a reason to disable the gated feature and tell the user exactly why.

Wire it into your product in a few lines: install the SDK, ship your issuer's public key, and call ValidateOffline on startup. See the Keyright getting-started guide for the full offline and air-gapped walkthrough.

Top comments (0)