DEV Community

Zero Heartbeat
Zero Heartbeat

Posted on Edited on Originally published at delta1labs.com

Rotating a licensing signing key without breaking the field

A licensing system signs every license it issues with a private key, and the application verifies with the matching public key. That asymmetry is the whole security model — covered in depth in our offline validation post. But it creates a problem people discover at the worst possible moment: what happens when the signing key has to change?

Keys have to change eventually. A key is suspected compromised. An employee with access to the signing material leaves. A compliance policy mandates rotation every N months. Your crypto library deprecates a curve. If your design has a single, permanent key baked into every shipped copy of your product, you are stuck: rotating it invalidates every license in the field overnight, because the public key your customers' apps carry no longer matches the key you now sign with. A key you can never rotate is not a security feature — it is a single point of failure with a long fuse.

Here is how Keyright makes rotation a routine operation instead of a catastrophe.

The client trusts a set of keys, not a key

The first design decision is the one that makes everything else possible: the SDK does not verify against a public key. It verifies against a small key set, each entry tagged with a key ID (kid). Every license carries, in its signed envelope, the kid of the key that signed it:

{
  "kid": "k3",
  "payload": "eyJsaWMiOiIzZjZh…",
  "sig": "W...base64url Ed25519 signature..."
}
Enter fullscreen mode Exit fullscreen mode

Verification becomes: read the kid, look up that key in the trusted set, verify the signature with it. A license signed by k2 and a license signed by k3 both validate on the same client, as long as both k2 and k3 are in the set it trusts:

var client = new KeyrightClient(new KeyrightOptions
{
    Product = "contoso-cad",
    // A set, each tagged with its kid — not a single key.
    TrustedKeys = new[]
    {
        new SigningKey("k2", "MCowBQYDK2VwAyEA…old…"),
        new SigningKey("k3", "MCowBQYDK2VwAyEA…new…"),
    },
});

LicenseResult r = client.ValidateOffline(licenseText);
Enter fullscreen mode Exit fullscreen mode

The verification path is a lookup, then the same Ed25519 check as always:

LicenseResult Verify(LicenseEnvelope env)
{
    if (!_trusted.TryGetValue(env.Kid, out var key))
        return LicenseResult.Fail(Reason.UnknownKey);   // kid not in the trusted set

    return Ed25519.Verify(key.PublicKey, env.PayloadBytes, env.Signature)
        ? LicenseResult.Ok(env)
        : LicenseResult.Fail(Reason.BadSignature);
}
Enter fullscreen mode Exit fullscreen mode

Because the kid is inside the signed payload envelope, an attacker cannot swap it to point at a different key without breaking the signature — the signature covers the kid too.

Rotation is three overlapping phases

With a key set in place, rotation stops being a cutover and becomes an overlap. There are always, briefly, two valid keys: the one being retired and the one taking over.

Phase 1 — introduce the new key as trusted, keep signing with the old. You generate k3, publish it into the trusted set shipped with your product (an app update, or a signed key-set fetch for connected clients), but keep signing with k2. Nothing changes for existing licenses; new clients simply now trust both. You wait until enough of the field trusts k3 — this is why you introduce it before you need it.

Phase 2 — switch signing to the new key. The issuer starts stamping new licenses with kid: k3. Old licenses (kid: k2) still verify everywhere because k2 is still in every trusted set. New licenses verify anywhere that completed Phase 1. Both keys are live; neither breaks anything.

Phase 3 — retire the old key. Once every license ever signed by k2 has expired (you know the longest validity you ever issued), k2 can be dropped from the trusted set in a future product update. Nothing in the field is still relying on it. The rotation is complete, and you are signing only with k3.

The critical property: the overlap window is at least as long as your longest-lived license. If you issue 1-year licenses, k2 must stay trusted (not signing — just trusted for verification) for a year after you stop signing with it. Retire it sooner and you orphan licenses that were perfectly valid when issued.

Compromise is a different, faster path

Planned rotation is leisurely. A suspected key compromise is not — you cannot afford to keep trusting a key an attacker may now hold, even though honest licenses depend on it. This is where rotation meets revocation, and where being honest about the trade-off matters:

  • Connected clients can be pushed a new trusted key set that drops the compromised kid quickly, and re-issued licenses under the new key. The exposure window is however long it takes clients to fetch the update.
  • Fully offline clients cannot be reached, by definition. The mitigation is the same as for revocation generally: short license lifetimes limit the blast radius, and the next time an offline client is updated it receives the corrected key set. There is no magic that reaches a disconnected machine — anyone claiming instant global revocation for offline licenses is selling you something that does not exist.

This is also why the signing key is treated as the most sensitive material in the whole system and is never the same across tenants: a compromise is contained to one tenant's key set, not every customer you have.

Why tiers do not touch this

A question that comes up: do Pro and Enterprise customers get different keys? No — and conflating the two is a design smell. The signing key proves authenticity ("Keyright issued this"); entitlements and tier live inside the signed payload and prove authorization ("this license unlocks these features"). Rotating a key must never change what a license is allowed to do, and changing a customer's tier must never require a new key. Keeping the two concerns separate is what lets you rotate keys for security reasons and change entitlements for commercial reasons without the two ever colliding.

The summary worth keeping: design for rotation on day one, because the day you need to rotate is the day it is already too late to add the mechanism. A key set with IDs and an overlap window turns "we think the key leaked" from an extinction event into a Tuesday.

Keyright ships key-set verification and overlap rotation out of the box. See the Keyright getting-started guide to wire it into your product.

Top comments (0)