Langages
Intégration C++
SDK C++17 : CNG + WinHTTP sous Windows, OpenSSL 3 + libcurl ailleurs.
SDK officiel
Dossier sdks/cpp du dépôt (CMake). Sous Windows, aucune dépendance (CNG pour la cryptographie, WinHTTP pour le réseau) ; sous Linux et macOS, OpenSSL 3 et libcurl.
bash
cmake -S sdks/cpp -B build && cmake --build buildcpp
#include <keyline/license_client.hpp>
keyline::Options opts;
opts.api_url = "https://keyline.example.com/api/v1";
opts.application_id = "<application_id>";
opts.public_keys = {{"<kid>", PUBLIC_KEY_PEM}};
opts.app_version = "1.4.2";
keyline::LicenseClient client(opts);
auto result = client.activate(license_key); // identifiant machine haché par défaut
if (result.success) {
auto heartbeat = client.start_heartbeat([](const keyline::LicenseResult& r) { lock_premium_features(r.error->code); });
} else {
std::cerr << "Refus : " << result.error->code << "\n";
}Sans SDK : OpenSSL + libcurl
Dépendances de cet exemple : libcurl, OpenSSL 3, nlohmann/json.
Vérification de la signature
cpp
// keyline_verify.cpp — C++17, OpenSSL 3
#include <openssl/bn.h>
#include <openssl/ecdsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <string>
#include <vector>
static std::vector<unsigned char> base64url_decode(std::string s) {
for (auto& c : s) { if (c == '-') c = '+'; else if (c == '_') c = '/'; }
size_t pad = 0;
while (s.size() % 4) { s += '='; ++pad; }
std::vector<unsigned char> out(s.size() / 4 * 3);
int n = EVP_DecodeBlock(out.data(), reinterpret_cast<const unsigned char*>(s.data()), static_cast<int>(s.size()));
if (n < 0) return {};
out.resize(static_cast<size_t>(n) - pad);
return out;
}
// Signature Keyline : ECDSA P-256 / SHA-256, format IEEE P1363 (r || s), sur "<ts>.<corps>".
bool keyline_verify(const std::string& public_key_pem, const std::string& ts, const std::string& body, const std::string& sig_b64url) {
std::vector<unsigned char> raw = base64url_decode(sig_b64url);
if (raw.size() != 64) return false;
BIO* bio = BIO_new_mem_buf(public_key_pem.data(), static_cast<int>(public_key_pem.size()));
EVP_PKEY* key = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr);
BIO_free(bio);
if (!key) return false;
// OpenSSL attend une signature DER : conversion depuis r || s.
ECDSA_SIG* sig = ECDSA_SIG_new();
ECDSA_SIG_set0(sig, BN_bin2bn(raw.data(), 32, nullptr), BN_bin2bn(raw.data() + 32, 32, nullptr));
unsigned char* der = nullptr;
int der_len = i2d_ECDSA_SIG(sig, &der);
ECDSA_SIG_free(sig);
const std::string message = ts + "." + body;
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
bool ok = der_len > 0 &&
EVP_DigestVerifyInit(ctx, nullptr, EVP_sha256(), nullptr, key) == 1 &&
EVP_DigestVerify(ctx, der, static_cast<size_t>(der_len),
reinterpret_cast<const unsigned char*>(message.data()), message.size()) == 1;
EVP_MD_CTX_free(ctx);
OPENSSL_free(der);
EVP_PKEY_free(key);
return ok;
}Appel de l’API
cpp
// keyline_client.cpp — libcurl + nlohmann/json + OpenSSL 3
#include <curl/curl.h>
#include <nlohmann/json.hpp>
#include <openssl/rand.h>
#include <cctype>
#include <map>
#include <stdexcept>
bool keyline_verify(const std::string&, const std::string&, const std::string&, const std::string&);
struct Response { long status = 0; std::string body; std::string signature; };
static size_t on_body(char* p, size_t s, size_t n, void* ud) { static_cast<Response*>(ud)->body.append(p, s * n); return s * n; }
static size_t on_header(char* p, size_t s, size_t n, void* ud) {
std::string h(p, s * n);
const std::string name = "keyline-signature:";
std::string lower = h.substr(0, name.size());
for (auto& c : lower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (h.size() > name.size() && lower == name) {
std::string v = h.substr(name.size());
v.erase(0, v.find_first_not_of(" \t"));
v.erase(v.find_last_not_of("\r\n ") + 1);
static_cast<Response*>(ud)->signature = v;
}
return s * n;
}
static std::string random_nonce() {
unsigned char b[16];
if (RAND_bytes(b, sizeof b) != 1) throw std::runtime_error("RAND_bytes");
static const char* hex = "0123456789abcdef";
std::string out;
for (unsigned char c : b) { out += hex[c >> 4]; out += hex[c & 15]; }
return out;
}
nlohmann::json keyline_call(const std::string& endpoint, nlohmann::json payload,
const std::string& application_id, const std::map<std::string, std::string>& public_keys) {
const std::string nonce = random_nonce();
payload["application_id"] = application_id;
payload["nonce"] = nonce;
const std::string json = payload.dump();
Response r;
CURL* curl = curl_easy_init();
curl_slist* headers = curl_slist_append(nullptr, "Content-Type: application/json");
const std::string url = "https://keyline.example.com/api/v1/licenses/" + endpoint;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.c_str());
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); // vérification TLS (défaut, à ne jamais désactiver)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, on_body);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &r);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, on_header);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &r);
CURLcode rc = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.status);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (rc != CURLE_OK) throw std::runtime_error(curl_easy_strerror(rc));
// kid=...,ts=...,sig=...
std::map<std::string, std::string> parts;
size_t start = 0;
while (start < r.signature.size()) {
size_t end = r.signature.find(',', start);
std::string item = r.signature.substr(start, end == std::string::npos ? std::string::npos : end - start);
size_t eq = item.find('=');
if (eq != std::string::npos) parts[item.substr(0, eq)] = item.substr(eq + 1);
if (end == std::string::npos) break;
start = end + 1;
}
auto key = public_keys.find(parts["kid"]);
if (key == public_keys.end() || !keyline_verify(key->second, parts["ts"], r.body, parts["sig"]))
throw std::runtime_error("Signature Keyline invalide");
nlohmann::json data = nlohmann::json::parse(r.body);
if (data.value("nonce", "") != nonce) throw std::runtime_error("Nonce inattendu");
return data; // data["success"], data["error"]["code"], data["session"]["token"]…
}Initialisez libcurl une fois au démarrage (curl_global_init(CURL_GLOBAL_DEFAULT)) et ne désactivez jamais la vérification TLS.