Verify data came from a device
Mint a signing key inside the device's chip, keep its public half on your server, and check what the device signs with it, with no call to Root Herald.
const { nonce, keyChallenge } = await rh.issueKeyChallenge({
purpose: "sign",
expectedDevices: [deviceId],
});
const key = await rh.certifyKey(nonce, certification);
await saveKey(accountOf(req), key.deviceId, key.jwk);
const ok = verifyKeySignature(jwk, rawBody(req), signature);Choose your client
iOS: the App Attest key signs assertions, which verifyKeySignature does not read; see the iOS SDK.
Use it for
- Sign each request from a signed-in device. Device-bound accounts: a session copied to another machine fails your check.
- Sign the approval of a sensitive action on the device that asked. Step-up: the approval and the request come from one chip.
- Sign a periodic heartbeat from a managed agent. Workforce and BYOD: each report is tied to the endpoint that sent it.
- Sign telemetry or a free-tier claim from a game or desktop client. Anti-cheat and free-tier abuse: a claim without that chip's signature is dropped.
What a signature proves
A signature proves which chip signed. It does not say how the machine booted: run an attestation challenge for that, per session or per period. Put a counter or timestamp in what you sign; a signature does not stop a replay.
The private half, locked in the chip, signs and decrypts. The public half, in the JWK, verifies and encrypts. To send something only that device can read, mint a decrypt key: Guarantee only a device can decrypt.
What each side keeps
The client keeps the key blob. The chip wrapped it, so it does not load on any other machine, and any process on this one that holds it can sign with the key. Your server keeps the public half as a JWK. Root Herald keeps the public half and the key's ID, and never any private material.
Rotate or stop trusting a key
Mint again. The new key replaces the old one under the same keyId; replace the JWK you kept. To stop trusting a key, delete its JWK on your side: nothing signed with it verifies after that.