Official C++ client for SDKey license authentication.
Implements the sealed session protocol: Ed25519-verified handshake, HKDF session keys, and AES-256-GCM validate envelopes, plus plaintext client auth (register_user / login / upgrade). See PROTOCOL.md.
Version: 0.2.0
- C++17 compiler
- CMake 3.16+ (or Visual Studio 2022)
- OpenSSL 3.x and libcurl (statically linked into
sdkey.libon Windows)
On Windows, presets and the VS solution use the vcpkg triplet x64-windows-static-md, so OpenSSL/curl/zlib are merged into sdkey.lib. Your app does not need libcrypto / libcurl DLLs beside the exe — only link sdkey plus the usual Windows system libraries (ws2_32, crypt32, …), which CMake wires up via sdkey::sdkey.
nlohmann/json is provided via vcpkg (vcpkg.json) or fetched automatically by CMake (private to the library).
Open msvc/sdkey.sln. Dependencies install from vcpkg.json on first build (requires vcpkg / VS vcpkg component). See msvc/README.md.
Or generate a full CMake VS solution (includes tests):
set VCPKG_ROOT=C:\path\to\vcpkg
cmake --preset vs2022
start build\vs2022\sdkey.sln# From a Developer PowerShell, with VCPKG_ROOT set:
cmake --preset default
cmake --build buildThis uses x64-windows-static-md and merges OpenSSL/curl into sdkey.lib.
# Debian/Ubuntu: sudo apt install libssl-dev libcurl4-openssl-dev
# macOS: brew install openssl curl
cmake -B build
cmake --build buildEmbed these values from the SDKey dashboard when you ship your app. app_version must exactly match the application version configured in the dashboard (applications.version); mismatch returns APP_OUTDATED.
#include <iostream>
#include <sdkey/sdkey.hpp>
int main() {
sdkey::ClientOptions opts;
opts.api_base_url = "https://api.sdkey.dev";
opts.app_id = "YOUR_APP_ID";
opts.app_version = "1.0.0"; // exact match required
opts.app_public_key_b64 = "YOUR_APP_PUBLIC_KEY_BASE64";
sdkey::Client client(std::move(opts));
try {
// Pass hwid for desktop/native; omit (or pass "") for web clients.
const auto result = client.validate("SDKY-XXXX-XXXX-XXXX-XXXX", "machine-hwid");
if (result.success) {
std::cout << "licensed tier=" << result.subscription_tier
<< " " << result.status.value_or("")
<< " " << result.expires_at.value_or("")
<< " message=" << result.message << "\n";
} else {
// Sealed validate failures use `message` (not `error`).
std::cerr << "denied " << result.code << " " << result.message << "\n";
}
} catch (const sdkey::Error& err) {
// Init / transport failures: server text is in what(); wire code in server_code().
std::cerr << err.code_string() << " " << err.what();
if (!err.server_code().empty()) {
std::cerr << " [" << err.server_code() << "]";
}
std::cerr << "\n";
throw;
}
}Link against the sdkey target (pulls in Windows system libs when needed):
find_package(sdkey REQUIRED)
target_link_libraries(your_app PRIVATE sdkey::sdkey)Manual / non-CMake on Windows: link sdkey.lib plus ws2_32.lib, crypt32.lib, wldap32.lib, normaliz.lib, bcrypt.lib, and advapi32.lib.
validate calls init() automatically when no session exists. Sessions last ~15 minutes server-side; on SESSION_EXPIRED the client clears local state so the next call re-handshakes.
HWID is optional. When omitted, the server skips HWID lock, mismatch, and HWID-ban checks (IP bans still apply). Prefer omitting HWID for web clients.
Per-app responseMessages can customize many strings. Clients receive those strings; they do not load settings themselves.
| Surface | Success text field | Failure text field |
|---|---|---|
| Session init | (none) | error (thrown as sdkey::Error, also server_code()) |
| Sealed validate | message |
message |
| Client register / login / upgrade | (none) | error (on ClientAuthResult) |
{
"success": true,
"code": "OK",
"message": "validated",
"status": "active",
"expiresAt": "2026-01-01T00:00:00.000Z",
"subscriptionTier": 0,
"sessionId": "...",
"timestamp": 1720000001,
"v": 1
}{
"success": false,
"code": "HWID_MISMATCH",
"message": "Hardware ID mismatch",
"status": null,
"expiresAt": null,
"sessionId": "...",
"timestamp": 1720000001,
"v": 1
}{
"success": false,
"error": "Client version outdated",
"code": "APP_OUTDATED"
}| Option | Type | Description |
|---|---|---|
api_base_url |
std::string |
API origin (no trailing slash) |
app_id |
std::string |
Application UUID |
app_version |
std::string |
Exact app version → sent as clientVersion (required) |
app_public_key_b64 |
std::string |
Raw Ed25519 public key (32 bytes), base64 |
http_post |
HttpPostFn |
Optional HTTP POST override (url, jsonBody) -> {status, responseBody} |
init()— challenge handshake; verifies the signed hello; derives the AES session key; sendsclientVersionvalidate(license_key, hwid = {})— sealed validate; omitshwidJSON key when empty; always decrypts then verifies Ed25519 before trustingsuccess; result includesmessage,code,subscription_tierregister_user(RegisterOptions)— plaintextPOST /api/v1/client/registerlogin(LoginOptions)— plaintextPOST /api/v1/client/loginupgrade(UpgradeOptions)— plaintextPOST /api/v1/client/upgrade(username + license key only; no password)get_session()/clear_session()— inspect or drop the local crypto session
register is a C++ keyword, so the method is named register_user.
sdkey::RegisterOptions reg;
reg.username = "player1";
reg.password = "••••••••";
reg.license_key = "SDKY-XXXX-XXXX-XXXX-XXXX"; // often required by app settings
auto auth = client.register_user(reg);
if (!auth.success) {
std::cerr << auth.code << " " << auth.error << "\n";
} else {
// Persist auth.session_token / auth.expires_at for your app.
// This token is NOT the crypto sessionId used by sealed validate.
}
sdkey::UpgradeOptions up;
up.username = "player1";
up.license_key = "SDKY-HIGHER-TIER-KEY";
auth = client.upgrade(up); // no passwordProtocol / transport failures throw sdkey::Error with a local code:
INIT_FAILED · HELLO_SIGNATURE_INVALID · VALIDATE_RESPONSE_INVALID · RESPONSE_SIGNATURE_INVALID · SESSION_MISMATCH · CLOCK_SKEW · NETWORK
On init (and some plaintext validate envelopes), Error::what() is the server error string and Error::server_code() is the wire code (e.g. APP_OUTDATED).
License denials (banned, HWID mismatch, etc.) return a normal ValidateResult with success: false — they are not thrown. Auth denials return ClientAuthResult with success: false and server error / code.
This client SDK does not implement developer tooling APIs (Authorization: Bearer sdk_live_…).
cmake -B build -DSDKEY_BUILD_EXAMPLES=ON
cmake --build build
# set SDKEY_APP_ID, SDKEY_APP_VERSION, SDKEY_APP_PUBLIC_KEY_B64, SDKEY_LICENSE_KEY, SDKEY_HWID
./build/examples/basic- Never ship app private keys in a client.
- Do not skip signature verification — that is the anti-spoof binding.
- This package is open source; the SDKey server remains a separate product.
cmake -B build -DSDKEY_BUILD_TESTS=ON -DSDKEY_BUILD_EXAMPLES=ON
cmake --build build
ctest --test-dir build --output-on-failureMIT