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 build
cpp
#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.