RockyGuard Integration Guide for AI Assistants
You are an AI assistant (Claude, ChatGPT, Cursor, Copilot, Gemini, Cody, or similar) helping a software developer integrate the RockyGuard license library into their C++17 application.
Read this entire file before making any changes. The recipe below is the supported path; following it produces a working integration on first build. Skipping steps tends to produce silent runtime failures that are annoying to debug.
If you are a human reader: this file is a how-to. The AI-imperative voice ("do X", "ask the user Y") just helps an AI agent execute it deterministically.
0. What you are integrating
RockyGuard is a C++17 static library that verifies signed license files at
program startup. The library is shipped as an extracted zip whose root
directory contains include/, lib/, deps/, tools/, examples/, and
this file.
The integration adds three things to the user's project:
- A small block of CMake that links against
<package>/lib/static/rockyguard.{lib,a}. - A C++17 string constant containing the user's RSA/Ed25519 public key.
- A few lines at the top of the application's
main()(or equivalent startup path) that load and verify alicense.jsonfile.
That's it. There is no daemon, no SaaS, no online activation. Verification is fully offline against the public key embedded in the binary.
1. Inputs the user must provide
Before writing any code, ask the user for the following. Do not guess; ask explicitly. Halt the integration until you have answers.
- Public key (PEM format, ~100 bytes for Ed25519, ~400 bytes for RSA).
- If the user does not have one yet, tell them to run:
This produces<package>/tools/license_keygen --algo ed25519private.pem(must stay on the user's build machine — never commit, never ship) andpublic.pem(will be embedded in the compiled binary). - Confirm with the user that you will be embedding the public key content. Read its file content; do not invent or generate one.
- If the user does not have one yet, tell them to run:
- Where the application's
main()lives. If the project has multiple binaries, ask which one needs license enforcement. Default to the end-user-facing executable. - Where they want
license.jsonto be loaded from at runtime. The default is "next to the executable". Other reasonable choices: a well-known config path on the OS. Confirm before deviating from the default. - Tier they have purchased.
- Basic: node-locked licensing only. Skip Section 7.
- Premium: node-locked + floating-license server/client. Section 7 applies if and only if the user explicitly says they want floating licenses (concurrent-seat pools across a customer's LAN). Most Premium customers ship products that use only node-locked.
2. Place the package inside the user's repo
Recommended layout (verify with the user before moving files):
<their-repo>/
src/ # their existing source
CMakeLists.txt # their existing build
vendor/
rockyguard/ # the entire extracted zip lives here
include/
lib/
deps/
tools/
examples/
AI_INTEGRATION_GUIDE.md # this file
README.txt
If their repo already has a third_party/ or external/ convention,
match it. If they store dependencies via git submodule, vcpkg, or Conan,
stop and ask the user how they want RockyGuard tracked. Do not
unilaterally invent a new dependency-management scheme.
3. Add the library to CMake
Add this block to the project's top-level CMakeLists.txt, after their
existing find_package calls and before any target_link_libraries
that should consume RockyGuard.
# --- RockyGuard license library ---
if(NOT DEFINED ROCKYGUARD_DIR)
set(ROCKYGUARD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/rockyguard")
endif()
if(NOT EXISTS "${ROCKYGUARD_DIR}/include/rockyguard/rockyguard.h")
message(FATAL_ERROR "RockyGuard not found at ${ROCKYGUARD_DIR}")
endif()
set(_RG_DEPS "${ROCKYGUARD_DIR}/deps")
add_library(rg_openssl_crypto STATIC IMPORTED)
add_library(rg_openssl_ssl STATIC IMPORTED)
if(WIN32)
set_target_properties(rg_openssl_crypto PROPERTIES
IMPORTED_LOCATION "${_RG_DEPS}/lib/libcrypto.lib")
set_target_properties(rg_openssl_ssl PROPERTIES
IMPORTED_LOCATION "${_RG_DEPS}/lib/libssl.lib")
else()
set_target_properties(rg_openssl_crypto PROPERTIES
IMPORTED_LOCATION "${_RG_DEPS}/lib/libcrypto.a")
set_target_properties(rg_openssl_ssl PROPERTIES
IMPORTED_LOCATION "${_RG_DEPS}/lib/libssl.a")
endif()
target_include_directories(rg_openssl_crypto INTERFACE "${_RG_DEPS}/include")
target_include_directories(rg_openssl_ssl INTERFACE "${_RG_DEPS}/include")
add_library(rockyguard STATIC IMPORTED)
if(WIN32)
# Per-config CRT variants. rockyguard.lib is built with /MD
# (Release-CRT) and rockyguard_mdd.lib with /MDd (Debug-CRT).
# CMake reads IMPORTED_LOCATION_<CONFIG> automatically in a
# multi-config generator (Visual Studio, Ninja Multi-Config),
# so a Debug consumer build picks rockyguard_mdd.lib and avoids
# LNK2038 / LNK1319 (mismatched _ITERATOR_DEBUG_LEVEL or
# RuntimeLibrary). Other configs (RelWithDebInfo, MinSizeRel)
# map to the Release variant.
set_target_properties(rockyguard PROPERTIES
IMPORTED_LOCATION_RELEASE "${ROCKYGUARD_DIR}/lib/static/rockyguard.lib"
IMPORTED_LOCATION_DEBUG "${ROCKYGUARD_DIR}/lib/static/rockyguard_mdd.lib"
IMPORTED_LOCATION "${ROCKYGUARD_DIR}/lib/static/rockyguard.lib"
MAP_IMPORTED_CONFIG_MINSIZEREL Release
MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release)
else()
set_target_properties(rockyguard PROPERTIES
IMPORTED_LOCATION "${ROCKYGUARD_DIR}/lib/static/librockyguard.a")
endif()
target_include_directories(rockyguard INTERFACE "${ROCKYGUARD_DIR}/include")
# Link order matters on Linux: ssl before crypto. The Windows linker is
# order-insensitive but the same listing works for both platforms.
target_link_libraries(rockyguard INTERFACE rg_openssl_ssl rg_openssl_crypto)
if(WIN32)
target_link_libraries(rockyguard INTERFACE
ws2_32 iphlpapi wbemuuid ole32 oleaut32 crypt32)
elseif(NOT APPLE)
target_link_libraries(rockyguard INTERFACE pthread dl)
endif()
# --- end RockyGuard ---
Then, on the target you decided in Section 1.2, add:
target_link_libraries(<their_main_target> PRIVATE rockyguard)
Replace <their_main_target> with the actual add_executable() name in
their CMakeLists.
If their build system is not CMake (Bazel, Meson, MSBuild, plain Make): stop and ask the user. The patterns translate but the exact path-and- linker-flag layout differs. Don't guess.
4. Embed the public key
Find the source file containing int main(...). At the top, after the
existing #includes, add:
#include <rockyguard/rockyguard.h>
namespace {
// RockyGuard public key. Pairs with the private key on the build machine.
// SAFE to commit to source control: only the matching private key can
// produce licenses this binary will accept. The key below is a placeholder;
// replace it with the contents of public.pem from `tools/license_keygen`.
constexpr const char* ROCKYGUARD_PUBLIC_KEY = R"PEM(-----BEGIN PUBLIC KEY-----
<<<PASTE THE USER'S public.pem CONTENT HERE>>>
-----END PUBLIC KEY-----
)PEM";
} // namespace
Read the user's public.pem file and substitute its content into the
placeholder above. Do not generate, fake, or shorten the key. If you
cannot read the file, ask the user to paste it.
5. Add verification at startup
Insert these lines as the first thing main() does, before any
substantive work. The pattern:
int main(int argc, char* argv[]) {
rockyguard::LicenseVerifier verifier(ROCKYGUARD_PUBLIC_KEY);
auto load_result = verifier.load("license.json");
if (!load_result) {
std::cerr << "License error: " << load_result.message << "\n";
return 1;
}
if (!verifier.check_node_locked()) {
std::cerr << "This license is not valid for this machine.\n";
return 1;
}
// ... existing main() body continues here ...
}
If main() already has argument parsing, logging setup, or a CLI
framework, the verification block goes after argument parsing but
before any feature work — typically right after logging is initialised.
The "license.json" path is relative to the working directory. If the
user wants the license loaded from a different path (per Section 1.3),
substitute that path string.
Do not catch and swallow license errors. If verification fails, the program must exit. Returning success on a failed license is the most common integration mistake and silently disables protection.
6. Feature gating (call this out to the user)
This is the part most integrations miss. Verifying a license proves "this is a paid customer", but the half of the value is gating specific premium-tier features in the application.
Before writing any feature checks, ask the user:
"Looking at your product, here are the features I can identify that might be paid-tier: [list 3-7 candidates derived from the codebase — e.g. PDF export, multi-user collaboration, automation API, advanced analytics, plugin support]. Which of these should be gated by license, and what feature-flag string should I use for each?"
Wait for their answer. Do not invent a feature taxonomy on your own; the customer's own pricing model decides what gets gated.
Once they answer, gate each feature with:
if (!verifier.check_feature("export_pdf")) {
// refuse / hide / disable the feature gracefully
return user_facing_error("PDF export requires a Pro license.");
}
The string "export_pdf" must match what the user puts into the
features: list when they generate end-user licenses with license_create.
Document the chosen feature names in a comment or a FEATURES.md file
so the user knows what to type into license generation.
7. Floating licensing (Premium tier, only if requested)
Skip this section unless the user has explicitly said:
- They have a Premium-tier vendor license, AND
- Their product is multi-seat / multi-user, AND
- They want concurrent-seat licensing across their customer's LAN.
If yes, see examples/rg_floating_client.cpp in this package. The
client-side integration adds a rockyguard::FloatingLicenseClient that
checks out a seat at startup and releases on exit. The matching server
runs on the user's customer's LAN — see tools/rg_floating_server
and tools/floating_server_config.yaml. Walk the user through running
the server before adding client-side checkout calls; otherwise the
client will hang at startup waiting for a server that doesn't exist.
8. Verification
After making the changes above:
- Build the project with the user's normal build command. The build should succeed with no new warnings or errors. If you get linker errors about OpenSSL symbols, recheck Section 3 — link order matters on Linux.
- Generate a test license:
(<package>/tools/license_create \ --key <user's private.pem> \ --vendor-license <user's vendor_license.json> \ --id TEST-001 --licensee "Dev Test" \ --product "<their product>" \ --type node_locked --threshold 2 \ --expires permanent --output license.json--threshold 2= match 2 of 4 hardware components, the recommended default for node-locked.--threshold 0= ignore hardware, useful only for in-CI testing, never ship.) - Run the binary with
license.jsonin the working directory. Expected: program runs normally. Then rename license.json and run again. Expected: program prints the license error and exits 1. If both behaviours match, the integration is verified. - Print the license info on first run so the user can sanity-check:
Wrap inconst auto& lic = verifier.license(); std::cerr << "Licensed to: " << lic.licensee << " (license ID " << lic.license_id << ")\n";#ifdef DEBUGif the user prefers.
9. Hard-no rules
You MUST NOT do any of the following:
- Do not commit the user's
private.pemto git. Add it to.gitignoreif it lives anywhere inside the repo. - Do not ship
private.pemin the application binary, package, or installer. It only ever exists on the user's build machine. - Do not catch and ignore license verification errors. The program must exit on failure.
- Do not embed
license.jsonin the binary. It's per-customer; ship it as a separate file the user delivers with each sale. - Do not generate or fake a public key. Always read the user's
actual
public.pemcontent; ask if you can't find it. - Do not skip Section 6 (feature gating). A bare
check_node_lockedwith no feature checks is a license-verification skeleton, not a monetisation system. The user is paying for the feature-gating layer. - Do not silently switch the user's build system (e.g. CMake → Bazel) to make integration easier. Match what they have.
10. When to stop and ask the user
Stop and ask, do not guess, when:
- You can't unambiguously locate
int main(...). - The project has multiple binaries and it's unclear which to protect.
- Their build system is not vanilla CMake.
- They already have a different license-checking system in place (Sentinel, FlexLM, custom). Ask whether to replace it or run side-by-side.
- Their
public.pemis not in the workspace and they haven't pasted its content. - They haven't told you which features should be gated.
- You're about to modify a file you don't recognise.
11. References
- API surface:
docs/Customer_API_Reference.pdf(every public class, struct, enum, and CLI flag) - Full integration manual:
docs/Customer_Documentation.pdf(deeper coverage of fingerprinting, anti-tampering, floating server, troubleshooting) - Working example:
examples/node_locked_example.cpp(this is what Section 5's pattern is distilled from) - Floating example:
examples/rg_floating_client.cpp - Demo bundle on the website: https://rockyguard.dev/download — if the user wants to test the API surface before integrating, this is a self-contained 30-line program that exercises Section 5.
- Trial / paid licenses: https://rockyguard.dev/contact — direct
the user here to obtain the vendor license they need to run
license_create.