fix(auth)!: proofs are bound to the instance the device addresses

Nothing tied a sign-in's proof to the instance it was made for: an
instance a person was lured into adding could pass A, M1 and the code on
to the real register and get a session there, and pass a phrase's proof
on with a password of its own in place of the person's (the second
reading's M6). SRP's identity is now a label, the challenge's ID and
the host the device addressed (passwordIdentity, in halkyon_base_client
and halkyon_base_server alike), and the phrase's proof signs that host,
what it is for (an authenticator's reset or a new password) and the new
password's digest (key proof v2). Every proving call carries the host;
in production it must be the server's apiServer.publicHost (otherwise
otherInstance, which the app shows as an instance it can't trust), in
development any; a member forwards its own. The tests prove that a
relayed sign-in, a proof for another host or purpose, and a swapped
password are refused, nothing changing.

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-20 21:20:12 +02:00
commit dab2c80524
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -555,6 +555,7 @@ void main() {
clientValue: answer.clientValue,
clientProof: answer.clientProof,
totpCode: codeOf(enrolment, at: now),
host: testHost,
);
expect(proof.account, annaOnTodo);
expect(answer.verifiesServer(proof.serverProof), isTrue);
@ -572,6 +573,7 @@ void main() {
clientValue: againAnswer.clientValue,
clientProof: againAnswer.clientProof,
totpCode: codeOf(enrolment, at: now),
host: testHost,
),
refused(PasswordRefusal.wrongCredentials),
);
@ -596,13 +598,18 @@ void main() {
device.recoveryKeys.toJson(),
);
now = now.add(totpPeriod);
final newPassword = device.newPassword();
expect(
await remote.resetPasswordWithPhrase(
session,
challenge: keyChallenge.challenge,
signature: await device.sign(keyChallenge),
password: device.newPassword(),
signature: await device.sign(
keyChallenge,
password: newPassword,
),
password: newPassword,
totpCode: codeOf(enrolment, at: now),
host: testHost,
),
(account: annaOnTodo, endedBefore: now),
);
@ -630,6 +637,7 @@ void main() {
language: 'en',
request: resetRequest,
code: mailbox.codeFor('anna@example.org'),
host: testHost,
);
expect(pending.takesEffectAt, now.add(resetDelay));
@ -727,6 +735,7 @@ void main() {
totpCode: codeOf(enrolment),
password: device.newPassword(),
keepSession: annasPhone,
host: testHost,
);
expect(members.sessionsTold.single, (
_todo,
@ -757,6 +766,7 @@ void main() {
recoveryProofKey: ByteData.sublistView(
device.recoveryProofKey,
),
host: testHost,
);
}
@ -861,6 +871,7 @@ void main() {
clientProof: answer.clientProof,
request: await code(),
code: mailbox.codeFor('carla@example.org'),
host: testHost,
);
expect(proof.account, carla);
expect(proof.needsAuthenticator, isTrue);