Skip to content
SDKs · ClientIn developmentView source

Native C

RootHerald.lib on Windows (NCrypt/PCP), librootherald.a on Linux (tpm2-tss) and macOS (Secure Enclave). One header, byte-identical across the three repos and sdk-ios; CI fails on drift.

In development

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

terminalbash
# one archive per platform: lib/, include/rootherald.h, include/protocol.h, SHA256SUMS
sha256sum -c SHA256SUMS
gh attestation verify rootherald-linux-x64.tar.gz --repo RootHerald/sdk-linux

Releases: sdk-windows · sdk-linux · sdk-macos. The archive is not code-signed and does not need to be: you link it, and your certificate signs the result.

The calls

rootherald.h (abridged, ABI 6.0)c
typedef struct RH_HANDLE__     *RH_HANDLE;       /* one TPM session */
typedef struct RH_KEY_HANDLE__ *RH_KEY_HANDLE;   /* an app key loaded under it */

RH_STATUS RootHeraldOpen(RH_HANDLE* out);                    /* keyless: no key, no endpoint */
void      RootHeraldClose(RH_HANDLE h);                      /* closes every key under it */
RH_STATUS RootHeraldPreCheck(RH_HANDLE h, RH_POSTURE* out);  /* local readiness; is_enrolled, app_keys_supported */

/* Enrollment: two legs on the SAME session. Elevated on Windows. */
RH_STATUS RootHeraldEnrollBegin(RH_HANDLE h, char* buf, size_t cap, size_t* out_len);
RH_STATUS RootHeraldEnrollComplete(RH_HANDLE h, const char* credential_json, size_t len,
                                   char* buf, size_t cap, size_t* out_len);

/* The one attestation verb. challenge is the relayed "rhc1.…" string; anything else is RH_ERR_INVALID_ARG.
   key: NULL, or a loaded key to re-certify. key_blob is written only for a key ask with key == NULL. */
RH_STATUS RootHeraldRespond(RH_HANDLE h, const char* challenge, RH_KEY_HANDLE key,
                            char* evidence, size_t evidence_cap, size_t* evidence_len,
                            uint8_t* key_blob, size_t key_cap, size_t* key_len);

/* App keys. No network, no nonce. Signature is raw r||s, 64 bytes for P-256. */
RH_STATUS RootHeraldLoadKey(RH_HANDLE h, const uint8_t* blob, size_t len, RH_KEY_HANDLE* out);
RH_STATUS RootHeraldSign(RH_KEY_HANDLE key, const uint8_t* data, size_t len,
                         uint8_t* sig, size_t sig_cap, size_t* sig_len);
void      RootHeraldCloseKey(RH_KEY_HANDLE key);

const char* RootHeraldErrorString(RH_STATUS);
const char* RootHeraldAbiVersion(void);      /* "6.0" */

Every byte-producing call takes (buf, cap, out_len) and returns RH_ERR_BUFFER_TOO_SMALL with *out_len set when the buffer is short. The library never allocates, so there is nothing to free. Respond does its TPM work before it knows the size, so start with 64 KiB for evidence and 512 bytes for a key blob and grow once. Every RH_STATUS is on the status codes page.

Example

launcher.cc
#include <rootherald.h>
#include <stdio.h>
#include <stdlib.h>

extern char* fetch_challenge(void);                 /* your backend: POST /api/challenge */
extern int   relay_evidence(const char* json);      /* your backend: POST /api/verify → verdict */
extern int   enroll_once(const char* challenge_id); /* EnrollBegin/Complete, elevated on Windows */

int main(void) {
    RH_HANDLE h = NULL;
    if (RootHeraldOpen(&h) != RH_OK) { fputs("no TPM reachable\n", stderr); return 1; }

    RH_POSTURE p;
    RootHeraldPreCheck(h, &p);                      /* free; p.is_enrolled, p.app_keys_supported */

    char* challenge = fetch_challenge();
    size_t cap = 64 * 1024, len = 0;
    char* evidence = malloc(cap);
    RH_STATUS st = RootHeraldRespond(h, challenge, NULL, evidence, cap, &len, NULL, 0, NULL);
    if (st == RH_ERR_NOT_ENROLLED && enroll_once(challenge) == 0)
        st = RootHeraldRespond(h, challenge, NULL, evidence, cap, &len, NULL, 0, NULL);
    if (st != RH_OK) { fprintf(stderr, "respond: %s\n", RootHeraldErrorString(st)); RootHeraldClose(h); return 1; }

    int ok = relay_evidence(evidence);              /* the verdict never travels through the client */
    free(evidence);
    RootHeraldClose(h);
    return ok ? 0 : 1;
}

Linking

CMakeLists.txtbash
find_package(RootHerald REQUIRED)
target_link_libraries(your_app PRIVATE RootHerald::RootHerald)
# Windows adds ncrypt tbs winhttp bcrypt crypt32; Linux tss2-esys tss2-tctildr tss2-mu curl pthread;
# macOS Security.framework Foundation.framework — transitively.
Threads and elevation

A handle is not thread-safe and the library takes no locks: one session per thread, or serialise. On Windows EnrollBegin / EnrollComplete return RH_ERR_ELEVATION_REQUIRED from an unelevated process and the SDK never elevates for you; Respond, LoadKey and Sign never need it. Check RootHeraldAbiVersion() at startup against the header you compiled with.