Vulnerability Management

Licensing With Ed25519 JWT and Quota Enforcement

By PMAP Security Team 19 min read

Most teams treat software licensing as a billing concern that lives outside the product. In a security platform that runs inside regulated networks, sometimes on air-gapped infrastructure, licensing is an engineering concern with real runtime behavior. It decides whether a write succeeds, whether a scanner import is allowed to persist a new asset, and what the platform does the day a license expires. Those decisions cannot depend on a server you reach over the internet, because the deployment may have no internet at all.

This article walks through how a vulnerability management platform can enforce a license entirely offline using a signed Ed25519 token, how it computes a small set of license states, and how it gates writes and quotas without ever calling home. The reference model throughout is PMAP, where licensing is enforcement infrastructure rather than a feature bolted onto the side. If you want the wider context of how this layer fits the rest of the system, the vulnerability management platform architecture pillar covers the broader foundation.

What a Platform License Actually Has to Enforce

A license is only useful if the running software respects it without supervision. For a vulnerability management platform, that means three concrete dimensions of usage need to be governed at runtime.

The first is time. A license has an expiry, and the platform has to know what to do before, during, and after that boundary. Hard-cutting all access at the stroke of expiry is hostile in a security context, because operators may still need to read findings during a renewal negotiation. So the time dimension is not a single flag. It is a sequence of states that change behavior gradually.

The second is quota. Entitlements usually scale with something countable. In PMAP the countable resource is asset seats, the number of assets the deployment is licensed to track. Quota enforcement has to happen at every point where a new asset could be created, not just in one tidy place, because assets enter the system from manual creation, bulk import, and several scanner integrations.

The third is feature gating. The token carries a map of feature flags that can switch capabilities on or off per deployment. In the current PMAP model these flags are present in the token claims and available for future enforcement. The infrastructure to read them exists, and business code asks the license provider rather than reading the token directly.

What ties these together is a single rule: business code never parses the token itself. Every part of the platform that needs a licensing decision delegates to a license Provider singleton. That keeps the cryptography and the state machine in one place, and it means a feature author never has to think about signatures or grace windows. They ask a question and get an answer.

Signed Ed25519 Tokens as the Source of Truth

The authoritative artifact in this design is a single signed token. PMAP uses Ed25519, the elliptic-curve signature scheme defined in RFC 8032, wrapped in a JWT-style claims payload along the lines of RFC 7519. The license itself is the data, and the signature is the proof that the data was issued by the vendor and has not been altered.

This matters because of what it removes. There is no license server to query, no activation handshake, no per-request callback. The token already contains everything the platform needs: a license ID, a product ID, a plan name for display, the customer company name, the tenant ID, the feature map, the resource limits map, the grace window duration, an issued-at timestamp, and an expiry timestamp. To trust the token, the platform only has to verify one Ed25519 signature against one public key.

That public key is where the design makes a deliberate choice. The verifying public key is compiled into the binary as a constant. It is not read from an environment variable, not loaded from a config file, and not stored in the database where it could be swapped. An attacker who wants to forge a license cannot point the platform at a key they control, because there is no override path to the key. The only way to change the trusted key is to rebuild the binary.

The private key that signs valid tokens never exists inside PMAP. Tokens are issued externally by the vendor’s license-creator application, which holds the signing key. The deployed platform only ever verifies. This separation is the core security property: even with full access to a running PMAP instance and its database, you cannot mint a license that the platform will accept, because the signing key is not there to steal.

Offline Validation for Air-Gapped Deployments

Because verification needs nothing but the embedded public key and the token bytes, validation is completely offline. An offline manager checks the signature and parses the claims without reaching any external service. This is not a degraded fallback mode for when the network is down. It is the only mode. There is no online path that offline validation replaces.

For security buyers this is often the deciding detail. Vulnerability management runs in the parts of an organization that are most isolated by design: segmented OT networks, classified environments, regulated zones with no outbound internet. A licensing scheme that phones home to activate or to re-check entitlements simply cannot be deployed there. Worse, a phone-home design creates an availability dependency, where a licensing outage at the vendor can disable a customer’s security tooling. Offline-by-construction removes that whole category of risk.

