Skip to content

Licensing

The platform is gated by an offline, asymmetrically-signed license file checked at startup. There is no license server and no online phone-home: the binary embeds an Ed25519 public key (XOR-scrambled at compile time), validates the signature on the configured .lic file, and refuses to initialise any module if validation fails. A 32-bit feature mask in the license enables or disables individual subsystems independently of the TOML [modules] table.

License File Layout

The on-disk file is Base64-encoded. Decoded, it is a packed 181-byte binary blob defined identically in include/security/license_validator.hpp and src/licensing.cpp:

#pragma pack(push, 1)
struct LicensePayload {
    char     magic[4];        // "MBSS"
    uint8_t  version;         // 1
    char     customer_id[36]; // UUID, e.g. "550e8400-e29b-41d4-..."
    int64_t  expires_at;      // Unix timestamp (UTC) of expiry midnight
    uint32_t feature_mask;    // 32-bit FEATURE_* bitmask
    char     hardware_id[64]; // /etc/machine-id, null-padded
};                            // 117 bytes
#pragma pack(pop)
// followed by 64 bytes of Ed25519 signature → 181 bytes total

Field semantics:

  • magic distinguishes a license from random base64 garbage.
  • customer_id is logged at startup for traceability.
  • expires_at is enforced inclusive — the license is valid until the start of the listed UTC day.
  • feature_mask is the per-module gate. See Feature Mask.
  • hardware_id binds the license to a specific machine. The validator reads /etc/machine-id on Linux and compares.

Validation Pipeline

LicenseValidator::load() runs every check in order; any failure causes startup to abort.

sequenceDiagram
    participant Main as main.cpp
    participant Utils as UTILS::setup
    participant LV as LicenseValidator
    participant Disk as platform.lic
    participant Mach as /etc/machine-id
    participant LKGT as license/.lkgt

    Main->>Utils: setup(argc, argv)
    Utils->>LV: new LicenseValidator(lkgt_path)
    Main->>LV: load()
    LV->>Disk: read base64 file
    LV->>LV: base64Decode → 181 bytes
    LV->>LV: check magic == "MBSS", version == 1
    LV->>LV: deobfuscatePublicKey() (XOR with KEY_SCRAMBLE)
    LV->>LV: Ed25519 verify(payload, sig, pub)
    alt invalid signature
        LV-->>Main: false → process exits
    end
    LV->>Mach: read /etc/machine-id
    LV->>LV: compare against payload.hardware_id
    alt mismatch
        LV-->>Main: false → process exits
    end
    LV->>LV: now() < expires_at?
    alt expired
        LV-->>Main: false → process exits
    end
    LV->>LKGT: read prior LKGT
    LV->>LV: now() >= prior LKGT timestamp?
    alt rollback detected
        LV-->>Main: false → process exits
    end
    LV->>LKGT: write current timestamp + HMAC
    LV-->>Main: true (also logs days_left and feature list)

Last-Known-Good-Time (LKGT)

The LKGT mechanism defends against clock-rollback attacks. After every successful validation the validator writes the current UTC time, with an HMAC-SHA256 trailer keyed off material derived from the public key, into a state file (license.lkgt_path, default ./license/.lkgt). On subsequent starts, the validator refuses to load if now() < stored_LKGT — the system clock has gone backwards since the last successful boot. The HMAC trailer prevents trivial tampering with the state file.

Public-key obfuscation

The Ed25519 public key is embedded as a 32-byte XOR-scrambled constant. deobfuscatePublicKey() un-scrambles it at runtime against KEY_SCRAMBLE (a 32-byte ASCII string also baked into the binary). This is not encryption — anyone with the binary and a debugger can recover the key — but it raises the bar for trivial binary patching that swaps in an attacker-controlled key.

Feature Mask

// include/security/license_validator.hpp
static constexpr uint32_t FEATURE_PREPAID    = (1U << 0);
static constexpr uint32_t FEATURE_CDR_LOADER = (1U << 1);
static constexpr uint32_t FEATURE_STATS      = (1U << 2);
static constexpr uint32_t FEATURE_BILLING    = (1U << 3);
static constexpr uint32_t FEATURE_BILL_FMT   = (1U << 4);
static constexpr uint32_t FEATURE_CRM        = (1U << 5);
static constexpr uint32_t FEATURE_DIAMETER   = (1U << 6);
static constexpr uint32_t FEATURE_ALL        = 0x7FFFFFFFU;

Bits 7–30 are reserved for future modules. requireFeature(bit, name) returns true if the bit is set and writes an LM_ERROR log line if not. Modules that depend on a feature must call requireFeature before initialising; the TOML [modules] table is therefore advisory, with the license being the authoritative gate.

featureMaskToString(mask) produces a comma-separated, human-readable list for startup logs. A typical line looks like:

[main | 2026-04-30 12:00:00] License valid — customer=550e8400-... days_left=42 features=[PREPAID,CDR_LOADER,STATS,BILLING,BILL_FMT,CRM,DIAMETER]

License Generator

bin/licensing is a standalone tool used by the operator vendor, not by deployed customers. Its private key must never be on the customer machine. It supports four operations:

license_generator --keygen [--out-priv FILE] [--out-pub FILE]
license_generator --issue  --customer UUID --expires YYYY-MM-DD
                           --features HEX --hwid MACHINE_ID
                           [--privkey FILE | env MBSS_LICENSE_KEY=FILE]
                           [--out FILE]
license_generator --inspect FILE.lic
license_generator --verify  FILE.lic

--keygen produces a fresh Ed25519 keypair. The public component must be re-XOR-scrambled with KEY_SCRAMBLE and re-baked into both licensing.cpp and license_validator.cpp (the EMBEDDED_PUB_KEY array) for the next platform release. --issue reads the offline private key (file path or MBSS_LICENSE_KEY env var), composes the LicensePayload, signs it, and writes the Base64-encoded .lic file.

--inspect decodes a .lic and prints its fields without checking the signature. --verify runs the full validation pipeline (signature, hardware id, expiry) against the public key compiled into the tool — useful for confirming a license still works on a target machine before shipping it.

Configuration

Reference: every TOML role file ships the same [license] section.

[license]
license_key = "./license/platform.lic"
lkgt_path   = "./license/.lkgt"

Permissions: the unpack.sh deployment script tightens keys/ to mode 700 and individual key files to 600 to prevent accidental world-readability.

Failure Modes and Logging

Each failure path emits a critical log entry and the process exits with code -1:

  • Missing or unreadable .lic file → License validation failed. Shutting down.
  • Bad magic / version → same critical log; common after a malformed file or wrong format version.
  • Signature mismatch → most common after a tampered file or after the platform's public key was rotated without a re-issued license.
  • Hardware mismatch → /etc/machine-id differs from the signed value; surfaces when a license is moved between machines.
  • Expiry → days remaining is negative; the startup line days_left=N will show this with a negative N.
  • LKGT rollback → the system clock has moved backward; can also surface after a virtual-machine snapshot revert.

There is no soft-fail, no grace period, and no retry. License is checked once at startup, not periodically; a license that expires while the process is running does not cause termination — it will be caught at the next restart.