Skip to content
← All posts
· Delta1 Labs LicensingSecurityGuide

License Revocation: Making a Key Stop Working, Online and Offline

Issuing a license is easy; making one stop working after a refund, a leak or a chargeback is the harder half. Here is how revocation actually works — online check-in that fails closed, signed revocation lists that reach offline clients, and how to bound the exposure window you can never fully close.

Every licensing tutorial teaches issuance: generate a key, sign it, hand it over. Far fewer teach the other half, which is where the real engineering is — making a key stop working. A customer refunds. A key turns up in a forum. A chargeback lands. An employee with the company key leaves. In each case you need a key that already exists, sitting on someone else’s machine, to stop granting your software. That is revocation, and it behaves very differently online and offline.

What revocation is — and is not

Revoking a license is a signed negative statement: the vendor declares key K no longer valid. It does not reach out and delete anything from the customer’s disk; it changes what your app concludes the next time it evaluates that key. So the whole problem reduces to one question: when does the app next re-evaluate, and does it hear about the revocation in time?

That framing matters because it sets the honest expectation. Revocation is never instantaneous everywhere. What you control is the window between revoking and the key going dead on a given machine. Your job is to make that window small enough for the risk, and to never pretend it is zero.

Online revocation: fail closed on the next check-in

The common case is a connected app. You mark the key revoked server-side, and the next time the client talks to the service — a re-validation, a re-activation, a heartbeat — the service answers “revoked” and the client fails closed to the unlicensed state.

var info = await client.ValidateOnlineAsync(key);
if (info.Status == LicenseStatus.Revoked)
{
    _cache.Clear();          // drop any cached lease so offline validate can't resurrect it
    EnterUnlicensedMode();   // fail closed — never throw and leave features on
}

The exposure window here is your recheck interval. A node-locked lease is typically cached so the app works offline between checks (you do not want to phone home on every launch); that same cache is what delays a revocation. If the lease is valid for 24 hours, a revoked key keeps working for up to 24 hours on that machine. Shorten the lease to shrink the window — at the cost of more frequent connectivity requirements. That trade-off is the design decision: tie the lease lifetime to how much exposure a leaked or refunded key actually costs you.

One caution: when you detect revocation, clear the cached lease. Otherwise an app that goes offline right after revocation can keep validating against the stale cached lease until it expires.

The offline problem and the signed revocation list

A fully offline or air-gapped client never checks in, so “revoked on next check-in” never fires. The answer is to bring the revocation to the client as data it can trust without a network: a signed revocation list (a CRL, in the classic sense). The vendor publishes the set of revoked key IDs, signs that list with the same private key that signs licenses, and distributes it — bundled with a software update, pushed through a management tool, or carried in on a file for truly isolated systems.

// The list is signed; verify it against the embedded public key before trusting a single entry.
RevocationList crl = RevocationList.Load(path);
if (!crl.VerifySignature(publicKey))
    throw new SecurityException("Revocation list signature invalid — ignoring it.");

if (crl.Contains(incomingLease.KeyId))
    EnterUnlicensedMode();   // known-bad key, even with no network

The signature is the crux: the client must accept the list offline, so the list has to carry its own proof of authenticity, exactly like the license does. An attacker who could feed a forged or truncated list would otherwise just delete their own revocation. Because it is signed, a stripped or altered list fails verification and is ignored.

The latency here is your distribution cadence — the list reaches a machine when the next update does. That is slower than online, but it means an isolated install is not permanently defenceless against a refunded or leaked key.

Vendor revokeskey marked invalidOnline: next check-infails closed within recheck intervalOffline: signed CRLarrives with an updateApp fails closedunlicensed mode

After revocation: re-issuing for the legitimate case

Not every revocation is adversarial. A customer who charged back by mistake, or whose key leaked through no fault of their own, still deserves to run your software. So pair revocation with clean re-issuance: revoke the compromised key, issue a fresh one, and let the customer activate it. Because the old key is on the revocation list and the new one is not, the two coexist correctly — the leaked key is dead everywhere it propagates, the new key works. Avoid the temptation to “un-revoke”; a revoked key should stay revoked forever, and the customer moves to a new credential.

Designing the window on purpose

Revocation forces one explicit decision: how long may a revoked key keep working on each machine? Online, that is the lease/recheck interval; offline, it is the update cadence plus the CRL. Set both from risk, not habit. A high-value enterprise seat that could be resold justifies short leases and frequent CRL updates; a cheap single-user tool can tolerate a day. The mistake is leaving it implicit — shipping a 30-day cached lease and then being surprised a refunded key still works four weeks later.

Issuance gets a license into the world. Revocation is how you keep authority over it afterward — and a licensing system that can do one but not the other only looks finished.

Try Nebula.NET

Harden your .NET code in minutes — start with the free edition.