The trade-off is real and worth stating plainly. Because there is no live callback, the platform cannot instantly learn that a license was cancelled mid-term in the way an online check could. Revocation and suspension are expressed as token states that take effect when a new token is installed or when the issuer re-issues. In exchange for that, the platform never breaks because of a network event, and it runs identically whether or not it can see the internet. For a security tool, predictable offline behavior is usually the better trade.

The Five License States

License status in PMAP is not a boolean. It is one of five computed states, derived from the token’s expiry, its grace window, and whether a token is present at all. Each state has a defined effect on what the platform allows.

unconfigured is the starting point. No token has been installed yet. The platform knows it has no entitlements and surfaces a banner directing an administrator to the license settings. Writes are blocked, because an unlicensed platform should not be accumulating state as if it were licensed.

valid is the normal operating state. A signed token is installed, its signature checks out, and the current time is before expiry. Everything works. Reads and writes pass, quotas are enforced against the licensed limits, and the admin license card shows the plan, the company, and an asset usage bar.

grace is the cushion after expiry. The token carries a grace window duration in its rooe claim. When the current time is past expiry but still inside that window, the platform stays fully functional. Status reads as grace, both reads and writes are allowed, and the operator sees that renewal is due without losing any capability. This is the difference between a renewal reminder and a service outage.

read_only is what happens once the grace window also passes. The platform stops accepting writes but keeps serving reads. Operators can still open findings, browse assets, and pull reports. They simply cannot mutate data until a fresh token is installed. For a security tool this is the humane failure mode, because it never hides the data an analyst may need during an incident, even if the commercial relationship has lapsed.

expired is the hard-expired state past expiry plus grace, with writes blocked the same way read-only blocks them. Two further issuer-driven states, revoked and suspended, also block writes immediately when a token carries that status. The throughline across all of these is consistent: blocked states stop mutations and never stop reads.

Grace Period Recovery After Expiry

The grace window is worth dwelling on, because it is where the design shows its intent. A token that is past its expiry timestamp but still inside the rooe grace window is accepted for both reads and writes, with the status reported as grace. Nothing degrades during this period. The point is to give renewals room to complete on a human timeline rather than forcing a same-day cutover.

Recovery from a lapsed state is always possible by installing a corrected token. Even a token whose expiry has already passed can be installed, as long as its signature is valid, which supports disaster-recovery situations where the vendor re-issues a fixed token. The act of installing a fresh, validly signed token re-evaluates the state and can move the platform from read-only or expired back to valid or grace. There is no separate unlock ritual. The token is the unlock.

The Global Write Gate Middleware

Enforcement at runtime comes down to one piece of middleware called LicenseGate, applied globally to all authenticated write paths. Every POST, PUT, PATCH, and DELETE request passes through it. The gate looks at the current license state and decides whether the mutation is allowed to proceed.

When the license is valid or grace, the gate passes the request through untouched. When the license is in a blocking state, the gate stops the request before it reaches the handler and returns an HTTP error with a message describing the situation. A read-only platform returns a 403 explaining that the system is in read-only mode because the license has expired. An expired license returns a 403 stating the license has expired. A revoked or suspended token returns a 403 naming that condition. An unconfigured platform returns a 403 telling the caller the license is not configured and to contact an administrator.

Centralizing this in one middleware is what makes the guarantee trustworthy. There is no scenario where a single forgotten handler quietly accepts writes while the rest of the platform is gated, because individual handlers do not implement the check at all. They inherit it from the route stack. A new endpoint added next year is gated automatically the moment it is registered on an authenticated write path. The same architectural principle that keeps per-user view scoping reliable in saved filters and views applies here: enforce once at the boundary, not repeatedly in every handler.

Why Reads Always Pass Through

The gate makes one deliberate exemption category and one deliberate route exemption. The category is read methods. GET, HEAD, and OPTIONS bypass LicenseGate unconditionally. Whatever the license state, operators keep full read access to their data. A hard-expired license never blinds an analyst to the findings already in the system.

The route exemption is the license endpoint itself. The PUT /api/v1/admin/license path is explicitly exempt from the gate. This is not an oversight, it is the recovery hatch. If the license endpoint were gated like everything else, an expired platform would block the very request needed to install a new license, creating a deadlock with no way out. By exempting that single path, a platform administrator can always upload a fresh token to recover from an expired, revoked, or read-only state. Every other mutation stays blocked.

