Real & secure hardware
Some features should run onlyon a genuine, securely-booted physical machine — think a banking app’s high-value actions, a DRM-protected download, or a workstation that touches regulated data. This tutorial builds that gate end to end: require a real hardware security chip, a verified Secure Bootchain, and a tamper-checked record of how the machine booted, and reject cloud VMs and software emulators. Every check runs on Root Herald’s servers and is bound to a signed proof from the chip, so a client can’t fake its way past it.
You don’t hand-roll the boot checks. The built-in rootherald:builtin:strict-hardware policy (the conservative default) accepts the hardware, firmware-tpm, and mobile-hardware (Apple Secure Enclave / Android StrongBox) TPM classes (so cloud vTPMs and emulators are rejected), then gates each further on event-log / ACR requirements rather than on class alone. It requires a signed quote, requires a quote-bound event log, requires Secure Boot to be enabled, and floors the verdict at affirming. You pass the policy name to verify(); the server returns an affirming verdict only when all of that holds.
1. Set up the server client
import { RootHerald } from "@rootherald/node";
export const rh = new RootHerald({ secretKey: process.env.RH_SECRET_KEY! });Enrollment is a prerequisite (a device must have an attestation key before it can quote) — see the hardware-ID tracking tutorial for the relay wiring. This guide is about the appraisal.
2. Attest against the strict-hardware policy
Issue a challenge, let the page collect a fresh quote over the nonce, then verify with the policy. The policy is what turns “a valid quote” into “a valid quote from real, secure hardware.”
// 1) mint a single-use challenge
const { challengeId, nonce } = await rh.issueChallenge();
// 2) hand `nonce` to the page; it calls attest(nonce) and posts `evidence` back
// 3) verify under the hardware-only, secure-boot policy
const verdict = await rh.verify(evidence, {
challengeId,
policy: "rootherald:builtin:strict-hardware", // (also the default if omitted)
});import { attest, enroll, NotEnrolledError } from "@rootherald/browser";
async function collect(nonce, relay) {
try {
return await attest(nonce); // unprivileged quote, no prompt
} catch (e) {
if (e instanceof NotEnrolledError) { // first run on this device
await enroll(relay); // the one-time elevation
return await attest(nonce);
}
throw e;
}
}3. Read the verdict
For real-and-secure, an affirming verdict is your green light: the policy already guaranteed hardware class + Secure Boot + measured boot. The device booleans let you display or log the specifics.
if (verdict.device.earStatus !== "affirming") {
// Contraindicated / warning: not genuine secure hardware under this policy.
return res.status(403).json({ error: "requires_secure_hardware" });
}
// Green light. What you now KNOW (all server-validated, quote-bound):
const d = verdict.device;
d.attestationType; // "tpm20" — a TPM device (vs apple-se / android-ka)
d.quoteVerified; // true — the quote signature checked against the enrolled AK
d.secureBootVerified; // true — Secure Boot established from the quote-bound event log
d.eventLogVerified; // true — the event log replayed and bound to the signed quote
d.platform; // "windows" | "linux" | ...
grantSecureHardwareFeature(verdict.device.ueid);secureBootVerified and eventLogVerified are computed on the server from an event log that had to reconstruct the exact PCR values the TPM signed. A client can’t set them: an event log that doesn’t match the signed quote is rejected before Secure Boot is even read. This is the difference between “the client told us Secure Boot is on” and “the hardware proved it.”
4. What gets rejected, and why
Under strict-hardware, each of these returns a fail (contraindicated) verdict:
| Device / condition | TPM class | Why it fails |
|---|---|---|
| AWS / Azure / GCP / Hyper-V VM | cloud-vtpm | Class not in the policy’s accepted set (hardware + firmware-tpm only). |
| swtpm / software emulator | emulated | No hardware root of trust; class rejected. |
| Real TPM, Secure Boot OFF | hardware / firmware-tpm | Quote-bound event log shows Secure Boot disabled → contraindicated. |
| Revoked boot component (dbx) | hardware / firmware-tpm | A forbidden signature in the boot chain → contraindicated. |
| No event log / PCR-only | any | Policy requires a quote-bound event log; a bare quote can’t prove boot posture. |
An unrecognized EK (no vendor chain) classifies as Unknown and is rejected too. See the TPM-class taxonomy for how the class is derived.
5. Tune it (optional): a custom policy
If strict-hardware is close but not exact (say you also accept mobile hardware, require a specific ACR, or want to reject rare boot configurations), author a custom policy in the dashboard, or via the admin API, and pass its id to verify(). Configurable knobs include the accepted TPM-class groups, whether an event log and signed quote are required, the required PCR set, a minimum ACR, and an opt-in cohort-prevalence floor (reject boot configs that are novel / too rare).
POST /api/v1/admin/policies
{
"name": "my-secure-hw",
"acceptedClassGroups": ["hardware", "firmware-tpm"], // no cloud-vtpm / emulated
"requireEventLog": true,
"requireSignedQuote": true,
"minAcr": "urn:rootherald:device:high"
}
// 201 Created — keep the returned id; it's how you reference the policy:
{ "id": "8f3c1e2a-... (UUID)", "name": "my-secure-hw", "version": 1 }Then reference it by that id: rh.verify(evidence, { challengeId, policy: "8f3c1e2a-... (UUID)" }). Built-in policies are immutable; custom ones are tenant-scoped and versioned.
Built-in policies are referenced by name (e.g. rootherald:builtin:strict-hardware); your own custom policies by their id. Passing a custom policy's name instead of its id resolves against the built-ins only and returns a 422.
6. Gate a whole route (server→server)
To gate an API route on device assurance, appraise the client's evidence server→server and branch on the verdict. Requiring urn:rootherald:device:high additionally demands quoteVerified && secureBootVerified && eventLogVerified:
import { RootHerald } from "@rootherald/node";
const rh = new RootHerald({ secretKey: process.env.RH_SECRET_KEY }); // rh_sk_…
app.post("/api/secure-action", async (req, res) => {
const verdict = await rh.verify(req.body.evidence, {
challengeId: req.body.challengeId,
policy: process.env.RH_SECURE_HW_POLICY_ID, // the custom policy's UUID id
});
if (verdict.acr !== "urn:rootherald:device:high") {
return res.status(403).json({ error: "device_rejected" });
}
// reaches here only for genuine, securely-booted hardware
res.json({ ok: true });
});Nothing here trusts the client. The TPM class comes from a vendor-chained EK; Secure Boot and the boot chain come from an event log that had to reproduce the signed PCRs; the policy decision runs on our side. The client collects evidence; it never renders the verdict.