
License C++ library
v2.1.0
Table of contents
- Overview
- Versions
- Library files
- License class description
- FIPS 140-3 compliance
- Security measures
- Usage
- Build and connect to your project
- Portability
- Example
Overview
License is a C++ library for offline, time-limited application licensing. It generates a token from a computer identifier, issues a license for that token, and verifies the license’s signature, expiry and module permissions. The vendor creates a key pair once: the private key stays with the vendor, and the public key is compiled into the application. Possessing the public key does not enable license forgery, and changing the signed computer, expiry or module data invalidates the signature. Hex letter case does not affect verification, and the license text is not a unique identifier for an entitlement. A license can name several functional modules, for example tracking and detector, so one key pair and one license file can cover a product. ECDSA P-256 with SHA-256 is provided by the CryptoProvider library (see below). The library prefers a compatible, correctly configured FIPS module and otherwise uses the standard mode; an invalid configuration file causes initialization to fail. Module selection alone does not establish FIPS validation. The public interface uses standard C++ types and static methods. The library requires C++17 and implements computer identification on Linux and Windows. Offline checks depend on the local clock and identifiers and cannot prevent clock rollback, cloned installations or application patching. For the complete workflow, see Usage.
License is general-purpose software: it is not tied to any sector or application, and the developer of a program decides what the program does with it.
License depends on the CryptoProvider library and contains no cryptographic implementation of its own: SHA-256, ECDSA P-256 key generation, signing and verification, constant-time comparison and the wiping of secrets are all calls of CryptoProvider. CryptoProvider is one library for every library that needs cryptography, so that only that one library has to be certified instead of many separate implementations. It uses the native Windows CNG on Windows. On Linux License needs OpenSSL 3.0 or newer: without it CryptoProvider builds its standalone backend, which has no ECDSA, and License configures and builds but does not work (selfTest() returns FALSE). License does not contain CryptoProvider: in a stand alone build it is expected in the folder next to License, and a parent project adds it (see Build and connect to your project).
Versions
Table 1 - Library versions.
| Version | Release date | What’s new |
|---|---|---|
| 1.0.0 | 31.01.2022 | First version. |
| 1.1.0 | 28.09.2023 | - Hardware dependency moved to MAC address from CPU ID. - Documentation updated. |
| 1.1.1 | 05.01.2024 | - Code style issues fixed. - Documentation updated. - Only AES256 algorithm is used. |
| 1.1.2 | 15.04.2024 | - Documentation updated. |
| 1.1.3 | 17.05.2024 | - Documentation updated. |
| 1.1.4 | 19.07.2024 | - CMake updated. - Repository structure updated. |
| 1.2.0 | 19.07.2024 | - Custom key support added instead of only MAC address. |
| 1.2.1 | 12.12.2025 | - Enumerate MAC interfaces for default key and validate licenses against all detected addresses. |
| 2.0.0 | 09.09.2026 | - Licensing moved from a shared secret to ECDSA P-256 with SHA-256, signed by a vendor private key and checked with a public key compiled into the application. - OpenSSL provides cryptography, preferring a compatible, configured FIPS provider at run time. - Tokens use OS or hardware identifiers; software-only network interfaces are filtered, but cloned identifiers are not detected. - Expiry uses UTC seconds rather than local time. - Cryptographic known-answer self-tests, hardened build flags and a test suite. - Breaking change: licenses issued by 1.x are not accepted and must be reissued. |
| 2.0.1 | 15.10.2026 | - All library, example and test targets are built with strict warning and exploit-mitigation flags (stack protector, stack-clash protection, control-flow protection, _FORTIFY_SOURCE, RELRO, non-executable stack, /guard:cf); standard C++ only.- Fixed a build error on MSVC ( fopen rejected by /sdl).- Decoded private key bytes and the copies made by the OpenSSL parameter builder are cleared on every path with a wipe that the optimiser keeps. - No method throws: a failed memory allocation is reported as a failure of the method. - A rejected license leaves no parsed values in the internal decoder outputs. - Linux identifier files are opened with the close-on-exec flag. - Examples read bounded lines and clear the private key text. - Tests: damaged-license campaign, concurrent use of every method, outputs after failures. - Documentation corrected: generated header location, limits of arguments, thread safety, build instructions. |
| 2.1.0 | 15.10.2026 | - Cryptography is provided by the CryptoProvider library (a new dependency: the folder ../CryptoProvider in a stand alone build); the library no longer links OpenSSL directly.- On Windows the native CNG is used by default; the OpenSSL backend of CryptoProvider can be selected. - License format, keys and signatures are unchanged: licenses and keys of 2.0.x are accepted and the licenses issued now are accepted by 2.0.x (checked on Linux and Windows). - getCryptoBackend() returns the backend and the mode, for example openssl-3.0.13 (FIPS mode).- setCryptoConfig(...) returns FALSE for a backend without a FIPS module (Windows CNG) and the environment variables OPENSSL_CONF and OPENSSL_MODULES do not choose the FIPS module.- selfTest() also runs the self-tests of the FIPS module of the OpenSSL backend.- A host application that has initialized CryptoProvider itself decides the mode. - New tests: initialization by the host application, backend and mode names. |
Library files
The library is supplied as source code in a CMake project. The CryptoProvider library that it depends on is not part of this repository.
License.h and the generated LicenseVersion.h are the only headers a consuming application needs. CMake generates src/LicenseVersion.h from src/LicenseVersion.h.in with the version of the project() command in src/CMakeLists.txt; the generated file is part of the repository. Linking the License CMake target provides both include directories and its dependencies. When packaging headers manually, include src/License.h and src/LicenseVersion.h. Everything under impl is implementation detail. The repository structure is shown below:
CMakeLists.txt ---------------- Main CMake file of the library.
static ------------------------ Folder with documentation images.
src --------------------------- Folder with library source code.
CMakeLists.txt ------------ Library CMake file.
License.h ----------------- PUBLIC header: the whole interface.
License.cpp --------------- C++ implementation file.
LicenseVersion.h.in ------- Template of the PUBLIC LicenseVersion.h header.
LicenseVersion.h ---------- PUBLIC header with the version (generated).
impl ---------------------- Internal implementation.
Crypto.h -------------- Calls of CryptoProvider (SHA-256, ECDSA P-256) and mode selection.
Crypto.cpp ------------ Implementation file of the CryptoProvider calls.
LicenseFormat.h ------- License record layout and hex coding.
LicenseFormat.cpp ----- License format implementation file.
Fingerprint.h --------- Hardware identification.
Fingerprint.cpp ------- Hardware identification implementation.
tests ------------------------- Folder with tests.
CMakeLists.txt ------------ Tests CMake file.
TestHarness.h ------------- Assertion helpers.
test_crypto.cpp ----------- Cryptographic primitive tests.
test_format.cpp ----------- License encoding tests.
test_license.cpp ---------- End-to-end issue and check tests.
test_regressions.cpp ------ Untrusted input and robustness tests.
test_examples.cmake ------ End-to-end command-line example tests.
run_fips_tests.sh --------- Helper to run the suite against a FIPS module.
examples ---------------------- Folder with examples.
CMakeLists.txt ------------ CMake file for examples.
KeyGenerator -------------- Folder with vendor key generator.
CMakeLists.txt -------- Application CMake file.
main.cpp -------------- Application source code file.
TokenGenerator ------------ Folder with token generator.
CMakeLists.txt -------- Application CMake file.
main.cpp -------------- Application source code file.
LicenseGenerator ---------- Folder with license generator.
CMakeLists.txt -------- Application CMake file.
main.cpp -------------- Application source code file.
LicenseTest --------------- Folder with license validator.
CMakeLists.txt -------- Application CMake file.
main.cpp -------------- Application source code file.
License class description
License class declaration
The License interface class is declared in License.h. Every method is static and every parameter is a standard C++ type. The class holds no state and every method is thread-safe. No method throws: a failed memory allocation is reported with the failure value of the method. Class declaration:
namespace rapidpixel
{
/// License class.
class License
{
public:
/// Get the library version.
static std::string getVersion();
/// Generate a vendor key pair.
static bool generateKeys(std::string &privateKey,
std::string &publicKey);
/// Generate the token of the computer this runs on.
static std::string getToken();
/// Generate a license string from a token.
static bool getLicense(const std::string &token,
const std::string &modules, int days,
const std::string &privateKey,
std::string &license);
/// Check whether a license unlocks one module.
static bool checkLicense(const std::string &license,
const std::string &moduleId,
const std::string &publicKey);
/// Get the number of days a license still has to run.
static bool getLicenseDaysLeft(const std::string &license,
const std::string &publicKey,
int &daysLeft);
/// List the modules a license unlocks.
static bool getLicenseModules(const std::string &license,
const std::string &publicKey,
std::string &modules);
/// Get the name of the cryptographic backend in use.
static std::string getCryptoBackend();
/// Report whether the FIPS provider was selected.
static bool isFipsMode();
/// Run the cryptographic known-answer self-tests.
static bool selfTest();
/// Point the library at an OpenSSL configuration file.
static bool setCryptoConfig(const std::string &configPath,
const std::string &moduleDir = "");
};
}
getVersion method
The getVersion() method returns the library version string without initializing cryptography. Method declaration:
static std::string getVersion();
The method can be used without a License instance:
std::cout << "License class version: " << rapidpixel::License::getVersion();
Console output:
License class version: 2.1.0
generateKeys method
The generateKeys(…) method creates a vendor key pair. Run it once, on a computer you trust, and use a separate key pair for each product: that is what keeps a license for one product from being accepted by another. The private key is returned as text in the memory of the caller, who must overwrite it after use. Method declaration:
static bool generateKeys(std::string &privateKey, std::string &publicKey);
| Parameter | Value |
|---|---|
| privateKey | Reference to store the private key, 64 lowercase hex characters. Secret. Never ship it and never commit it. Empty after a failure. |
| publicKey | Reference to store the public key, 130 lowercase hex characters. Safe to publish and to compile into the application. Empty after a failure. |
Returns: TRUE if the key pair is generated or FALSE if not.
getToken method
The getToken() method generates a computer token that the customer sends to the vendor. It hashes the first available identifier: the OS installation ID, a hardware-backed network adapter address, or a readable firmware UUID on Linux. Software-only network interfaces are filtered, but virtual hardware may still be reported as hardware-backed. This is not hardware attestation: cloned installations and machines exposing the same identifier can have the same token. Method declaration:
static std::string getToken();
Returns: a token of 32 lowercase hex characters, or an empty string when no identifier is available or cryptography fails.
getLicense method
The getLicense(…) method generates a license from a token string, a list of modules and a number of days. The resulting string may be written to any file. One license can unlock several modules at once, so a customer who buys three modules still receives a single file. This is the vendor side and needs the private key, which is passed as text in the memory of the caller; the caller must overwrite it after use. Method declaration:
static bool getLicense(const std::string &token,
const std::string &modules, int days,
const std::string &privateKey,
std::string &license);
| Parameter | Value |
|---|---|
| token | Token string received from the customer: exactly 32 hex characters, as returned by getToken(). |
| modules | Module name or comma-separated list, for example tracking,detector. Names use ASCII letters, digits, _, - and ., with at most 32 characters each. Case is ignored and surrounding spaces and tabs are trimmed. The sorted, deduplicated list, including commas, must fit in 255 bytes. |
| days | Validity period from 1 to 36500 days, each 86400 seconds, starting at issuance rather than first use. |
| privateKey | Vendor private key from generateKeys(): exactly 64 hex characters. |
| license | Reference to store the uppercase hex license string; cleared on failure. |
Returns: TRUE if a license is created or FALSE if not.
checkLicense method
The checkLicense(…) method checks whether a license unlocks one module. It verifies the signature, then that the license was issued for this computer, has not expired, and names the module asked about. Call it once per module, at the place where that module is used. Method declaration:
static bool checkLicense(const std::string &license,
const std::string &moduleId,
const std::string &publicKey);
| Parameter | Value |
|---|---|
| license | License string read from your file. Hex characters only: strip any trailing newline or carriage return a text file adds. At most 1076 characters are accepted. |
| moduleId | Module to ask about, for example tracking. Case-insensitive. Exactly one name; a list is not accepted here. |
| publicKey | Vendor public key compiled into the application: exactly 130 hex characters. |
Returns: TRUE if license is valid for this computer, not expired and unlocks that module, or FALSE.
getLicenseDaysLeft method
The getLicenseDaysLeft(…) method extracts the remaining validity from a license, so an application can warn a customer before it runs out. It checks the signature but applies no computer or expiry rule, so it still reports a value for a license that has expired or belongs to another machine. Method declaration:
static bool getLicenseDaysLeft(const std::string &license,
const std::string &publicKey,
int &daysLeft);
| Parameter | Value |
|---|---|
| license | License string read from your file. |
| publicKey | Vendor public key compiled into the application. |
| daysLeft | Remaining whole days, rounded down and clamped to [-36500, 36500]. It reads 0 in the final partial day and -1 in the first day after expiry. Reset to 0 on failure; use checkLicense() to decide validity, including at the exact expiry time. |
Returns: TRUE if the signature is valid and the information is extracted or FALSE if not.
getLicenseModules method
The getLicenseModules(…) method lists the modules a license unlocks, which is useful in an “About” dialog and in support correspondence. Only the signature is checked, so it also reports the modules of a license that has expired or belongs to another computer. Method declaration:
static bool getLicenseModules(const std::string &license,
const std::string &publicKey,
std::string &modules);
| Parameter | Value |
|---|---|
| license | License string read from your file. |
| publicKey | Vendor public key compiled into the application. |
| modules | Reference to store the comma-separated list, in lowercase and sorted. Empty after a failure. |
Returns: TRUE if the signature is valid and the list is extracted or FALSE if not.
getCryptoBackend method
The getCryptoBackend() method returns the name of the cryptographic backend selected at run time. Method declaration:
static std::string getCryptoBackend();
Returns: a backend name and mode, for example openssl-3.0.13 (FIPS mode) or windows-cng (standard mode), or unavailable if initialization failed. The version identifies the OpenSSL library, not necessarily the separately installed FIPS module.
isFipsMode method
The isFipsMode() method reports whether CryptoProvider runs in its FIPS mode: the FIPS module of the OpenSSL backend was loaded and its settings enforced during initialization, or the Windows FIPS policy is on (CNG backend). It does not check a CMVP certificate or continuously monitor module health. Log it at startup and require it, along with successful cryptographic operations, when the deployment requires FIPS. The module version, platform and configuration must separately satisfy the applicable security policy. Method declaration:
static bool isFipsMode();
Returns: TRUE when FIPS was selected at initialization, or FALSE otherwise.
selfTest method
The selfTest() method checks SHA-256 and ECDSA signature verification against fixed known answers once per process; when the FIPS module of the OpenSSL backend is in force, its own self-tests are run too. Later calls return the cached result; license operations also consult it. Calling this at startup checks the selected backend before a license is available. These application-level checks do not replace the self-tests of a FIPS module. Method declaration:
static bool selfTest();
Returns: TRUE if every self-test passed or FALSE if not.
setCryptoConfig method
The setCryptoConfig(…) method records an OpenSSL configuration file and an optional provider directory for the FIPS module that CryptoProvider loads. Call it before any method except getVersion(), because the backend is selected once on first use. Only the OpenSSL backend (Linux, or Windows with the OpenSSL backend) has a FIPS module to locate: with the Windows CNG backend the method returns FALSE. Parsing is deferred: after this call succeeds, check selfTest() and, when required, isFipsMode().
The environment variable OPENSSL_CONF does not select the configuration file and OPENSSL_MODULES does not select the provider search directory (CryptoProvider ignores both for this library). This is not isolation from the entire process environment: OpenSSL configurations can contain environment substitutions, includes and explicit module paths, and the host process has its own OpenSSL initialization and dynamic-loader behavior. Protect the configuration, included files, provider modules and their parent directories. Avoid environment-dependent or relative paths in security-sensitive configuration. If the host application has initialized CryptoProvider itself, its choice is used and the method returns FALSE. Method declaration:
static bool setCryptoConfig(const std::string &configPath,
const std::string &moduleDir = "");
| Parameter | Value |
|---|---|
| configPath | Readable regular OpenSSL configuration file. For FIPS, include the installed module’s integrity configuration. |
| moduleDir | Existing, trusted provider directory. Empty uses the directory compiled into OpenSSL. Prefer absolute paths and verify directory permissions. |
Returns: TRUE if the settings were recorded; FALSE for invalid paths, an unreadable configuration file, a backend without a FIPS module, or an already selected backend (selected by a call of this library or by CryptoProvider::init() of the host application). TRUE does not mean the file parsed successfully or that FIPS is available. A recorded configuration file that cannot be loaded makes initialization fail: cryptographic operations then fail and the standard mode is not used instead. A valid configuration without a loadable FIPS module selects the standard mode.
FIPS 140-3 compliance
This library has no FIPS validation certificate. It contains no cryptographic implementation: it consumes the cryptographic services of the CryptoProvider library, which are provided by the OpenSSL FIPS provider on Linux and by the Windows CNG on Windows, outside the cryptographic boundary of the module. A compatible FIPS module is preferred when available and configured; the standard mode is not a substitute for a validated deployment. FIPS 140-3 claims depend on the actual module and operating environment.
First, approved algorithms alone do not establish module validation. The NIST/CCCS CMVP validates specific modules and configurations. An independent implementation could undergo validation, but this project instead uses CryptoProvider for hashing and signatures. Merely building OpenSSL with FIPS support does not establish validation, and a FIPS 140-2 certificate is not a FIPS 140-3 certificate.
Second, license checking can consume approved cryptographic services, but the deployed module’s security policy, key handling, entropy requirements and supported operating environments still apply. Consult the OpenSSL FIPS provider documentation and the security policy associated with the exact certificate.
What you may state, and how:
- Identify the deployed FIPS module, its version and CMVP certificate, including the standard it was validated against. Confirm its current status and the applicability of its security policy to your platform and configuration.
- Declare in writing that
rapidpixel::Licenselies outside the cryptographic boundary and is a consumer of the module’s signature and message-digest services through CryptoProvider, the single library that holds every cryptographic call of this library. - The library uses ECDSA P-256 key generation, signing and verification, key validation and SHA-256, with provider-managed random generation as needed. Raw private-key input and output also exist outside the cryptographic boundary and require protection.
- Log the backend and
isFipsMode()at startup, requireselfTest()to succeed, and handle each operation’s failure. These checks are useful operational evidence, not proof of certification or product compliance. The FIPS test helper requires an installed module and fails if the test processes fall back to the standard mode.
Security measures
The measures of the library that matter for the security assessment of a product that uses it. They do not replace the assessment of the product.
Table 2 - Security measures.
| Topic | Measure |
|---|---|
| License verification | Licenses are verified with a public key, so producing one requires the vendor’s private key. |
| Input parsing | The parser bounds decoded license lengths and module lists. Regression tests and optional ASan/UBSan builds support testing; dependency monitoring (CryptoProvider and, through it, OpenSSL), security review and vulnerability handling are still required. |
| Key material | No secret key material is embedded in a shipped binary; the private key is supplied by the vendor at issuing time only. |
| Integrity | A signature covers the computer digest, expiry and modules with a domain tag. The tag separates protocols with different tags, but does not prevent replay of an existing license or use across products sharing a key and format. |
| Data minimisation | Tokens and licenses contain a digest rather than the raw identifier. This is not anonymisation: low-entropy identifiers can be recovered by search, and tokens can enable correlation. Treat them as potentially personal identifiers. |
| Attack surface | The library does not invoke a shell, and the environment variables OPENSSL_CONF and OPENSSL_MODULES do not choose the FIPS module (CryptoProvider ignores them for this library). Trusted configuration contents, includes, explicit provider paths and the host process environment remain part of the deployment’s trust boundary. |
| Exploitation mitigation | GCC/Clang builds enable stack protection, stack-clash protection, control-flow protection where the compiler supports it for the target, and _FORTIFY_SOURCE=2 in optimized builds; Linux ELF links also request full RELRO and a non-executable stack. MSVC builds request /GS, /sdl, /guard:cf and /permissive-, plus the linker mitigations /DYNAMICBASE, /NXCOMPAT and /guard:cf. The compiler flags apply to the code of this repository; the linker options are public options of the License target and are also applied to the programs that link it. CryptoProvider is built with its own flags. Verify the final shipped binaries. |
| Vulnerability handling | Not covered by the library. Publish a coordinated vulnerability disclosure policy, a security contact, a software bill of materials and a record of fixed vulnerabilities for your product. |
Usage
This section explains, step by step, everything that has to happen to license an application. Two different people are involved and it is important not to confuse them:
- You, the vendor. You build the application and you create licenses. You own a secret signing key.
- Your customer, the user. They run the application and receive a license from you. They never see your secret key.
The whole scheme rests on one idea: the secret key creates licenses, and the key inside your application only checks them. Checking cannot create. That is why it is safe to ship the checking key inside your application, and why a customer cannot make their own license or edit the one you sent them.
Before you start, build the library and its example programs as described in Build and connect to your project. Single-configuration builds place them in build/bin; multi-configuration generators usually add a configuration directory such as Release. KeyGenerator and LicenseGenerator handle the private key and belong on the vendor’s trusted system. TokenGenerator prints a token. LicenseTest demonstrates validation, but reads public.key from a file; a production application must anchor trust in its own embedded or otherwise protected public key. File paths in the examples are relative to the working directory. Run vendor tools in an access-restricted directory, not a shared download or temporary directory.
Step 1. Create your key pair, once
Do this once, on a computer you trust. You will not repeat it for each customer.
./KeyGenerator
It creates private.key and public.key without overwriting existing files, and does not print the private key. Keep private.key secret: never ship or commit it, and back it up securely. On Linux the new private file is restricted to its owner; on Windows prepare a directory with an appropriate ACL. If the key leaks, replace the trusted public key in the product and reissue licenses under a new key pair; existing binaries trusting the old key remain vulnerable. For non-exportable HSM or cloud-managed keys, a separate issuer integration is needed: the current getLicense() API requires the raw private scalar.
In code the same call is:
#include <stdexcept>
#include <string>
#include "License.h"
std::string privateKey;
std::string publicKey;
if (!rapidpixel::License::generateKeys(privateKey, publicKey))
{
throw std::runtime_error("Key generation failed");
}
Step 2. Put the public key into your application
The public key is a plain string. Replace the illustrative value below with the complete key from your own public.key and compile it into your application:
static const std::string PUBLIC_KEY =
"049cf38417a13b8624babd87b5e3dbace0fccedb32ca811c2fce87b30f82e66d15"
"1c660ca8434826acfa4ab54835f1cf8e5fc64c6a03655dfe5b11bdae04e88658";
One key pair covers the whole product, because the modules a license unlocks are named inside the license itself. Use a separate key pair only when you sell genuinely separate products.
Step 3. The customer sends you a token
The customer runs the token tool you ship alongside your application:
./TokenGenerator
Send this token to your software vendor:
208a3310a33ae76291937359ffc79e30
They copy that line and send it to you with their order. To show the same value from inside your own application, call rapidpixel::License::getToken(). Run it with the same user account that runs the licensed application, so it sees the same identifiers.
Step 4. Create the license
On your machine, with your private key, run the generator and answer its questions:
./LicenseGenerator
Enter token: 208a3310a33ae76291937359ffc79e30
Enter modules (comma separated, e.g. tracking,detector): tracking,detector
Enter number of days: 365
It writes license.key, one file that unlocks both modules. In code the same call is:
#include <stdexcept>
#include <string>
#include "License.h"
// The token received from the customer and the private key from Step 1.
const std::string token = "208a3310a33ae76291937359ffc79e30";
const std::string privateKey = "<64 hex characters of private.key>";
std::string license;
if (!rapidpixel::License::getLicense(token, "tracking,detector", 365,
privateKey, license))
{
throw std::runtime_error("License generation failed");
}
Sell a single module by naming just that one, and the whole product by naming them all. Module names are yours to choose; decide them once and keep them stable, because they are written into every license you issue.
Step 5. The customer installs the license
Send the license string to the customer. It is not a signing secret, but it contains a correlatable computer token and can be reused on a cloned installation with the same identifier. Use a delivery channel appropriate for that data; email, a download page or a customer portal can all work.
The customer saves it where your application looks for it. You choose that location; common choices are ~/.config/YourProduct/license.key on Linux and %PROGRAMDATA%\YourProduct\license.key on Windows. The library does not read files itself: your application loads the text and passes it in, which leaves you free to keep the license in a configuration file, a registry value or a database instead.
Step 6. Check the license in your application
#include <iostream>
#include <string>
#include "License.h"
// The public key of Step 2, the license text loaded by the application
// (Step 5) and the functions of your own modules.
extern const std::string PUBLIC_KEY;
extern std::string license;
void startTracking();
void startDetector();
void onTrackingRequested();
void onDetectorRequested();
// Ask about a module where that module is actually used.
void onTrackingRequested()
{
if (!rapidpixel::License::checkLicense(license, "tracking", PUBLIC_KEY))
{
std::cout << "No valid license for tracking on this computer."
<< std::endl;
return;
}
startTracking();
}
void onDetectorRequested()
{
if (!rapidpixel::License::checkLicense(license, "detector", PUBLIC_KEY))
{
std::cout << "No valid license for detector on this computer."
<< std::endl;
return;
}
startDetector();
}
Check where each module is used and repeat checks as needed in long-running operations. Rechecking observes expiry and module permissions, but is not tamper resistance: patching the shared checking function can still bypass every call. A FALSE result can mean expiry, a different computer, a missing module, malformed input or a cryptographic failure.
To warn a customer before the license runs out, or to show what they have bought:
#include <iostream>
#include <string>
#include "License.h"
// The public key of Step 2 and the license text loaded by the application.
extern const std::string PUBLIC_KEY;
extern std::string license;
int daysLeft = 0;
if (rapidpixel::License::getLicenseDaysLeft(license, PUBLIC_KEY, daysLeft) &&
daysLeft >= 0 && daysLeft < 14)
{
std::cout << "Your license expires in " << daysLeft << " days."
<< std::endl;
}
std::string modules;
if (rapidpixel::License::getLicenseModules(license, PUBLIC_KEY, modules))
{
std::cout << "Your license covers: " << modules << std::endl;
}
Common questions
What happens when a license expires while the application is running? Nothing, until you check again. A license is evaluated only when you call checkLicense(...). Call it periodically if you want a long-running process to notice.
How does a customer renew? Issue a new license and replace the file. Its period starts at issuance, not at the expiry of the replaced license or the customer’s first use.
What if the customer changes their hardware? The license remains valid while the particular identifier used for issuance is still collected. Replacing a network card normally does not matter when the license uses the OS installation ID. A license bound to the removed card itself will stop matching. An OS reinstall can also change the token. Issue a replacement when the original identifier is unavailable.
Can one license be used on two computers? It is refused when none of the other computer’s collected identifiers matches. Cloned OS images, spoofed identifiers or copied virtual machines can defeat this binding; there is no online duplicate-use detection.
How do I license modules separately? Name them when you issue the license. One license can cover several modules, so a customer still receives a single file, and each module of your application asks about its own name. A license issued for tracking will not unlock detector.
What if a customer later buys another module? Issue a new license naming every module they own, and they replace the file. There is no need to keep several license files side by side.
Can I cancel a license I have already issued? Not remotely. The library checks offline and has no way to reach you, so an issued license remains valid until its expiry date. Your practical control is the validity period: short terms, renewed regularly, limit the damage.
Does the customer need an internet connection? No. Everything described here works entirely offline.
How strong is this against a determined attacker? With a secure private key and cryptographic implementation, forging new signed license data is not feasible. Local enforcement is different: application patching, identifier cloning and system-clock rollback are outside this library’s protection. UTC encoding avoids timezone errors, not clock tampering. Stronger enforcement needs additional trusted hardware or an online service; short validity periods alone do not prevent rollback.
Build and connect to your project
Requirements: CMake 3.13 or newer, a C++17 compiler and standard library with filesystem support, and the CryptoProvider library (a separate repository). On Linux CryptoProvider uses OpenSSL 3.x, so the OpenSSL 3.x development headers and libraries are required there; on Windows it uses the native CNG and nothing else is required. Without OpenSSL 3.x on Linux CryptoProvider uses its standalone implementations, which have no ECDSA: License is not usable then (selfTest() returns FALSE). The library is built and tested with GCC 12 and 13, Clang 15 and 18 (Linux) and Visual Studio 2022 (Windows, x64). A FIPS deployment additionally needs a compatible validated module and its installed configuration.
Build stand alone
The repository does not contain sub-repositories. The CryptoProvider library must be cloned into the same folder as License. The folder structure must be:
<work folder>
License
CryptoProvider
Typical commands to build the library, the test application and the examples (use the URLs of the repositories provided to you):
git clone <License repository URL>
git clone <CryptoProvider repository URL>
cd License
mkdir build
cd build
cmake ..
make
If License is the top-level CMake project, the main CMakeLists.txt file adds the folder ../CryptoProvider before the library itself. If the folder does not exist CMake stops with an error. On Windows use cmake --build . --config Release instead of make. Run the tests with ctest (on Windows ctest -C Release). Test licenses and tokens require a readable machine identifier. For sanitizer runs enable CR_LICENSE_SANITIZE. The helper tests/run_fips_tests.sh runs the tests against a FIPS module and requires the environment variable FIPS_PREFIX with the prefix of a compatible installed FIPS module.
CMake options
Table 3 - CMake options of the repository. In the options below ${PARENT} is the prefix defined by the top-level project (empty if License is the top-level project).
| Option | Default | Description |
|---|---|---|
${PARENT}_LICENSE | ON | Build the library. With an empty PARENT, the option is _LICENSE. |
${PARENT}_LICENSE_EXAMPLES | ON stand alone, OFF included in other project | Build example utilities. |
${PARENT}_LICENSE_TESTS | ON stand alone, OFF included in other project | Build the test suite. |
| BUILD_TESTING | ON when tests are enabled | Standard CTest switch; OFF also suppresses this project’s tests. |
| CR_LICENSE_SANITIZE | OFF | Build the library and its consumers with ASan/UBSan on non-MSVC toolchains. |
| CMAKE_BUILD_TYPE | Release (if not specified) | Build type. |
${PARENT}_CRYPTOPROVIDER_BACKEND | AUTO | Backend of CryptoProvider: AUTO (CNG on Windows; on other systems OpenSSL 3.0 or newer if it is found, otherwise CUSTOM, with which License builds but is not usable), CNG, OPENSSL or CUSTOM (the standalone implementations, which have no ECDSA: License is not usable with it). It is an option of CryptoProvider: set it in the parent project, or pass -D_SUBMODULE_CACHE_OVERWRITE=OFF -D_CRYPTOPROVIDER_BACKEND=<value> in a stand alone build. |
| OPENSSL_ROOT_DIR | not set | Root of the OpenSSL installation, for the OpenSSL backend of CryptoProvider (option of the CMake OpenSSL module). |
Connect to your project
The library depends on the CryptoProvider library only. To include License as source code in a CMake project, follow the steps below, which add CryptoProvider as well. For example, start with this repository structure:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
Create a 3rdparty directory and place the CryptoProvider and License repositories inside it:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
3rdparty
CryptoProvider
License
Create 3rdparty/CMakeLists.txt with the following content:
cmake_minimum_required(VERSION 3.13)
################################################################################
## 3RD-PARTY
## dependencies for the project
################################################################################
project(3rdparty LANGUAGES CXX)
################################################################################
## SETTINGS
## basic 3rd-party settings before use
################################################################################
# To inherit the top-level architecture when the project is used as a submodule.
SET(PARENT ${PARENT}_YOUR_PROJECT_3RDPARTY)
# Disable self-overwriting of parameters inside included subdirectories.
SET(${PARENT}_SUBMODULE_CACHE_OVERWRITE OFF CACHE BOOL "" FORCE)
################################################################################
## CONFIGURATION
## 3rd-party submodules configuration
################################################################################
SET(${PARENT}_SUBMODULE_CRYPTOPROVIDER ON CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_CRYPTOPROVIDER)
SET(${PARENT}_CRYPTOPROVIDER ON CACHE BOOL "" FORCE)
SET(${PARENT}_CRYPTOPROVIDER_TEST OFF CACHE BOOL "" FORCE)
SET(${PARENT}_CRYPTOPROVIDER_EXAMPLE OFF CACHE BOOL "" FORCE)
endif()
SET(${PARENT}_SUBMODULE_LICENSE ON CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_LICENSE)
SET(${PARENT}_LICENSE ON CACHE BOOL "" FORCE)
SET(${PARENT}_LICENSE_TESTS OFF CACHE BOOL "" FORCE)
SET(${PARENT}_LICENSE_EXAMPLES OFF CACHE BOOL "" FORCE)
endif()
################################################################################
## INCLUDING SUBDIRECTORIES
## Adding subdirectories according to the 3rd-party configuration
################################################################################
if (${PARENT}_SUBMODULE_CRYPTOPROVIDER)
add_subdirectory(CryptoProvider)
endif()
if (${PARENT}_SUBMODULE_LICENSE)
add_subdirectory(License)
endif()
This adds CryptoProvider and then License to the project, in dependency order; examples and tests default to OFF when embedded, while explicit cache choices are preserved. Link the License target to obtain its headers, C++17 requirement and dependencies, CryptoProvider included. The public dependencies are CryptoProvider (with the library of its backend: libcrypto of OpenSSL or bcrypt of Windows), the threads library (Threads::Threads) and, on Windows, iphlpapi, advapi32 and ws2_32; a program that links License needs them too. License does not add CryptoProvider itself when it is included in another project: the parent project has to add it, as above. Without it the compiler stops with CryptoProvider.h: No such file or directory. Your repository structure will look like:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
3rdparty
CMakeLists.txt
CryptoProvider
License
Add 3rdparty to the root CMakeLists.txt before adding the application directory that links License:
add_subdirectory(3rdparty)
Link the library to your existing application target in src/CMakeLists.txt:
target_link_libraries(yourApplication PRIVATE License)
Replace yourApplication with the actual target name, then include License.h in your source files. The examples and the tests of License are not built when it is included in another project, unless ${PARENT}_LICENSE_EXAMPLES or ${PARENT}_LICENSE_TESTS is set. On Windows, use a generator supported by the installed toolchain and select the desired configuration in the IDE.
Portability
The computer identification is implemented for Linux and Windows. On another platform getToken() returns an empty string and checkLicense(...) returns FALSE. The license format stores every integer in an explicit big-endian layout, so it does not depend on the byte order or on the structure padding of the computer. The library has no processor-specific code: it uses no SIMD instructions and no run-time detection of processor features. Cryptography is provided by CryptoProvider: OpenSSL 3.x on Linux and the native CNG on Windows; the OpenSSL backend can be selected on Windows too. On Linux OpenSSL 3.0 or newer is required: without it CryptoProvider builds its standalone backend, which has no ECDSA, and the library builds but does not work (selfTest() returns FALSE). The library is built and the tests are run with GCC 12 and 13 and Clang 15 and 18 on x86-64 Linux and with Visual Studio 2022 on x64 Windows, with the CNG and with the OpenSSL backend. The sources also compile for aarch64 with the GCC 13 cross compiler; the tests are not run on aarch64.
Example
A complete application that checks a license and refuses to run without one:
#include <fstream>
#include <iostream>
#include <string>
#include "License.h"
// Your own feature switches.
static void enableTracking(bool)
{
}
static void enableDetector(bool)
{
}
// Illustrative public key: replace it with your own KeyGenerator output.
static const std::string PUBLIC_KEY =
"049cf38417a13b8624babd87b5e3dbace0fccedb32ca811c2fce87b30f82e66d15"
"1c660ca8434826acfa4ab54835f1cf8e5fc64c6a03655dfe5b11bdae04e88658";
int main()
{
std::cout << "License library " << rapidpixel::License::getVersion()
<< std::endl;
std::cout << "Crypto backend: "
<< rapidpixel::License::getCryptoBackend()
<< (rapidpixel::License::isFipsMode() ? " [FIPS]" : "")
<< std::endl;
// A product should refuse to run when its own cryptography is unwell.
if (!rapidpixel::License::selfTest())
{
std::cerr << "Cryptographic self-test failed!" << std::endl;
return -1;
}
// Read the license from wherever your application keeps it.
std::ifstream file("license.key");
if (!file)
{
std::cerr << "No license file found." << std::endl;
std::cout << "Send this token to your vendor: "
<< rapidpixel::License::getToken() << std::endl;
return -1;
}
std::string license;
std::getline(file, license);
// A file written on Windows carries a trailing carriage return, which
// is not part of the license string.
while (!license.empty() &&
(license.back() == '\r' || license.back() == '\n'))
{
license.pop_back();
}
// "tracking" is the module this program needs in order to start at
// all; optional modules are asked about where they are used.
if (!rapidpixel::License::checkLicense(license, "tracking", PUBLIC_KEY))
{
std::cerr << "Invalid license for this computer!" << std::endl;
return -1;
}
int daysLeft = 0;
std::string modules;
if (!rapidpixel::License::getLicenseDaysLeft(license, PUBLIC_KEY, daysLeft) ||
!rapidpixel::License::getLicenseModules(license, PUBLIC_KEY, modules))
{
std::cerr << "Cannot read license details." << std::endl;
return -1;
}
std::cout << "License valid!" << std::endl;
std::cout << "Modules : " << modules << std::endl;
std::cout << "Days left: " << daysLeft << std::endl;
if (daysLeft < 14)
{
std::cout << "Warning: this license expires soon." << std::endl;
}
// Switch features on according to what the license names.
enableTracking(rapidpixel::License::checkLicense(license, "tracking",
PUBLIC_KEY));
enableDetector(rapidpixel::License::checkLicense(license, "detector",
PUBLIC_KEY));
return 0;
}