Skip to content
Guides · Verify data came from a device

Verify data came from a Linux device

You'll mint a key once, keep its public half on your server, and from then on check every request the client signs with it. The installation needs to be enrolled first.

1

Ask for a key challenge

On your server, use issueKeyChallenge with purpose: "sign" and the device ID you expect, and send the keyChallenge string to the client with its nonce.

server (Node)ts
app.post("/key/challenge", async (req, res) => {
  const deviceId = await deviceOf(accountOf(req));

  const { nonce, keyChallenge } = await rh.issueKeyChallenge({
    purpose: "sign",
    expectedDevices: [deviceId],
  });

  res.json({ nonce, keyChallenge });
});
2

Mint the key

On the client, answer the key challenge with RootHeraldMintKey, passing the attestation key blob and a second buffer for the new key. The chip makes the key, the attestation key certifies it, and the certification lands in one buffer and the key blob in the other. Post the certification and its nonce to your server, and once it has kept the key, save the blob wherever your app keeps its files.

client.cc
size_t cert_len, blob_len;

if (!load_ak_blob(ak, sizeof ak, &ak_len)) {
    return enroll_then_mint();
}

RH_STATUS st = RootHeraldMintKey(rh, key_challenge, ak, ak_len,
                                 cert, sizeof cert, &cert_len,
                                 blob, sizeof blob, &blob_len);

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

post_certification("/key/certify", nonce, cert);

save_key_blob(blob, blob_len);
3

Keep the public key

On your server, pass the certification and nonce to certifyKey. It returns the key: keep jwk, its public half, with the account that deviceId belongs to.

server (Node)ts
app.post("/key/certify", async (req, res) => {
  const { nonce, certification } = req.body;

  const key = await rh.certifyKey(nonce, certification);

  await saveKey(accountOf(req), key.deviceId, key.jwk);

  res.sendStatus(204);
});
4

Sign what you send

On the client, load the blob once with RootHeraldLoadKey, then use RootHeraldSign on each request body. Signing only touches the chip: no network, no challenge, no prompt. RootHeraldKeyInfo reports the signature size. Send the signature along with the body, and close the key with RootHeraldCloseKey when you're done with it.

client.cc
RH_KEY_HANDLE key;

st = RootHeraldLoadKey(rh, blob, blob_len, &key);

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

uint8_t sig[256];

size_t sig_len;

st = RootHeraldSign(key, body, body_len,
                    sig, sizeof sig, &sig_len);

If the TPM has been cleared, every blob is gone, the attestation key's included: RootHeraldLoadKey returns RH_ERR_KEY_UNLOADABLE. Enroll again, then mint a new key. The device ID stays the same; the key ID is new.

5

Check each signature

On your server, use verifyKeySignature to check the signature against the JWK you kept, over the exact bytes the client signed. It runs locally and returns false rather than throwing, so treat anything but true as a refusal.

server (Node)ts
import { verifyKeySignature } from "@rootherald/node";

app.post("/orders", async (req, res) => {
  const jwk = await keyOf(accountOf(req));

  const signature = req.header("x-signature") ?? "";

  if (!verifyKeySignature(jwk, rawBody(req), signature)) {
    return res.sendStatus(401);
  }

  res.json(await placeOrder(req.body));
});