Guarantee only a device can decrypt
Mint a decrypt key inside the device's chip, encrypt to its public half on your server, and only that chip opens the result, with no call to Root Herald.
const { nonce, keyChallenge } = await rh.issueKeyChallenge({
purpose: "decrypt",
expectedDevices: [deviceId],
});
const key = await rh.certifyKey(nonce, certification);
await saveDecryptKey(accountOf(req), key.deviceId, key);
const envelope = encryptToDevice(key, sessionToken);st = RootHeraldDecrypt(key, jwe, jwe_len,
token, jwe_len, &token_len);Choose your client
iOS: no decrypt key. App Attest keys only sign; see the iOS SDK.
Use it for
- Deliver the session or refresh token as ciphertext the device must open to use it. Device-bound accounts: an exported cookie store is ciphertext.
- Encrypt the payload of a sensitive action to the device that approved it. Step-up: the approval and the payload belong to one chip.
- Send a per-device configuration, VPN profile or wrapped data key to an enrolled endpoint. Workforce and BYOD: the file opens on that endpoint and nowhere else.
- Hand a game server's session key to the one machine allowed to read it. Anti-cheat: a copied client without that chip reads nothing.
What the chip guarantees
Encryption uses the public half, so anyone holding the JWK can encrypt to the device. What the chip guarantees is that only it can open the result. An envelope that opens proves which chip holds the key; it does not say how the machine booted: run an attestation challenge for that, per session or per period.
The envelope
encryptToDevice produces one JWE compact serialization: ECDH-ES for an EC P-256 key, RSA-OAEP-256 for an RSA key, A256GCM for the content in both. A fresh ephemeral key and IV are drawn per call, so the same bytes encrypt to two different envelopes. Windows and Linux keys open it; a macOS key is format: "apple-ecies", which encryptToDevice refuses by name: encrypt to it with an Apple-side encryptor.
What each side keeps
The client keeps the key blob. The chip wrapped it, so it does not load on any other machine. Any process on this one that holds the blob can open everything ever sent to the key. Your server keeps the certified key: its jwk, alg and format are what encryptToDevice reads. Root Herald keeps the public half and the key's ID, and never any private material.
Sign then encrypt
For origin and secrecy together, mint both keys on the installation: the device signs what it sends with its sign key, and your server encrypts what it returns to the decrypt key. Two keys, two JWKs, one keyId each. The sign half is Verify data came from a device.
Rotate or stop trusting a key
Mint again. The new key replaces the old one under the same keyId; replace the key you kept, and nothing encrypted to the old one opens on the device after that. To stop sending to a device, delete its key on your side.