fix: scope server pins by port (fumi-core rev 1468f3c)

This commit is contained in:
randogoth 2026-09-30 08:52:57 +03:00
parent 996e879509
commit 7d2a147fec
16 changed files with 109 additions and 79 deletions

View file

@ -60,7 +60,7 @@ void main() {
final me = client.identity!;
final user = "e2e${hex(randomBytes(4))}";
final address = parseAddress("$user@$host");
store.pinServer(host, serverKey);
store.pinServer(host, serverKey, port);
await client.registerAccount(address.short);
expect(client.accountAddress()!.short, address.short);
@ -129,7 +129,7 @@ void main() {
secondStore.restoreMaster(store.master()!, 0);
await expectLater(
secondDevice.recallAccount(address.short), throwsA(isA<SmolError>()));
secondStore.pinServer(host, serverKey);
secondStore.pinServer(host, serverKey, port);
await expectLater(
secondDevice.registerAccount(address.short), throwsA(isA<SmolError>()));
final bound = await secondDevice.recallAccount(address.short);
@ -157,7 +157,7 @@ void main() {
await client.createIdentity();
final user = "e2ekeep${hex(randomBytes(4))}";
final address = parseAddress("$user@$host");
store.pinServer(host, serverKey);
store.pinServer(host, serverKey, port);
await client.registerAccount(address.short);
await store.setLeaveOnServer(true);

View file

@ -13,6 +13,7 @@ import "dart:typed_data";
import "package:flutter_test/flutter_test.dart";
import "package:hive_flutter/hive_flutter.dart";
import "package:smol_mail/smol/address.dart";
import "package:smol_mail/smol/store.dart";
const fumiBinary = "../fumi/target/release/fumi";
@ -38,7 +39,7 @@ void main() {
stateBox: "interop-state",
readBox: "interop-read");
mine.restoreMaster(master, 0);
mine.pinServer("example.org", pinKey);
mine.pinServer("example.org", pinKey, defaultPort);
// One contact, bound to the current key; rotation history is written by
// the trust engine on a validated rotation, not by a plain re-save.
await mine.saveContact("alice@example.org", pinKey, verified: true);
@ -72,6 +73,6 @@ void main() {
// The binding survived both directions of the round trip.
final contact = restored.contact("alice@example.org")!;
expect(contact.key, pinKey);
expect(restored.serverPin("example.org"), pinKey);
expect(restored.serverPin("example.org", defaultPort), pinKey);
});
}

View file

@ -11,6 +11,7 @@ import "package:flutter_test/flutter_test.dart";
import "package:smol_mail/native/client.dart";
import "package:smol_mail/native/ffi.dart";
import "package:smol_mail/smol/address.dart";
const spikeServerKey = "wukbhdiwboarbg4pujxstbqi3caveunrruaopbwxr226ga3xv5ga";
const spikeServer = "127.0.0.1";
@ -39,7 +40,9 @@ void main() async {
expect(await client.accountAddress(), isNull);
// A pinned but dead host fails by code, never by prose: Unreachable (7).
await client.pinServer("127.0.0.1", spikeServerKey);
// The pin is scoped to the dead port itself — a default-port pin would
// not cover it, and the register would fail NotPinned instead.
await client.pinServer("127.0.0.1", spikeServerKey, 19629);
client.setMaster(Uint8List.fromList(List.filled(32, 9)));
try {
await client.register("nobody@127.0.0.1:19629");
@ -63,7 +66,7 @@ void main() async {
final mine = FumiNative("${dir.path}/mine.db");
await mine.open();
mine.setMaster(master);
await mine.pinServer("example.org", spikeServerKey);
await mine.pinServer("example.org", spikeServerKey, defaultPort);
await mine.importContact(
"smol://alice@example.org/ayb6y3mwr3cfkcmcaqbn3cgfi6xsrsoqkalykw5s7oqmwqcpnmsq");
@ -76,7 +79,7 @@ void main() async {
as Map<String, dynamic>;
expect(summary["contactsAdded"], 1);
expect(summary["pinsAdded"], 1);
expect(await restored.serverPin("example.org"), spikeServerKey);
expect(await restored.serverPin("example.org", defaultPort), spikeServerKey);
await mine.close();
await restored.close();
@ -98,14 +101,14 @@ void main() async {
final alice = FumiNative("${dir.path}/alice.db");
await alice.open();
alice.setMaster(aliceMaster);
await alice.pinServer(spikeServer, spikeServerKey);
await alice.pinServer(spikeServer, spikeServerKey, spikePort);
await alice.register("a$run@$spikeServer:$spikePort");
expect(await alice.accountAddress(), "a$run@$spikeServer:$spikePort");
final bob = FumiNative("${dir.path}/bob.db");
await bob.open();
bob.setMaster(bobMaster);
await bob.pinServer(spikeServer, spikeServerKey);
await bob.pinServer(spikeServer, spikeServerKey, spikePort);
await bob.register("b$run@$spikeServer:$spikePort");
final sent = await alice.send("b$run@$spikeServer:$spikePort",
@ -147,7 +150,7 @@ void main() async {
final alice = FumiNative("${dir.path}/alice.db");
await alice.open();
alice.setMaster(aliceMaster);
await alice.pinServer(spikeServer, spikeServerKey);
await alice.pinServer(spikeServer, spikeServerKey, spikePort);
await alice.register("a$run@$spikeServer:$spikePort");
final bob = FumiNative("${dir.path}/bob.db");
@ -156,7 +159,7 @@ void main() async {
// The onboarding screen zeroes its own master reference on dispose;
// whatever holds the master after that must not share that buffer.
bobMaster.fillRange(0, 32, 0);
await bob.pinServer(spikeServer, spikeServerKey);
await bob.pinServer(spikeServer, spikeServerKey, spikePort);
await bob.register("b$run@$spikeServer:$spikePort");
await alice.send("b$run@$spikeServer:$spikePort",

View file

@ -36,7 +36,7 @@ class StubClient extends SmolClient {
@override
Future<SmolAddress> restoreAndRecall(String masterHex, String addressText) async {
final addr = parseAddress(addressText);
if (store.serverPin(addr.host) == null) {
if (store.serverPin(addr.host, addr.port) == null) {
throw SmolError("no pinned key for ${addr.host}");
}
store.restoreMaster(unhex(masterHex.trim()), 0);

View file

@ -8,6 +8,7 @@ import "dart:typed_data";
import "package:flutter_test/flutter_test.dart";
import "package:hive_flutter/hive_flutter.dart";
import "package:smol_mail/smol/address.dart";
import "package:smol_mail/smol/store.dart";
Uint8List randomBytes(int n) =>
@ -66,11 +67,11 @@ void main() {
expect(store.allPins(), isEmpty);
// A syntactically valid key: the system server's, a real 52-char form.
const key = "lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq";
store.pinServer("example.org", key);
expect(store.serverPin("example.org"), key);
store.pinServer("example.org", key, defaultPort);
expect(store.serverPin("example.org", defaultPort), key);
expect(store.allPins().length, 1);
await store.unpinServer("example.org");
expect(store.serverPin("example.org"), isNull);
await store.unpinServer("example.org", defaultPort);
expect(store.serverPin("example.org", defaultPort), isNull);
});
test("wipe clears every secret and mark, overwriting the master", () async {
@ -80,7 +81,7 @@ void main() {
// The store's own copy, read back before the wipe.
final stored = store.master()!;
store.pinServer("example.org",
"lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq");
"lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq", defaultPort);
store.markRead("cd" * 32);
await store.wipe();
@ -93,7 +94,7 @@ void main() {
expect(master.every((b) => b == 0), isFalse);
expect(store.master(), isNull);
expect(store.identity(), isNull);
expect(store.serverPin("example.org"), isNull);
expect(store.serverPin("example.org", defaultPort), isNull);
expect(store.isRead("cd" * 32), isFalse);
});
}