Asset Seat Quota Enforcement

Beyond time-based state, the second enforcement dimension is quota. The licensed resource in PMAP is asset seats, and the limit lives in the token’s limits map as limits.assets. Whenever the platform is about to create an asset, it asks the provider whether doing so would exceed the licensed cap by calling CheckLimit("assets", currentCount).

The semantics are inclusive, which is worth being precise about. A limit of N permits up to N total rows in the assets table. The check rejects a new asset when the current count is already at or above the limit, returning a dedicated ErrLicenseLimitReached sentinel that surfaces to the caller as an HTTP 402. The 402 status, Payment Required, is an honest signal: the operation did not fail because of a bug or a permission problem, it failed because the deployment has reached the seats it is licensed for and needs a larger entitlement.

This single convention is shared across every path that can add an asset. The asset service uses it on manual creation and on bulk import alike. There is no separate quota rule for the bulk path that could drift out of sync with the single-create path. One limit, one comparison, one error.

Keeping Scanner Imports Within Quota

Assets do not only enter PMAP through the asset service. They also arrive through scanner integrations, where an import can discover and persist many previously unknown assets in a single run. If quota were only enforced in the asset service, scanner ingestion would be a back door around the cap.

It is not, because the same quota logic is exposed as a package-level function, CheckAssetQuota, built on a process-wide provider singleton. All six scanner importers in PMAP, covering Acunetix, Nessus, Nmap, Masscan, Qualys, and SonarQube, call this function before persisting newly discovered assets. The singleton exists precisely so these importers can ask the quota question without threading the provider through every constructor. The result is that a Nessus import and a manual asset creation answer to the exact same licensed cap, and neither can quietly exceed it.

This consistency is the point. A quota that only some entry paths respect is not a quota, it is a suggestion. By routing every asset-creating path, service and scanner alike, through the same inclusive check against the same token limit, the platform makes the seat count mean something it can stand behind.

Installing and Refreshing a Token

Installing a license is a single operation against PUT /api/v1/admin/license, restricted to the platform-admin role. The endpoint accepts the new signed token string, and the first thing it does is validate. Before the token is written anywhere, its signature is verified. A token that fails verification, including a malformed token that cannot be parsed, is rejected at the door with an HTTP 400. Forgeries never reach persistence.

There is a careful distinction in what counts as valid for installation. An expired token whose signature is genuine is accepted, because that supports the disaster-recovery case where the vendor re-issues a corrected token and the admin needs to install it to recover. What is rejected is a token that fails signature verification outright. The line is drawn at cryptographic authenticity, not at expiry, which keeps the recovery path open without weakening the security guarantee.

Once a valid token is persisted into the tenant_license table as an upsert, the platform refreshes immediately. The provider keeps an in-memory cache of the active token and normally refreshes it on a 300-second cycle, re-reading from the database and re-validating. After a successful upload, the endpoint calls the provider’s refresh synchronously so the new claims take effect at once rather than waiting up to five minutes for the next scheduled cycle. An administrator who installs a renewed license sees the platform come back to a valid state right away.

The read side mirrors this. GET /api/v1/admin/license returns the live status, the parsed claims, a validity flag, and a live count of assets in use. That usage count is computed fresh on every read so the admin license card can render an accurate progress bar comparing assets used to the licensed cap. The status surfaced here is the same enum the gate uses, so what an administrator sees and what the platform enforces are always the same thing.

Key Rotation by Design

Because the trust anchor is a compiled public key, rotating it is intentionally a deployment event rather than a configuration change. The public key is a constant in the source. Changing the key the platform trusts means rebuilding and redeploying the binary.

This is a feature, not a limitation. A key that can be rotated through configuration is a key that can be replaced by anyone who can write that configuration, which reopens the forgery path the compiled key was meant to close. By making rotation require a rebuild, the design ensures that the set of trusted keys is fixed at build time and cannot be altered by a running attacker or a misconfiguration. The cost is that key rotation is a release, and that cost is accepted deliberately.

