Skip to content
SDKs · Native C++In developmentView source

RootHerald.lib / librootherald.a

Embeddable native SDK for game studios, desktop applications, and any product whose integration target isn't a browser. Ships as a static library — you link it into your own binary, your own EV / Developer ID cert covers the result. Talks the TPM via NCryptOpenStorageProvider on Windows, tpm2-tss on Linux, SecKey on macOS. It is a keyless evidence collector: it holds no RootHerald key, opens no socket to RootHerald, and never renders a verdict — it emits opaque JSON blobs your backend relays to the verify API, which appraises them.

In development

This SDK is implemented but not yet published to its package registry. Until it ships, mint attestation tokens on the device and verify them server-side with @rootherald/node (or any available server SDK). The API shown below is the planned surface and may change before release.

Static archive · single binary · server-enforced version floor
Windows: first-time enrollment needs elevation

On Windows, the one-time device enrollment runs TPM2_ActivateCredential, which the OS permits only for an elevated process (attestation afterwards is unprivileged). The SDK does not elevate for youRootHeraldClient_EnrollBegin / RootHeraldClient_EnrollComplete return ROOTHERALD_ERR_ELEVATION_REQUIRED and you choose a strategy: a self-elevation shim, our native host, a privileged helper/service, or graceful check-then-skip. See Windows elevation patterns for copy-paste samples of each. A process that already runs elevated never sees this code.

Distribution

Released as a static archive + headers on every supported platform. There is no runtime DLL / .so / .dylib dependency. The library code is linked into your binary at compile time, then signed by your own code-signing certificate.

  • Windows: RootHerald.lib (MSVC 19+ static archive) + headers.
  • Linux: librootherald.a (static archive) + headers, glibc 2.31+.
  • macOS: librootherald.a (static archive) + headers, universal arm64+x86_64.

Single header (rootherald.h) exposes a C99 ABI so you can link from C++, Rust, Go, Zig, or any toolchain that consumes a C archive.

Why static?

When you ship a DLL or .so we've signed with our EV certificate, your security reviewer wants to know why a third-party brand is signing a binary in your install footprint. We don't want to be that question. By shipping a static archive, our code becomes part of your binary, so only your name appears in the signed product. Updates flow via server-enforced version floors: when a fix ships, our backend declines attestations from outdated builds, and you ship a customer update on your own cadence. The same model Rust crates, Unreal plugins, Unity native plugins, and the Android / iOS AAR / XCFramework world already use.

C ABI overview

The ABI is keyless (ABI 3.0): RootHeraldClient_Create takes no api_key and no endpoint, there is no verdict enum, and every blob-emitting call returns a caller-owned char* you free with RootHeraldClient_FreeEvidence. The client collects; your backend appraises.

rootherald.hcpp
#include <rootherald.h>

/* Opaque handle. */
typedef struct RootHeraldClient RootHeraldClient;

/* Status codes — there is NO client-side verdict enum. */
typedef enum {
    ROOTHERALD_OK = 0,
    ROOTHERALD_ERR_INVALID_ARG = 1,
    ROOTHERALD_ERR_TPM_UNAVAILABLE = 2,
    ROOTHERALD_ERR_NETWORK = 3,
    ROOTHERALD_ERR_SERVER = 4,
    ROOTHERALD_ERR_QUOTA_EXCEEDED = 5,
    ROOTHERALD_ERR_NOT_ENROLLED = 6,        /* no enrolled attestation key on the device */
    ROOTHERALD_ERR_ELEVATION_REQUIRED = 7,  /* Windows enroll: process not elevated. See "Windows elevation patterns". */
    ROOTHERALD_ERR_INTERNAL = 99
} RootHeraldStatus;

/* Caller-allocated local-info structs (never touch the network). */
typedef struct {
    int  is_enrolled;
    int  has_tpm;
    char device_id[129];
    char platform_name[16];   /* "windows" | "linux" | "macos" */
} RootHeraldDeviceInfo;

typedef struct {
    int  has_tpm;
    int  is_enrolled;
    int  ek_cert_present;
    int  secure_boot;          /* 1 on, 0 off, -1 undetermined */
    int  oem_keyed;            /* 1 known-OEM, 0 custom, -1 undetermined */
    char oem_name[64];
    int  boot_log_measurements;
    int  boot_log_revoked;
    char device_id[129];
    char detail_json[2048];
} RootHeraldPosture;

/* Lifecycle — keyless: no api_key, no endpoint. */
RootHeraldClient* RootHeraldClient_Create(void);
void              RootHeraldClient_Destroy(RootHeraldClient* client);
RootHeraldStatus  RootHeraldClient_SetApplicationId(RootHeraldClient* client, const char* app_id);

/* Enrollment — keyless, two-leg blob handshake (elevated on Windows).
   Your backend relays each blob to /devices/enroll then /devices/activate. */
RootHeraldStatus RootHeraldClient_EnrollBegin(RootHeraldClient* client,
                                              char** out_request_json);
RootHeraldStatus RootHeraldClient_EnrollComplete(RootHeraldClient* client,
                                                 const char* challenge_json,
                                                 char** out_activation_json);

/* Per-attestation evidence — keyless quote over a backend-issued nonce.
   The returned blob is exactly what /attestations/verify expects. */
RootHeraldStatus RootHeraldClient_CollectEvidence(const char* nonce_b64,
                                                  char** out_evidence_json);

