Enroll a Windows device
You'll make two calls on the device and two on your server. By the end, Root Herald knows this TPM, and your server holds an ID for the device.
Without elevation, RootHeraldEnrollBegin returns RH_ERR_ELEVATION_REQUIRED. Attesting afterwards never needs it, so you pay for elevation once per device. Windows elevation covers the ways to get it: from your installer, a one-off worker, or a service.
Create the attestation key
On the client, use RootHeraldEnrollBegin to create a new attestation key in the TPM. It writes the enrollment request into a buffer: the new key and the TPM's endorsement certificate, packed for Root Herald. Post the buffer to your server.
size_t n;
RH_STATUS st = RootHeraldEnrollBegin(rh, buf, sizeof buf, &n);
if (st != RH_OK) {
return 0;
}
const char* reply = post("/enroll", buf);Relay it to Root Herald
On your server, pass the enrollment request to relayEnroll. Root Herald checks that the endorsement certificate chains to a TPM maker it trusts, and that your key's identity policy accepts this kind of TPM. Then it replies with a secret that only this TPM can recover. Send the reply back to the client as it is.
If the identity policy on your key doesn't accept the device, relayEnroll throws AdmissionRefusedError instead. That's a final answer for this device under this policy, so don't retry it.
app.post("/enroll", async (req, res) => {
const { challenge } = await rh.relayEnroll(req.body);
res.json(challenge);
});Recover the secret
Back on the client, pass the reply to RootHeraldEnrollComplete. The TPM can recover the secret only if the new attestation key lives in the same chip as the endorsement key, so recovering it is the proof. The answer lands in the buffer; post it to your server.
Make both calls with the same handle, in the same process. The secret works once, so if this step fails, start again from RootHeraldEnrollBegin.
st = RootHeraldEnrollComplete(rh, reply, strlen(reply),
buf, sizeof buf, &n);
if (st != RH_OK) {
return 0;
}
post("/enroll/activate", buf);Keep the device's ID
Pass the answer to relayActivate. Root Herald checks it and returns the device's ID, along with status and enrolledAt. Keep the ID with whatever the device belongs to: every verdict from this device carries the same ID, so it's how your server recognises the device later.
Two failures are worth handling. If the answer doesn't prove the device holds the key, relayActivate throws ActivationRefusedError. It throws the same error for an enrollment that's unknown, expired or already complete, and never says which, so have the client start again from RootHeraldEnrollBegin. If your key's budget can't pay for another new device this period, it throws QuotaExceededError instead. The enrollment stays open, so posting the same answer again works once the budget has room.
app.post("/enroll/activate", async (req, res) => {
const { deviceId } = await rh.relayActivate(req.body);
await saveDevice(accountOf(req), deviceId);
res.sendStatus(204);
});