architecture
How keystone works
keystone speaks the protocol of stock tangd and serves many named key sets from one Cloudflare Worker. Deleting a set's secret revokes it. keyset mints, checks, deploys and revokes the sets.
Overview
clevis in the host's initramfs talks to keystone through Cloudflare's edge, where a WAF rate limit can sit ahead of the Worker to slow a flood that would otherwise spend the Free plan's daily request quota. A key set is one signing key and one exchange key, held in a Worker secret named TANG_KEY_<NAME>. keystone answers two requests per set: adv returns its signed public keys, and rec runs the key recovery a booting host needs.
Binding
clevis checks the advertisement's signature against the pinned thp, the thumbprint of the set's signing key, so a forged advertisement fails the bind.
It picks a random c and computes K = c·S from S, the exchange public key; K wraps a new slot passphrase.
The header keeps C = c·G (G is the curve's base point), the kid (the exchange key's thumbprint) and the advertisement, and c and K are discarded.
Unlock
At boot clevis reads C from the header, picks a fresh random e and sends X = C + e·G to rec.
The Worker returns Y = s·X, where s is the exchange private key.
With S from the advertisement stored in the header, the host computes K = Y − e·S = s·C, which equals the bind's c·S because both are s·c·G, and K unwraps the slot passphrase. The Worker sees only the blinded X, never C or K.
On the wire
The advertisement, X, Y, and the set name, kid and client address cross the network; the Worker logs one line per successful rec with name, address, ASN and country. C, e, K and the passphrase never leave the host. Since e is fresh each boot, X differs every time. A recording gives up neither C nor K, even to someone holding s: e hides C, and K needs s together with C from the disk's own header.
A key set
The set server-1 lives in the Worker secret TANG_KEY_SERVER_1: the name upper-cased, with - turned into _. Its routes are /server-1/adv, /server-1/adv/<thp>, which answers only for the matching signing thumbprint, and POST /server-1/rec/<kid>. The signing key (ES512 on P-521, ES256 on P-256) signs the advertisement; the exchange key, labelled ECMR for the McCallum-Relyea exchange above, answers rec. Sets share no keys, so revoking one leaves the others serving.
Lifecycle
keyset new mints a set for the offline key store, deploy --new makes it live, and revoke deletes its secret, so its adv answers 404 and its disks stop unlocking.
The store then keeps null under that name, a tombstone, and every later deploy checks it still answers 404.
keyset refuses new keys under a live name, which would strand every disk bound to it, so rotation uses a new name.
Rotation
The disk's LUKS header holds a passphrase slot and one clevis slot per bound set.
Rotation binds server-2 in a second slot, reboots, then unbinds and revokes server-1. Rebooting before the unbind proves the new slot while the old one still opens the disk.
A copy taken before the unbind still opens with server-1's keys, so the store swaps them for the tombstone.
Rollback
Each Worker version carries the secrets it was deployed with. After the revoke in version 3, rolling back to version 2 serves server-1 again, though the Worker's secret list no longer shows it. keyset deploy flags that through the tombstone as REVOKED-LIVE, and --apply builds version 4 from the current secrets, which drops it. So never roll back across a revoke.
keyset deploy
keyset deploy reads the key store on stdin as one JSON object, a set or null per name, and gives each entry a verdict against the live Worker.
Any REFUSE stops the run with exit 1; without --apply it ends at the plan.
With --apply, wrangler uploads only the --new sets, then keyset checks each set's signed adv and a real rec, and each tombstone for a 404. The commands are in Install.
Curves
Each bar is the measured CPU time for one rec, against the Free plan's 10 ms of CPU per request. P-521, the default and the curve tangd-keygen uses, takes 7 ms at the median and up to 12 ms cold, so it needs Workers Paid. P-256 stays at 6 ms or under and fits the Free plan; keyset new --curve P-256 mints one.
Trust
Cloudflare, and whoever controls the account, can read the private keys, as can any user or token allowed to deploy a new version, since that code reads the secrets. The API lists secret names only, and disk hosts hold only public keys. To split trust, bind with clevis sss at a threshold of two across keystone and a second Tang server, so neither alone unlocks the disk.
Alerts
When alerts are set up, each successful rec asks whether its source, a /24 or /48 network plus ASN and country, is new for the set. The first new source in an hour sends one alert; up to five more wait in KV for the hourly digest, and the host never waits on the mail. The kid is public, so an alert shows a request was made; it does not prove an unlock. The Workers log stays the full record.
Failure
If keystone is unreachable or the day's request quota is spent, clevis gets no key and LUKS asks for the passphrase, so losing keystone costs a typed passphrase per disk; the data is untouched as long as each disk keeps its passphrase slot. If a disk is stolen, revoking its set makes rec answer 404, and the disk stays locked from then on.