Enroll a macOS device
The Mac creates a key in its Secure Enclave and proves it holds that key by signing a nonce from Root Herald. Your server relays both steps and keeps the device's ID.
The default policy refuses Macs. Select Mac among your identity policy's platforms, and expect a Mac's verdicts to be warn; Policies explains why.
Create the enclave key
On the client, use RootHeraldEnrollBegin to create a Secure Enclave key and write the enrollment request for it into a buffer, then post the buffer to your server. If your app's provisioning profile doesn't grant keychain access, RootHeraldOpen has already returned RH_ERR_ENTITLEMENT_MISSING.
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 your policy admits Macs and replies with a nonce for the enclave key to sign. 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);
});Sign the nonce
Back on the client, pass the reply to RootHeraldEnrollComplete. It signs the nonce with the enclave key, which proves the Mac holds the key it enrolled, and writes the signature into the buffer. Post it to your server.
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);
});