fix(matrix): handle SAS device verification

This commit is contained in:
mytechdream
2026-05-31 01:00:14 +08:00
committed by Xubin Ren
parent 0cc58a80a4
commit 68712fc489
3 changed files with 294 additions and 0 deletions
+192
View File
@@ -50,7 +50,15 @@ class _FakeAsyncClient:
self.stop_sync_forever_called = False
self.join_calls: list[str] = []
self.callbacks: list[tuple[object, object]] = []
self.to_device_callbacks: list[tuple[object, object]] = []
self.response_callbacks: list[tuple[object, object]] = []
self.key_verifications: dict[str, object] = {}
self.accept_key_verification_calls: list[str] = []
self.confirm_short_auth_string_calls: list[str] = []
self.to_device_calls: list[object] = []
self.accept_key_verification_response: object | None = None
self.confirm_short_auth_string_response: object | None = None
self.to_device_response: object | None = None
self.rooms: dict[str, object] = {}
self.room_send_calls: list[dict[str, object]] = []
self.typing_calls: list[tuple[str, bool, int]] = []
@@ -70,6 +78,9 @@ class _FakeAsyncClient:
def add_event_callback(self, callback, event_type) -> None:
self.callbacks.append((callback, event_type))
def add_to_device_callback(self, callback, event_type) -> None:
self.to_device_callbacks.append((callback, event_type))
def add_response_callback(self, callback, response_type) -> None:
self.response_callbacks.append((callback, response_type))
@@ -82,6 +93,18 @@ class _FakeAsyncClient:
async def join(self, room_id: str) -> None:
self.join_calls.append(room_id)
async def accept_key_verification(self, transaction_id: str):
self.accept_key_verification_calls.append(transaction_id)
return self.accept_key_verification_response
async def confirm_short_auth_string(self, transaction_id: str):
self.confirm_short_auth_string_calls.append(transaction_id)
return self.confirm_short_auth_string_response
async def to_device(self, message):
self.to_device_calls.append(message)
return self.to_device_response
async def room_send(
self,
room_id: str,
@@ -166,6 +189,61 @@ class _FakeAsyncClient:
return None
class _FakeSas:
def __init__(self) -> None:
self.share_key_called = False
self.get_mac_called = False
def share_key(self):
self.share_key_called = True
return {"type": "share_key"}
def get_mac(self):
self.get_mac_called = True
return {"type": "mac"}
class _FakeKeyVerificationStart:
def __init__(
self,
*,
sender: str = "@alice:matrix.org",
transaction_id: str = "tx1",
short_authentication_string: list[str] | None = None,
) -> None:
self.sender = sender
self.transaction_id = transaction_id
self.short_authentication_string = short_authentication_string or ["emoji"]
class _FakeKeyVerificationKey:
def __init__(
self,
*,
sender: str = "@alice:matrix.org",
transaction_id: str = "tx1",
) -> None:
self.sender = sender
self.transaction_id = transaction_id
class _FakeKeyVerificationMac:
def __init__(
self,
*,
sender: str = "@alice:matrix.org",
transaction_id: str = "tx1",
) -> None:
self.sender = sender
self.transaction_id = transaction_id
def _patch_key_verification_events(monkeypatch) -> None:
monkeypatch.setattr(matrix_module, "KeyVerificationStart", _FakeKeyVerificationStart)
monkeypatch.setattr(matrix_module, "KeyVerificationKey", _FakeKeyVerificationKey)
monkeypatch.setattr(matrix_module, "KeyVerificationMac", _FakeKeyVerificationMac)
def _make_config(**kwargs) -> MatrixConfig:
kwargs.setdefault("allow_from", ["*"])
return MatrixConfig(
@@ -209,6 +287,7 @@ async def test_start_skips_load_store_when_device_id_missing(
assert clients[0].config.encryption_enabled is True
assert clients[0].load_store_called is False
assert len(clients[0].callbacks) == 3
assert clients[0].to_device_callbacks == []
assert len(clients[0].response_callbacks) == 3
await channel.stop()
@@ -227,6 +306,119 @@ async def test_register_event_callbacks_uses_media_base_filter() -> None:
assert client.callbacks[1][1] == matrix_module.MATRIX_MEDIA_EVENT_FILTER
def test_register_to_device_callbacks_when_sas_verification_enabled() -> None:
channel = MatrixChannel(_make_config(sas_verification=True), MessageBus())
client = _FakeAsyncClient("", "", "", None)
channel.client = client
channel._register_to_device_callbacks()
assert client.to_device_callbacks == [
(channel._on_key_verification_event, (matrix_module.KeyVerificationEvent,))
]
def test_register_to_device_callbacks_skips_when_e2ee_disabled() -> None:
channel = MatrixChannel(
_make_config(e2ee_enabled=False, sas_verification=True),
MessageBus(),
)
client = _FakeAsyncClient("", "", "", None)
channel.client = client
channel._register_to_device_callbacks()
assert client.to_device_callbacks == []
@pytest.mark.asyncio
async def test_sas_verification_start_accepts_allowed_sender(monkeypatch) -> None:
_patch_key_verification_events(monkeypatch)
channel = MatrixChannel(
_make_config(allow_from=["@alice:matrix.org"], sas_verification=True),
MessageBus(),
)
client = _FakeAsyncClient("", "", "", None)
sas = _FakeSas()
client.key_verifications["tx1"] = sas
channel.client = client
await channel._handle_key_verification_event(_FakeKeyVerificationStart())
assert client.accept_key_verification_calls == ["tx1"]
assert sas.share_key_called is True
assert client.to_device_calls == [{"type": "share_key"}]
@pytest.mark.asyncio
async def test_sas_verification_ignores_denied_sender(monkeypatch) -> None:
_patch_key_verification_events(monkeypatch)
channel = MatrixChannel(
_make_config(allow_from=["@alice:matrix.org"], sas_verification=True),
MessageBus(),
)
client = _FakeAsyncClient("", "", "", None)
client.key_verifications["tx1"] = _FakeSas()
channel.client = client
await channel._handle_key_verification_event(
_FakeKeyVerificationStart(sender="@mallory:matrix.org")
)
assert client.accept_key_verification_calls == []
assert client.to_device_calls == []
@pytest.mark.asyncio
async def test_sas_verification_ignores_when_disabled(monkeypatch) -> None:
_patch_key_verification_events(monkeypatch)
channel = MatrixChannel(
_make_config(allow_from=["@alice:matrix.org"], sas_verification=False),
MessageBus(),
)
client = _FakeAsyncClient("", "", "", None)
client.key_verifications["tx1"] = _FakeSas()
channel.client = client
await channel._handle_key_verification_event(_FakeKeyVerificationStart())
assert client.accept_key_verification_calls == []
assert client.to_device_calls == []
@pytest.mark.asyncio
async def test_sas_verification_key_confirms_allowed_sender(monkeypatch) -> None:
_patch_key_verification_events(monkeypatch)
channel = MatrixChannel(
_make_config(allow_from=["@alice:matrix.org"], sas_verification=True),
MessageBus(),
)
client = _FakeAsyncClient("", "", "", None)
channel.client = client
await channel._handle_key_verification_event(_FakeKeyVerificationKey())
assert client.confirm_short_auth_string_calls == ["tx1"]
@pytest.mark.asyncio
async def test_sas_verification_mac_sends_mac_for_allowed_sender(monkeypatch) -> None:
_patch_key_verification_events(monkeypatch)
channel = MatrixChannel(
_make_config(allow_from=["@alice:matrix.org"], sas_verification=True),
MessageBus(),
)
client = _FakeAsyncClient("", "", "", None)
sas = _FakeSas()
client.key_verifications["tx1"] = sas
channel.client = client
await channel._handle_key_verification_event(_FakeKeyVerificationMac())
assert sas.get_mac_called is True
assert client.to_device_calls == [{"type": "mac"}]
def test_media_event_filter_does_not_match_text_events() -> None:
assert not issubclass(matrix_module.RoomMessageText, matrix_module.MATRIX_MEDIA_EVENT_FILTER)