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:
magicdistinguishes a license from random base64 garbage.customer_idis logged at startup for traceability.expires_atis enforced inclusive — the license is valid until the start of the listed UTC day.feature_maskis the per-module gate. See Feature Mask.hardware_idbinds the license to a specific machine. The validator reads/etc/machine-idon 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.
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
.licfile →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-iddiffers from the signed value; surfaces when a license is moved between machines. - Expiry → days remaining is negative; the startup line
days_left=Nwill show this with a negativeN. - 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.