An app that pins the CA at build time has to be built on the machine its
server runs on. ai-app's iris client is cross-compiled in a VM and run
against the host's ai-server, so it cannot be -- the CA travels with the
enrollment link instead, as base64url of its DER under an optional 'ca'
parameter.
Optional per call rather than always present because it is not free: on
ai-app's P-256 CA the link goes from 89 bytes to 652 and print_enrollment's
terminal QR from 45 to 93 columns, which a project whose app already pins
at build time should not pay. Unrecognised keys are ignored by every reader
of this format, so a link carrying it still enrolls an app that predates it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A second process cannot append a token to the config: the server holds
its config in memory and writes it back whole, so the append loses the
race with the next save, silently. spool_pending writes one file per
token, named by the hash, into a private directory; take_pending lets
the running server move it into its own config the first time the phone
presents it, and sweeps anything older than an hour unused. This is what
lets a tool -- Dev Updater -- ask for an enrolment link without being at
the terminal the QR is printed on.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Rust crate and the Android half of one arrangement: a server that binds
the tunnel interface and nothing else, certificates it generates and keeps
outside any shared checkout, enrolment that carries a token and the CA to a
phone, and a client that trusts exactly that certificate and no other.
Extracted because ai-app and dev-updater had written all of it twice and the
two copies had already drifted -- one of them carried a bug the other did
not. History before this point was squashed away; it was a running record of
that extraction and of a personal machine's addresses, and neither is worth
keeping in a public repository.