docs(auth): proofs bound to the base URL and to what they approve (ADR 0017)

The full reading's part in ADR 0017 records the device's medium 1 as done
(the identity's purpose and digest, the base URL in place of the host, an
instance under a path taking no password sign-in), and "The rules as they
stand" says what "password" binds; the module's and halkyon_app's READMEs
say the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-21 14:18:23 +02:00
commit 7bef70948d
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -271,6 +271,7 @@ deletion):
production the host must be the server's `apiServer.publicHost` (`otherInstance`
otherwise), any in development; members forward their own. A hostile instance relaying
a sign-in or a phrase's proof to the real one, or swapping in a password, gets nothing.
(Since the full reading, the instance's whole base URL in place of the host.)
- **M7: data keys weren't authenticated.** A sealed data key is now signed by its sealer
and checked against the expected one on opening, so the register can't plant a key of
its own (before step 4 encrypts anything).
@ -471,18 +472,34 @@ refused but on this machine; the phrase is saved, never copied. Server side:
matters only once someone holding it made codes wait; L8, decoys answer today's costs
(`newPasswordCosts`), revisit when `PasswordKdf.current` rises; L9, a key proof's first
step gives out an address's public keys (step 4 shares keys: revisit then).
- **Not done yet, the device's M1:** the owner's proof is bound to the challenge and the
host, not to the operation it approves nor the instance's whole base URL; binding a
purpose label and the digest of what is set into SRP's identity on both sides, and
scheme, host, port and path in place of the host, is the next change. Until then a
`change` challenge's proof can be replayed for another signed-in change by whoever
reads and changes the traffic (plain `http://`, TLS intercepted), which the refusal of
`http://` instances (the device's part 2) narrows.
- **The device's medium 1, the owner's proof bound to the challenge and the host
alone.** Whoever read and changed the traffic (plain `http://`, TLS intercepted or
broken: what SRP is for) could keep the owner's proof and code and swap in a password,
a phrase or an authenticator of their own, or turn a proof made for new recovery codes
into a deletion; and instances on one host under another port, path or scheme could
relay each other's proofs (the device's low 4). SRP's identity is now a label, the
challenge's ID, the instance's base URL, what the proof is for and the digest of what
it sets (`passwordIdentity`, identity v2), and the phrase's proof signs the base URL
too (key proof v3). The base URL is written alike on both sides
(`canonicalInstanceUrl`: scheme and host in lower case, the port only where it isn't
the scheme's own, the path ending in `/`); the register takes only its own public
scheme, host and port at the root (`publicInstanceUrlOf`; Serverpod serves at the root,
so an instance under a path takes no password sign-in), any in development, a member's
as the member names it. The purposes are signing in, an authenticator's reset (each of
its own, so one never stands for the other), a new password (the digest of the
`NewPassword` sent), a new phrase (of its wrapping and proof key), the first or another
authenticator and removing one (of its ID), new recovery codes, calling resets off and
deleting (none); the register works out purpose and digest from the call that brings
the proof, never from the device. `OwnerProof`'s and `DeletionProof`'s `host`, and
every endpoint's, are `instanceUrl`. Each refusal has a test, which fails with the
binding taken out on both sides.
### The rules as they stand (21 Oct 2026)
Who changes what of an account, with which proofs. "Password" is SRP's proof bound to
the host the device addressed; "code" a TOTP code from a confirmed authenticator or a
the instance's base URL the device addressed, and to what it approves (the purpose and
the digest of what it sets: a proof made for one change counts for no other); "code" a
TOTP code from a confirmed authenticator or a
recovery code; "mailed code" one asked for that purpose, for the account's own address
unless said otherwise. Signed-in proofs count per session (ten wrong in a row end it);
sign-ins' codes per account and way, after the password or the phrase (waits from ten