For development, a helper script generates throwaway keypairs so engineers can produce test tokens without touching the production trust anchor. The principle is the same one NIST SP 800-57 describes for key management generally: control where keys live and who can change them. Here the answer is that the verifying key lives in the binary and changes only through a build.

How PMAP Stays Enforced Without Calling Home

Step back and the design resolves to one property: the platform is fully enforced and entirely self-contained. There are no external systems in the licensing path. There is no background job, no queue, and no webhook. Validation is an Ed25519 signature check against an embedded key, and refresh is a plain database read of the persisted token every 300 seconds. Nothing in licensing reaches outside the deployment.

That is what makes the model fit the deployments security tooling actually lands in. An air-gapped install behaves identically to a connected one, because connectivity was never part of the design. The license cannot be disabled by a vendor outage, because there is no vendor service in the loop. And it cannot be forged from inside the deployment, because the signing key was never inside it to begin with.

PMAP also pins this to a single tenant. The tenant ID is a compiled constant of 0, and every validation, quota check, and feature lookup uses it. The underlying license SDK is multi-tenant capable, but PMAP fixes one tenant, which keeps the enforcement model simple and matches how the platform is deployed. Multi-tenant access control and role separation are a different concern handled elsewhere in the platform. Licensing here is about a single deployment proving its entitlements offline. For administrators who own that day-to-day, the platform administrator workflow and the platform architecture pillar show where licensing sits among the other controls.

The result is licensing you can reason about. Five states with defined behavior, one gate on every write, one inclusive quota on every asset, one signed token as the source of truth, and zero outbound calls. For a vulnerability management platform that has to run where the network does not reach, that combination is the point.

Frequently Asked Questions

What does PMAP use to sign and validate licenses?

PMAP licenses are signed Ed25519 tokens carrying a JWT-style claims payload. Ed25519 is the elliptic-curve signature scheme from RFC 8032, and the claims follow the JWT model from RFC 7519. The platform validates a license by verifying its signature against a public key compiled into the binary. No license server or online activation is involved.

How does licensing work in an air-gapped or offline deployment?

It works the same as in a connected one. Validation is fully offline. The offline manager checks the Ed25519 signature and parses the claims using only the embedded public key and the token bytes, with no call to any external service. Offline is the only validation mode, not a fallback, so air-gapped deployments are supported by construction.

What are the five license states and what does each one do?

The states are unconfigured, valid, grace, read_only, and expired. unconfigured means no token is installed and writes are blocked. valid is normal operation. grace is the period past expiry but inside the token’s grace window, where both reads and writes still work. read_only follows once the grace window passes, blocking writes while keeping reads. expired is the hard-expired state. Issuer-driven revoked and suspended states also block writes immediately. In every blocking state, reads still pass.

What happens to my data when a license expires?

You never lose read access. The global write gate exempts GET, HEAD, and OPTIONS requests unconditionally, so even a hard-expired license leaves operators able to open findings, browse assets, and pull reports. Only mutations are blocked, and only until a fresh token is installed. The grace period also delays any blocking at all, giving renewals time to complete.

How does the asset quota get enforced across scanners?

The licensed asset cap lives in the token’s limits map. The asset service calls CheckLimit("assets", currentCount) on every create and bulk-import path, and the limit is inclusive, so a cap of N allows up to N assets and rejects the next one with an HTTP 402. The same quota logic is exposed as CheckAssetQuota and called by all six scanner importers before they persist discovered assets, so manual creation and scanner ingestion answer to the same cap.

Can a license be forged if someone gains access to the deployment?

No. The signing private key is never present in PMAP. Tokens are issued externally by the vendor’s license-creator application, and the deployed platform only verifies. The verifying public key is compiled into the binary with no environment-variable or database override, so an attacker cannot point the platform at a key they control. Even with full access to a running instance and its database, there is no signing key to steal and no override path to abuse.

How are license keys rotated?

Rotation is intentional and deliberate. The trusted public key is a compiled constant in the source, so changing it requires rebuilding and redeploying the binary. This prevents the verifying key from being swapped through configuration by an attacker or a misconfiguration. A development helper script generates throwaway keypairs for testing without touching the production trust anchor.

author avatar
PMAP Security Team

Newsletter

Get the next writeup in your inbox

One short email when a new case writeup or detection deep dive ships. No marketing drip, no third-party tracking.