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:
parent
009970faab
commit
dab2c80524
1 changed files with 966 additions and 137 deletions
|
|
@ -48,6 +48,9 @@ final class _Register implements AccountRegister {
|
|||
/// Thrown by every call while set: the register refusing, or down.
|
||||
Exception? failing;
|
||||
|
||||
/// The hosts sign-ins were bound to, as members named them.
|
||||
final hosts = <String>[];
|
||||
|
||||
Future<T> _answer<T>(String call, T Function() answer) async {
|
||||
asked.add(call);
|
||||
if (failing case final error?) {
|
||||
|
|
@ -236,7 +239,11 @@ final class _Register implements AccountRegister {
|
|||
String? recoveryCode,
|
||||
UuidValue? request,
|
||||
String? code,
|
||||
}) => _answer('completePasswordSignIn', _proven);
|
||||
required String host,
|
||||
}) {
|
||||
hosts.add(host);
|
||||
return _answer('completePasswordSignIn', _proven);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UuidValue> signUpWithCode(
|
||||
|
|
@ -310,6 +317,7 @@ final class _Register implements AccountRegister {
|
|||
String? code,
|
||||
UuidValue? keyChallenge,
|
||||
ByteData? signature,
|
||||
required String host,
|
||||
}) => _answer(
|
||||
'resetAuthenticator',
|
||||
() => ResetPending(takesEffectAt: DateTime.utc(2026, 10, 23)),
|
||||
|
|
@ -342,6 +350,7 @@ final class _Register implements AccountRegister {
|
|||
required NewPassword password,
|
||||
String? totpCode,
|
||||
String? recoveryCode,
|
||||
required String host,
|
||||
}) => _answer(
|
||||
'resetPasswordWithPhrase',
|
||||
() => (account: _proven().account, endedBefore: endedBefore),
|
||||
|
|
@ -399,6 +408,7 @@ final class _Register implements AccountRegister {
|
|||
String? recoveryCode,
|
||||
required NewPassword password,
|
||||
UuidValue? keepSession,
|
||||
required String host,
|
||||
}) => _answer('changePassword', () {
|
||||
changed[person] = keepSession;
|
||||
});
|
||||
|
|
@ -414,6 +424,7 @@ final class _Register implements AccountRegister {
|
|||
String? recoveryCode,
|
||||
required WrappedKeys recoveryKeys,
|
||||
required ByteData recoveryProofKey,
|
||||
required String host,
|
||||
}) => _answer('setRecoveryKeys', () {});
|
||||
|
||||
@override
|
||||
|
|
@ -633,6 +644,7 @@ void main() {
|
|||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
totpCode: '123456',
|
||||
host: testHost,
|
||||
);
|
||||
|
||||
expect(register.asked, [
|
||||
|
|
@ -640,6 +652,8 @@ void main() {
|
|||
'completePasswordSignIn',
|
||||
]);
|
||||
expect(register.networks, hasLength(1));
|
||||
// Bound to the host the device addressed: this member's.
|
||||
expect(register.hosts, [testHost]);
|
||||
expect(signedIn.authUserId, carla);
|
||||
expect(await scopesOf(carla), {identified.name});
|
||||
expect(
|
||||
|
|
@ -683,6 +697,7 @@ void main() {
|
|||
challenge: const Uuid().v7obj(),
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: testHost,
|
||||
),
|
||||
throwsA(
|
||||
isA<PasswordRefusedException>().having(
|
||||
|
|
@ -1002,6 +1017,7 @@ void main() {
|
|||
cipherText: ByteData(112),
|
||||
),
|
||||
),
|
||||
host: testHost,
|
||||
);
|
||||
|
||||
expect(register.asked, ['beginPasswordChange', 'changePassword']);
|
||||
|
|
@ -1038,6 +1054,7 @@ void main() {
|
|||
),
|
||||
),
|
||||
totpCode: '123456',
|
||||
host: testHost,
|
||||
);
|
||||
|
||||
expect(signedIn.authUserId, ben);
|
||||
|
|
|
|||
Loading…
Reference in a new issue