Skip to content
Guides · Enrollment

Enroll a Linux 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.

Give the user access to the TPM

The process that enrolls needs to open /dev/tpmrm0, which on most distributions means its user is in the tss group. Linux never needs elevation. For posture asks it also needs to read the boot event log, which is root-only by default; the sdk-linux README has the one-line rule that grants it.

1

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.

client.cc
size_t n;

RH_STATUS st = RootHeraldEnrollBegin(rh, buf, sizeof buf, &n);

if (st != RH_OK) {
    return 0;
}

const char* reply = post("/enroll", buf);
2

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.

server (Node)ts
app.post("/enroll", async (req, res) => {
  const { challenge } = await rh.relayEnroll(req.body);

  res.json(challenge);
});
3

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.

client.cc
st = RootHeraldEnrollComplete(rh, reply, strlen(reply),
                              buf, sizeof buf, &n);

if (st != RH_OK) {
    return 0;
}

post("/enroll/activate", buf);
4

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.

server (Node)ts
app.post("/enroll/activate", async (req, res) => {
  const { deviceId } = await rh.relayActivate(req.body);

  await saveDevice(accountOf(req), deviceId);

  res.sendStatus(204);
});