Skip to content
SDKs · ClientIn developmentView source

@rootherald/browser

Client ABI 6.0. Drives the TPM through the Root Herald extension and native host and returns opaque blobs. Holds no key, sees no verdict.

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
npm install @rootherald/browser

The calls

  • respond(challenge, { key?, timeoutMs? }){ evidence, key? }. Answers the relayed rhc1. string; anything else is a TypeError. key is returned for a key ask when none was supplied; supply an existing blob to re-certify it.
  • enroll({ enroll, activate }, { timeoutMs? }) — the one-time bootstrap. The two callbacks post each leg to your backend's relayEnroll / relayActivate.
  • sign(key, data){ alg: "ES256", signature }. base64url, r||s or DER. No network.
  • getClientStatus() / getPosture() — readiness signals: extension, host, OS support, app keys, enrolled. Never a verdict.
  • Errors: ExtensionMissingError, HostMissingError, NotEnrolledError, AskUnsupportedError, AdmissionRefusedError, KeyUnloadableError, TimeoutError, AbiMismatchError. Check with instanceof.

Example

signup.tsts
import { respond, enroll, NotEnrolledError, AdmissionRefusedError } from "@rootherald/browser";

const post = (url: string, body: unknown) =>
  fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
    .then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))));

const { challengeId, challenge } = await post("/api/challenge", {});

let evidence;
try {
  ({ evidence } = await respond(challenge));              // no prompt on an enrolled device
} catch (err) {
  if (!(err instanceof NotEnrolledError)) throw err;
  try {
    await enroll({
      enroll: (blob) => post("/api/enroll", { enrollRequestBlob: blob, challengeId }),
      activate: (blob) => post("/api/activate", { activationResponse: blob }),
    });
  } catch (e) {
    if (e instanceof AdmissionRefusedError) throw new Error("device not eligible");   // no prompt was shown
    throw e;
  }
  ({ evidence } = await respond(challenge));
}

await post("/api/signup", { challengeId, evidence, ...form });   // your backend: rh.verify()