/* Local, network-free snapshots. */
RootHeraldStatus RootHeraldClient_GetDeviceInfo(RootHeraldClient* client,
                                                RootHeraldDeviceInfo* out_result);
RootHeraldStatus RootHeraldClient_CollectPosture(RootHeraldClient* client,
                                                 RootHeraldPosture* out_result);

/* Free any blob returned by EnrollBegin / EnrollComplete / CollectEvidence. */
void RootHeraldClient_FreeEvidence(char* evidence_json);

const char* RootHerald_AbiVersionString(void);
const char* RootHerald_LibraryVersionString(void);

C++ usage example (game launcher)

The client never talks to RootHerald and never sees a verdict. It collects an evidence blob over a nonce your backend issued; your backend relays that blob (server→server, authenticated with its rh_sk_ secret) to POST /api/v1/attestations/verify and enforces the returned verdict.

launcher.cppcpp
#include <rootherald.h>
#include <cstdio>

// Provided by YOUR app: fetches a fresh challenge nonce from your backend
// (which called POST /api/v1/attestations/challenge with its rh_sk_ secret)
// and relays the collected evidence back for /attestations/verify.
extern std::string fetch_nonce_from_your_backend();
extern bool        relay_evidence_to_your_backend(const std::string& evidence_json);

int main()
{
    // Keyless: no api_key, no endpoint — nothing to connect to.
    auto* client = RootHeraldClient_Create();
    if (!client)
    {
        std::fprintf(stderr, "Root Herald init failed\n");
        return 1;
    }

    RootHeraldClient_SetApplicationId(client, "com.acme.launcher");

    // 1. Get the server-issued nonce from your backend.
    std::string nonce = fetch_nonce_from_your_backend();

    // 2. Collect a quote-over-nonce evidence blob locally. Keyless, no network.
    char* evidence_json = nullptr;
    auto status = RootHeraldClient_CollectEvidence(nonce.c_str(), &evidence_json);
    if (status != ROOTHERALD_OK)
    {
        std::fprintf(stderr, "collect failed (%d): %s\n",
                     status, RootHerald_ErrorString(status));
        RootHeraldClient_Destroy(client);
        return 1;
    }

    // 3. Hand the blob to your backend, which relays it to /attestations/verify
    //    and enforces the verdict. The verdict never travels through the client.
    bool ok = relay_evidence_to_your_backend(evidence_json);
    RootHeraldClient_FreeEvidence(evidence_json);

    if (!ok)
    {
        std::fprintf(stderr, "device not trusted (backend rejected)\n");
        RootHeraldClient_Destroy(client);
        return 1;
    }

    std::printf("Device attested; backend allowed launch.\n");
    RootHeraldClient_Destroy(client);
    return 0;
}

Where the evidence goes

The library neverPOSTs to RootHerald and holds no key — so there is no endpoint to configure. The only traffic it originates is best-effort fetches to TPM-vendor PKI (e.g. AMD's AIA endpoint) to complete an EK certificate chain. Every RootHerald call happens on your server:

  • Your backend issues a nonce via POST /api/v1/attestations/challenge (authenticated with its rh_sk_ secret) and passes it to the client.
  • The client returns an evidence blob; your backend relays it verbatim to POST /api/v1/attestations/verify.
  • RootHerald appraises the evidence and returns the verdict to your server, which enforces it. Use a server SDK such as @rootherald/node for that leg — the rh_sk_ secret must never reach the client.

CMake integration

The library is a static archive: there is no DLL to copy next to your EXE, no rpath to configure, and no runtime dependency on Root Herald. Add the archive to your linker inputs and your build produces a single self-contained binary.

CMakeLists.txtbash
find_package(RootHerald REQUIRED)
target_link_libraries(your_game PRIVATE RootHerald::RootHerald)

# Or, vendored as a sub-tree (typical for game studios):
add_subdirectory(third_party/rootherald)
target_link_libraries(your_game PRIVATE RootHerald)
target_include_directories(your_game PRIVATE third_party/rootherald/include)

The CMake target propagates its system-library dependencies transitively. On Windows that means ncrypt, tbs, winhttp, bcrypt, and crypt32 are added to your link line automatically. On Linux: tss2-esys, tss2-tctildr, tss2-mu, curl, pthread. On macOS: Security.framework and Foundation.framework.

Plain MSVC / GCC / Clang linker invocation

If you don't use CMake, point your linker at the archive directly:

MSVC (link.exe)bash
link.exe your_game.obj RootHerald.lib ncrypt.lib tbs.lib winhttp.lib bcrypt.lib crypt32.lib
GCC / Clang (Linux)bash
gcc your_game.o -lrootherald -ltss2-esys -ltss2-tctildr -ltss2-mu -lcurl -lpthread
Clang (macOS)bash
clang your_game.o -lrootherald -framework Security -framework Foundation
Threading model

RootHeraldClient* is thread-safe at the ABI level via an internal mutex, but operations are serialized, so concurrent calls block. Create one client per logical thread of work if you need true parallelism. The underlying NCrypt / tpm2-tss handles are not concurrency-friendly, so we surface that constraint up to you rather than silently bottlenecking inside the library.

ABI version

Check RootHerald_AbiVersionString() at startup to validate that the library you loaded matches the header you compiled against. Additive changes (new entry points, new enum values) ship in minor versions; struct layout and existing function signatures change only on a major bump.