Book a Demo

Licensing: generation and verification

Cabinee includes functions for verifying the digital signature and integrity of a license using a standard algorithm (ECDSA-256) that is part of the Java Security API. These functions are used only to confirm license authenticity and are not intended to protect information that is subject to regulation. Cabinee software is not a cryptographic information protection facility, is not subject to mandatory certification, and does not require a license from regulatory authorities (FSB of the Russian Federation, KNB of the Republic of Kazakhstan) for the development or distribution of cryptographic protection tools.

1) Overview

The platform supports two modes:

  • COMMUNITY — functional limitations (in the base distribution: 1 Workflow is allowed for the entire instance).
  • ENTERPRISE — without the stated limitations. Enabled by a valid license.

A license is a JWS token (signed JSON) that is stored on the server and verified on every start and in online mode.


2) Cryptography and formats

  • Signature algorithm: ES256 (ECDSA).
  • Private key (at the licensor): PKCS#8 PEM.

This is a standard container for a private key (one key — one file), convenient for libraries and storage.

  • Public key (on the server for verification):X.509 SubjectPublicKeyInfo PEM.

This is a standard representation of the public key only, without a certificate. It is suitable for fast signature verification.

  • Key identifier (kid) is the public key file name without the extension, for example 2025-08-e1.pem -> kid = 2025-08-e1.

JWS structure

Header

{
  "alg": "ES256",
  "kid": "2025-08-e1",
  "typ": "JWT"
}

Payload

{
  "edition": "Enterprise",
  "issuedTo": "nazym.kazizmurat@reunico.com",
  "dueDate": "2025-08-20T15:30:59Z",
  "licenseId": "UUID",
  "licenseVersion": 1
}

The dueDate field is ISO-8601 UTC. After this date the license becomes invalid.


3) Placing public keys

Public keys are shipped with the application and placed in resources:

src/main/resources/licenses.keys/<kid>.pem

On start, the PublicKeyProvider component loads all *.pem files from:

classpath:/licenses/keys/*.pem

(our path is mapped to licenses.keys in resources).


4) Key generation (at the licensor)

# 1) Generate an EC key (secp256r1)
openssl ecparam -name prime256v1 -genkey -noout -out ec-private.pem

# 2) Convert to PKCS#8 (no password)
openssl pkcs8 -topk8 -nocrypt -in ec-private.pem -out ec-private-pkcs8.pem

# 3) Extract the public key
openssl ec -in ec-private.pem -pubout -out ec-public.pem

# 4) Save the contents of ec-public.pem to the resource:
# src/main/resources/licenses.keys/2025-08-e1.pem

5) Issuing a license (CLI)

The project includes the GenLicenseCli utility.

Run from the IDE — arguments:

--due 2025-08-20T15:30:59Z \
--to "nazym.kazizmurat@reunico.com " \
--kid 2025-08-e1 \
--priv /path/to/ec-private-pkcs8.pem \
--alg ES256

The output is a JWS string that is used to upload the license through the Admin UI.


6) Uploading and managing a license through the Admin UI

Uploading and managing a license in Cabinee is performed through the Admin UI.

6.1 Upload through the Admin UI

Path in the interface:

Admin → License management

The following actions are available in the UI:

  • Upload — upload a license
  • View — view license information
  • Delete — delete a license

When uploading a license:

  1. The administrator pastes the license JWS token into the UI.
  2. The UI sends the JWS token to the server through an internal backend mechanism.
  3. The server validates the license and applies it without restarting the application.

6.2 What the server does when a license is uploaded

Data transferred:

License JWS token

What the server does when a license is uploaded:

  1. Validates the JWS (ES256, a valid kid, a non-expired dueDate, edition = ENTERPRISE, and so on).

  2. Stores the JWS itself in the database, in the bd_attachment table, as a record with:

  • entity_type = LICENSE

  • file_name = "license.key"

  • content_type = "text/plain"

  • content = <JWS bytes, UTF-8>

  • version_tag = licenseId (UUID)

The version_tag field is used to store licenseId and to prevent the same license from being uploaded again.

  1. Calls LicenseManager.reload() — the mode switches without a restart.

6.3 Deleting a license

A license is deleted through the Admin UI (Delete button).

On deletion:

  1. The license record is deleted from the bd_attachment table.
  2. LicenseManager.reload() is executed.
  3. If there is no longer an active license, the system automatically switches to COMMUNITY mode.

An application restart is not required.

7) Where the license is stored

The license source is the LicenseSource component, which reads in priority order:

  1. Database: the latest record from bd_attachment with entity_type = LICENSE is taken.
  2. Optional config fallback: if there is no license in the database, the string from application.yaml is used:
    app:
      license:
        initial: "${LICENSE_KEY:}" 

This fallback is read-only at startup and is not saved to the database automatically. The persistent license store is the database. License upload is performed exclusively through the Admin UI.


8) How verification works on the server

The LicenseVerifier component does the following:

  1. Parses the JWS (JJWT).
  2. Takes the public key from PublicKeyProvider by kid.
  3. Checks alg == ES256 (others are rejected).
  4. Requires edition == "Enterprise".
  5. Compares dueDate with the current time (UTC).
  6. Returns LicenseClaims (issuedTo, dueDate, kid, and so on).

If verification fails, the platform runs as COMMUNITY.


9) License policy (limitations)

  • Space (Workflow) — always the very first one (the earliest by creation date).
    • On a downgrade from ENTERPRISE to COMMUNITY, if the database already contains several spaces (Workflow), the system will automatically work only with the very first space; the others are treated as “invisible/unavailable” for operations.
  • GET list / GET by id: in COMMUNITY we show and allow operations only for the very first space (Workflow); other ids are ignored (or return 404/403 — depending on the operation).
  • UPDATE / DELETE: allowed only for the very first space (Workflow).
  • CREATE:
    • if the system has 0 spaces — the first space (Workflow) can be created;
    • if the system has > 1 spaces (legacy after a downgrade) — creating new ones is forbidden until the database is reduced to 1 space (Workflow) (so that the discrepancy is not made worse).

10) Protection against license overwrite

The application includes a protection mechanism against changing a license after it has been uploaded.

    app:
      license:
        immutable: true   # false by default

When immutable = false, uploading and deleting a license through the Admin UI is allowed.

When immutable = true, after the first upload:

  • re-uploading a license is forbidden;

  • deleting a license is forbidden;

  • all attempts to change the license return 403 Forbidden.

The immutable flag does not affect license status checks or the /info endpoint.

The mechanism is used to protect the license in production environments.

11) Test checklist

  1. Keys are located in src/main/resources/licenses.keys/*.pem, names = kid.
  2. Upload a valid license through the Admin UI → the system switches to ENTERPRISE mode.
  3. A record with entity_type = LICENSE appears in bd_attachment in the database; the content is the original JWS (UTF-8).
  4. The license was deleted from the database / dueDate expired → the mode falls back to COMMUNITY.
  5. If there is no license in the database and app.license.initial is set in application.yaml, the server can start in ENTERPRISE in memory, but a database record is created only after the license is uploaded through the Admin UI.

12) Determining the current licensing mode

The current platform licensing mode (ENTERPRISE or COMMUNITY) is determined through the service endpoint:

 GET /info
{
 "license": {
   "status": "ENTERPRISE"
 }
}

The endpoint is used to:

  • determine the current licensing mode in the UI;

  • diagnose system state;

  • verify the license without access to the administrative interface.