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
|
|
@ -7,6 +7,11 @@ import 'package:halkyon_base_client/srp.dart' as srp;
|
|||
import 'package:halkyon_base_server/base.dart';
|
||||
import 'package:halkyon_base_server/src/srp/srp_math.dart';
|
||||
|
||||
/// The host devices address the tests' server by: its `apiServer.publicHost`
|
||||
/// (config/test.yaml), which SRP's identity and the phrase's proofs are
|
||||
/// bound to.
|
||||
const testHost = 'localhost';
|
||||
|
||||
/// The instance keys the tests' register holds (ADR 0017).
|
||||
const testPasswordKeys = (
|
||||
totpKey: 'a TOTP key of the tests, 32 characters at least',
|
||||
|
|
@ -119,18 +124,22 @@ final class PasswordDevice {
|
|||
);
|
||||
}
|
||||
|
||||
/// The device's answer to [challenge] (A and M1), with its password or
|
||||
/// the [privateValue] of another; and whether a register's M2 is the one
|
||||
/// that answer expects.
|
||||
/// The device's answer to [challenge] (A and M1) on [host], with its
|
||||
/// password or the [privateValue] of another; and whether a register's M2
|
||||
/// is the one that answer expects.
|
||||
({
|
||||
ByteData clientValue,
|
||||
ByteData clientProof,
|
||||
bool Function(ByteData serverProof) verifiesServer,
|
||||
})
|
||||
answer(PasswordChallenge challenge, {BigInt? privateValue}) {
|
||||
answer(
|
||||
PasswordChallenge challenge, {
|
||||
BigInt? privateValue,
|
||||
String host = testHost,
|
||||
}) {
|
||||
final client = srp.SrpClient(_clientGroup, random: _random);
|
||||
final step = client.respond(
|
||||
identity: challenge.challenge.toBytes(),
|
||||
identity: srp.passwordIdentity(challenge.challenge.toBytes(), host),
|
||||
salt: Uint8List.sublistView(challenge.salt),
|
||||
privateValue: privateValue ?? this.privateValue,
|
||||
serverValue: numberOf(Uint8List.sublistView(challenge.serverValue)),
|
||||
|
|
@ -146,16 +155,24 @@ final class PasswordDevice {
|
|||
);
|
||||
}
|
||||
|
||||
/// The signature proving the recovery phrase for [challenge]: by the
|
||||
/// phrase's proof key, or [keys]' where given.
|
||||
/// The signature proving the recovery phrase for [challenge] on [host]:
|
||||
/// for the new [password] it sets, or for an authenticator's reset
|
||||
/// without one; by the phrase's proof key, or [keys]' where given.
|
||||
Future<ByteData> sign(
|
||||
KeyProofChallenge challenge, {
|
||||
SimpleKeyPair? keys,
|
||||
NewPassword? password,
|
||||
String host = testHost,
|
||||
}) async {
|
||||
final signature = await Ed25519().sign(
|
||||
keyProofMessage(
|
||||
challenge.challenge,
|
||||
Uint8List.sublistView(challenge.nonce),
|
||||
host: host,
|
||||
purpose: password == null
|
||||
? KeyProofPurpose.authenticatorReset
|
||||
: KeyProofPurpose.passwordReset,
|
||||
digest: password == null ? const [] : newPasswordDigest(password),
|
||||
),
|
||||
keyPair: keys ?? proofKeys,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue