#pragma once // Keyguard C++ client — HMAC after init. Never put a seller key or website API key in this binary. // C++17. Windows: link winhttp.lib bcrypt.lib. JSON: nlohmann/json.hpp next to this file. #include #include #include #include #include #include #include #ifdef _WIN32 #define NOMINMAX #include #include #include #include #include #pragma comment(lib, "winhttp.lib") #pragma comment(lib, "bcrypt.lib") #pragma comment(lib, "crypt32.lib") #endif namespace keyguard { struct Response { bool success = false; nlohmann::json body; std::string message; explicit operator bool() const { return success; } }; class Client { public: explicit Client(std::string base_url = "https://api.keyguard.live") : base_(trim_slash(std::move(base_url))) {} void init(const std::string& app_name, const std::string& owner_id, const std::string& version = "", const std::string& hash = "") { nlohmann::json req{ {"name", app_name}, {"ownerId", owner_id} }; if (!version.empty()) req["ver"] = version; if (!hash.empty()) req["hash"] = hash; auto res = http_post("/api/client/v1/init", req); if (!res.value("success", false)) throw std::runtime_error(res.value("message", "init failed")); session_id_ = res.at("sessionid").get(); signing_key_ = b64decode(res.at("enckey").get()); } Response user_register(const std::string& username, const std::string& password, const std::string& license_key, const std::string& hwid) { return dispatch("register", { {"username", username}, {"password", password}, {"key", license_key}, {"hwid", hwid} }); } Response user_login(const std::string& username, const std::string& password, const std::string& hwid) { return dispatch("login", { {"username", username}, {"password", password}, {"hwid", hwid} }); } Response user_logout() { return dispatch("logout", nlohmann::json::object()); } Response user_change_password(const std::string& current_password, const std::string& new_password, bool invalidate_other = true) { return dispatch("changepassword", { {"current_password", current_password}, {"new_password", new_password}, {"invalidate_other_sessions", invalidate_other} }); } Response user_renew(const std::string& username, const std::string& password, const std::string& license_key) { return dispatch("renew", { {"username", username}, {"password", password}, {"key", license_key} }); } Response license_validate(const std::string& key, const std::string& hwid) { return dispatch("license", { {"key", key}, {"hwid", hwid} }); } Response check() { return dispatch("check", nlohmann::json::object()); } Response file(const std::string& file_id, const std::string& expected_sha256 = "") { auto r = dispatch("file", { {"fileid", file_id} }); if (r.success && !expected_sha256.empty()) { auto got = r.body.value("sha256", std::string{}); if (!ieq(got, expected_sha256)) { r.success = false; r.message = "SHA-256 mismatch — refuse this file."; } } return r; } Response updates() { return dispatch("updates", nlohmann::json::object()); } bool is_initialized() const { return !session_id_.empty(); } static std::string generate_hwid() { #ifdef _WIN32 wchar_t computer[MAX_COMPUTERNAME_LENGTH + 1]{}; DWORD n = MAX_COMPUTERNAME_LENGTH + 1; GetComputerNameW(computer, &n); DWORD serial = 0; GetVolumeInformationW(L"C:\\", nullptr, 0, &serial, nullptr, nullptr, nullptr, 0); auto raw = std::to_wstring(serial) + L"|" + computer; std::string utf8(raw.begin(), raw.end()); return sha256_hex(std::vector(utf8.begin(), utf8.end())); #else return "unknown-hwid"; #endif } private: std::string base_; std::string session_id_; std::vector signing_key_; Response dispatch(const std::string& type, const nlohmann::json& payload) { if (session_id_.empty()) throw std::runtime_error("Call init() first"); auto body = payload.dump(); auto timestamp = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count(); auto nonce = random_hex(8); auto canonical = type + "|" + std::to_string(timestamp) + "|" + nonce + "|" + session_id_ + "|" + body; auto signature = hmac_sha256_hex(signing_key_, canonical); nlohmann::json envelope{ {"type", type}, {"sessionid", session_id_}, {"timestamp", timestamp}, {"nonce", nonce}, {"signature", signature}, {"payload", payload} }; auto json = http_post("/api/client/v1/dispatch", envelope); Response out; out.body = json; out.success = json.value("success", false); out.message = json.value("message", std::string{}); return out; } static std::string trim_slash(std::string s) { while (!s.empty() && s.back() == '/') s.pop_back(); return s; } static bool ieq(const std::string& a, const std::string& b) { if (a.size() != b.size()) return false; unsigned diff = 0; for (size_t i = 0; i < a.size(); ++i) diff |= (unsigned char)tolower((unsigned char)a[i]) ^ (unsigned char)tolower((unsigned char)b[i]); return diff == 0; } #ifdef _WIN32 nlohmann::json http_post(const std::string& path, const nlohmann::json& body) { URL_COMPONENTS uc{}; uc.dwStructSize = sizeof(uc); wchar_t host[256]{}, urlpath[1024]{}; uc.lpszHostName = host; uc.dwHostNameLength = 256; uc.lpszUrlPath = urlpath; uc.dwUrlPathLength = 1024; auto full = std::wstring(base_.begin(), base_.end()) + std::wstring(path.begin(), path.end()); if (!WinHttpCrackUrl(full.c_str(), 0, 0, &uc)) throw std::runtime_error("Bad API URL"); auto session = WinHttpOpen(L"Keyguard/1", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, nullptr, nullptr, 0); auto connect = WinHttpConnect(session, host, uc.nPort, 0); DWORD flags = (uc.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0; auto request = WinHttpOpenRequest(connect, L"POST", urlpath, nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags); auto payload = body.dump(); std::wstring hdrs = L"Content-Type: application/json\r\n"; BOOL ok = WinHttpSendRequest(request, hdrs.c_str(), (DWORD)-1, (LPVOID)payload.data(), (DWORD)payload.size(), (DWORD)payload.size(), 0); if (!ok || !WinHttpReceiveResponse(request, nullptr)) throw std::runtime_error("HTTP failed"); std::string resp; DWORD avail = 0; while (WinHttpQueryDataAvailable(request, &avail) && avail) { std::string chunk(avail, 0); DWORD read = 0; WinHttpReadData(request, chunk.data(), avail, &read); chunk.resize(read); resp += chunk; } WinHttpCloseHandle(request); WinHttpCloseHandle(connect); WinHttpCloseHandle(session); return nlohmann::json::parse(resp.empty() ? "{}" : resp); } static std::string hmac_sha256_hex(const std::vector& key, const std::string& data) { BCRYPT_ALG_HANDLE alg{}; BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG); BCRYPT_HASH_HANDLE hash{}; BCryptCreateHash(alg, &hash, nullptr, 0, (PUCHAR)key.data(), (ULONG)key.size(), 0); BCryptHashData(hash, (PUCHAR)data.data(), (ULONG)data.size(), 0); uint8_t out[32]; BCryptFinishHash(hash, out, 32, 0); BCryptDestroyHash(hash); BCryptCloseAlgorithmProvider(alg, 0); return to_hex(out, 32); } static std::string sha256_hex(const std::vector& data) { BCRYPT_ALG_HANDLE alg{}; BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, nullptr, 0); BCRYPT_HASH_HANDLE hash{}; BCryptCreateHash(alg, &hash, nullptr, 0, nullptr, 0, 0); BCryptHashData(hash, (PUCHAR)data.data(), (ULONG)data.size(), 0); uint8_t out[32]; BCryptFinishHash(hash, out, 32, 0); BCryptDestroyHash(hash); BCryptCloseAlgorithmProvider(alg, 0); return to_hex(out, 32); } #else nlohmann::json http_post(const std::string&, const nlohmann::json&) { throw std::runtime_error("Use the C# / Python / Node SDK on this platform, or port WinHTTP."); } static std::string hmac_sha256_hex(const std::vector&, const std::string&) { return {}; } static std::string sha256_hex(const std::vector&) { return {}; } #endif static std::string to_hex(const uint8_t* p, size_t n) { static const char* hexd = "0123456789abcdef"; std::string s(n * 2, '0'); for (size_t i = 0; i < n; ++i) { s[i * 2] = hexd[p[i] >> 4]; s[i * 2 + 1] = hexd[p[i] & 0xf]; } return s; } static std::string random_hex(size_t bytes) { std::random_device rd; std::vector buf(bytes); for (auto& b : buf) b = (uint8_t)rd(); return to_hex(buf.data(), buf.size()); } static std::vector b64decode(const std::string& s) { #ifdef _WIN32 DWORD n = 0; CryptStringToBinaryA(s.c_str(), 0, CRYPT_STRING_BASE64, nullptr, &n, nullptr, nullptr); std::vector out(n); CryptStringToBinaryA(s.c_str(), 0, CRYPT_STRING_BASE64, out.data(), &n, nullptr, nullptr); out.resize(n); return out; #else return {}; #endif } }; } // namespace keyguard