diff --git a/pantalaimon/client.py b/pantalaimon/client.py index dabc36a..807c7dc 100644 --- a/pantalaimon/client.py +++ b/pantalaimon/client.py @@ -9,12 +9,15 @@ from nio import (AsyncClient, ClientConfig, EncryptionError, KeyVerificationEvent, LocalProtocolError, KeyVerificationStart, KeyVerificationKey, KeyVerificationMac) from nio.store import SqliteStore +from nio.crypto import Sas from pantalaimon.log import logger from pantalaimon.thread_messages import ( DevicesMessage, - DeviceAuthStringMessage, - InfoMessage + InviteSasSignal, + ShowSasSignal, + SasDoneSignal, + DaemonResponse ) @@ -72,9 +75,8 @@ class PanClient(AsyncClient): } } - async def send_info(self, string): - """Send a info message to the UI thread.""" - message = InfoMessage(string) + async def send_message(self, message): + """Send a thread message to the UI thread.""" await self.queue.put(message) async def sync_tasks(self, response): @@ -125,10 +127,11 @@ class PanClient(AsyncClient): logger.info(f"{event.sender} via {event.from_device} has started " f"a key verification process.") - message = DeviceStartSasMessage( + message = InviteSasSignal( self.user_id, event.sender, - event.from_device + event.from_device, + event.transaction_id ) task = loop.create_task( @@ -144,10 +147,11 @@ class PanClient(AsyncClient): device = sas.other_olm_device emoji = sas.get_emoji() - message = DeviceAuthStringMessage( + message = ShowSasSignal( self.user_id, device.user_id, device.id, + sas.transaction_id, emoji ) @@ -163,11 +167,14 @@ class PanClient(AsyncClient): device = sas.other_olm_device if sas.verified: - task = loop.create_task( - self.send_info(f"Device {device.id} of user " - f"{device.user_id} succesfully " - f"verified.") - ) + task = loop.create_task(self.send_message( + SasDoneSignal( + self.user_id, + device.user_id, + device.id, + sas.transaction_id + ) + )) self.key_verificatins_tasks.append(task) def start_loop(self): @@ -199,13 +206,42 @@ class PanClient(AsyncClient): sas = self.get_active_sas(user_id, device_id) if not sas: - await self.send_info("No such verification process found.") + await self.send_message( + DaemonResponse( + message.message_id, + self.user_id, + Sas._txid_error[0], + Sas._txid_error[1] + ) + + ) return try: await self.accept_key_verification(sas.transaction_id) - except (LocalProtocolError, ClientConnectionError) as e: - await self.send_info(f"Error accepting key verification: {e}") + await self.send_message( + DaemonResponse( + message.message_id, + self.user_id, + "m.ok", + "Successfully accepted the key verification request" + )) + except LocalProtocolError as e: + await self.send_message( + DaemonResponse( + message.message_id, + self.user_id, + Sas._unexpected_message_error[0], + e + )) + except ClientConnectionError as e: + await self.send_message( + DaemonResponse( + message.message_id, + self.user_id, + "m.connection_error", + e + )) async def confirm_sas(self, message): user_id = message.user_id @@ -214,20 +250,49 @@ class PanClient(AsyncClient): sas = self.get_active_sas(user_id, device_id) if not sas: - await self.send_info("No such verification process found.") + await self.send_message( + DaemonResponse( + message.message_id, + self.user_id, + Sas._txid_error[0], + Sas._txid_error[1] + ) + + ) return try: await self.confirm_short_auth_string(sas.transaction_id) except ClientConnectionError as e: - await self.send_info(f"Error confirming short auth string: {e}") + await self.send_message( + DaemonResponse( + message.message_id, + self.user_id, + "m.connection_error", + e + )) + + return device = sas.other_olm_device + if sas.verified: - await self.send_info(f"Device {device.id} of user {device.user_id}" - f" succesfully verified.") + await self.send_message( + SasDoneSignal( + self.user_id, + device.user_id, + device.id, + sas.transaction_id + ) + ) else: - await self.send_info(f"Waiting for {device.user_id} to confirm...") + await self.send_message( + DaemonResponse( + message.message_id, + self.user_id, + "m.ok", + f"Waiting for {device.user_id} to confirm." + )) async def loop_stop(self): """Stop the client loop.""" diff --git a/pantalaimon/daemon.py b/pantalaimon/daemon.py index b1a320a..92236d8 100755 --- a/pantalaimon/daemon.py +++ b/pantalaimon/daemon.py @@ -24,8 +24,9 @@ from pantalaimon.thread_messages import ( ExportKeysMessage, ImportKeysMessage, DeviceConfirmSasMessage, - DeviceAcceptSasMessage, - InfoMessage + SasMessage, + AcceptSasMessage, + DaemonResponse ) @@ -89,7 +90,7 @@ class ProxyDaemon: pan_client.start_loop() - async def _verify_device(self, client, device): + async def _verify_device(self, message_id, client, device): ret = client.verify_device(device) if ret: @@ -100,9 +101,9 @@ class ProxyDaemon: f"{device.user_id} already verified") logger.info(msg) - await self.send_info(msg) + await self.send_response(message_id, client.user_id, "m.ok", msg) - async def _unverify_device(self, client, device): + async def _unverify_device(self, message_id, client, device): ret = client.unverify_device(device) if ret: @@ -113,11 +114,11 @@ class ProxyDaemon: f"{device.user_id} already unverified") logger.info(msg) - await self.send_info(msg) + await self.send_response(message_id, client.user_id, "m.ok", msg) - async def send_info(self, string): - """Send a info message to the UI thread.""" - message = InfoMessage(string) + async def send_response(self, message_id, pan_user, code, message): + """Send a thread response message to the UI thread.""" + message = DaemonResponse(message_id, pan_user, code, message) await self.send_queue.put(message) async def receive_message(self, message): @@ -125,8 +126,7 @@ class ProxyDaemon: if isinstance( message, - (DeviceVerifyMessage, DeviceUnverifyMessage, - DeviceConfirmSasMessage, DeviceAcceptSasMessage) + (DeviceVerifyMessage, DeviceUnverifyMessage) ): device = client.device_store[message.user_id].get( @@ -137,15 +137,22 @@ class ProxyDaemon: if not device: msg = (f"No device found for {message.user_id} and " f"{message.device_id}") - await self.send_info(msg) + await self.send_response( + message.message_id, + message.pan_user, + "m.unknown_device", + msg + ) logger.info(msg) return if isinstance(message, DeviceVerifyMessage): - await self._verify_device(client, device) + await self._verify_device(message.message_id, client, device) elif isinstance(message, DeviceUnverifyMessage): - await self._unverify_device(client, device) - elif isinstance(message, DeviceAcceptSasMessage): + await self._unverify_device(message.message_id, client, device) + + elif isinstance(message, SasMessage): + if isinstance(message, AcceptSasMessage): await client.accept_sas(message) elif isinstance(message, DeviceConfirmSasMessage): await client.confirm_sas(message) @@ -160,12 +167,23 @@ class ProxyDaemon: info_msg = (f"Error exporting keys for {client.user_id} to" f" {path} {e}") logger.info(info_msg) - await self.send_info(info_msg) + await self.send_response( + message.message_id, + client.user_id, + "m.os_error", + str(e) + ) + else: info_msg = (f"Succesfully exported keys for {client.user_id} " f"to {path}") logger.info(info_msg) - await self.send_info(info_msg) + await self.send_response( + message.message_id, + client.user_id, + "m.ok", + info_msg + ) elif isinstance(message, ImportKeysMessage): path = os.path.abspath(os.path.expanduser(message.file_path)) @@ -177,12 +195,22 @@ class ProxyDaemon: info_msg = (f"Error importing keys for {client.user_id} " f"from {path} {e}") logger.info(info_msg) - await self.send_info(info_msg) + await self.send_response( + message.message_id, + client.user_id, + "m.os_error", + str(e) + ) else: info_msg = (f"Succesfully imported keys for {client.user_id} " f"from {path}") logger.info(info_msg) - await self.send_info(info_msg) + await self.send_response( + message.message_id, + client.user_id, + "m.ok", + info_msg + ) def get_access_token(self, request): # type: (aiohttp.web.BaseRequest) -> str diff --git a/pantalaimon/main.py b/pantalaimon/main.py index cd55378..3a0b7f3 100644 --- a/pantalaimon/main.py +++ b/pantalaimon/main.py @@ -14,7 +14,7 @@ from logbook import StderrHandler from aiohttp import web from pantalaimon.ui import GlibT -from pantalaimon.thread_messages import InfoMessage +from pantalaimon.thread_messages import DaemonResponse from pantalaimon.daemon import ProxyDaemon from pantalaimon.config import PanConfig, PanConfigError, parse_log_level from pantalaimon.log import logger @@ -81,8 +81,8 @@ async def message_router(receive_queue, send_queue, proxies): return None - async def send_info(string): - message = InfoMessage(string) + async def send_info(message_id, pan_user, code, string): + message = DaemonResponse(message_id, pan_user, code, string) await send_queue.put(message) while True: @@ -94,7 +94,12 @@ async def message_router(receive_queue, send_queue, proxies): if not proxy: msg = f"No pan client found for {message.pan_user}." logger.warn(msg) - send_info(msg) + await send_info( + message.message_id, + message.pan_user, + "m.unknown_client", + msg + ) await proxy.receive_message(message) diff --git a/pantalaimon/panctl.py b/pantalaimon/panctl.py index 6a0bdc5..b837ba9 100644 --- a/pantalaimon/panctl.py +++ b/pantalaimon/panctl.py @@ -276,15 +276,34 @@ class PanCtl: self.ctl = self.pan_bus["org.pantalaimon1.control"] self.devices = self.pan_bus["org.pantalaimon1.devices"] - self.ctl.Info.connect(self.show_info) - self.devices.SasReceived.connect(self.show_sas) + self.own_message_ids = [] - def show_info(self, message): - print(message) + self.ctl.Response.connect(self.show_response) + self.devices.VerificationInvite.connect(self.show_sas_invite) + self.devices.VerificationString.connect(self.show_sas) + self.devices.VerificationDone.connect(self.sas_done) + + def show_response(self, response_id, pan_user, message): + if response_id not in self.own_message_ids: + return + + self.own_message_ids.remove(response_id) + + print(message["message"]) + + def sas_done(self, pan_user, user_id, device_id, _): + print(f"Device {device_id} of user {user_id}" + f" succesfully verified for pan user {pan_user}.") + + def show_sas_invite(self, pan_user, user_id, device_id, _): + print(f"{user_id} has started an interactive device " + f"verification for his device {device_id} with pan user " + f"{pan_user}\n" + f"Accept the invitation with the accept-verification command.") # The emoji printing logic was taken from weechat-matrix and was written by # dkasak. - def show_sas(self, pan_user, user_id, device_id, emoji): + def show_sas(self, pan_user, user_id, device_id, _, emoji): emojis = [x[0] for x in emoji] descriptions = [x[1] for x in emoji] @@ -378,27 +397,39 @@ class PanCtl: self.list_users() elif command == "import-keys": - self.ctl.ImportKeys(args.pan_user, args.path, args.passphrase) + self.own_message_ids.append( + self.ctl.ImportKeys( + args.pan_user, + args.path, + args.passphrase + )) elif command == "export-keys": - self.ctl.ExportKeys(args.pan_user, args.path, args.passphrase) + self.own_message_ids.append( + self.ctl.ExportKeys( + args.pan_user, + args.path, + args.passphrase + )) elif command == "list-devices": self.list_devices(args) elif command == "accept-verification": - self.devices.AcceptKeyVerification( - args.pan_user, - args.user_id, - args.device_id - ) + self.own_message_ids.append( + self.devices.AcceptKeyVerification( + args.pan_user, + args.user_id, + args.device_id + )) elif command == "confirm-verification": - self.devices.ConfirmKeyVerification( - args.pan_user, - args.user_id, - args.device_id - ) + self.own_message_ids.append( + self.devices.ConfirmKeyVerification( + args.pan_user, + args.user_id, + args.device_id + )) def main(): diff --git a/pantalaimon/thread_messages.py b/pantalaimon/thread_messages.py index f4c4d51..48f21bc 100644 --- a/pantalaimon/thread_messages.py +++ b/pantalaimon/thread_messages.py @@ -7,8 +7,11 @@ class Message: @attr.s -class InfoMessage(Message): - string = attr.ib() +class DaemonResponse(Message): + message_id = attr.ib() + pan_user = attr.ib() + code = attr.ib() + message = attr.ib() @attr.s @@ -19,6 +22,7 @@ class DevicesMessage(Message): @attr.s class _KeysOperation(Message): + message_id = attr.ib() pan_user = attr.ib() file_path = attr.ib() passphrase = attr.ib() @@ -36,6 +40,7 @@ class ExportKeysMessage(_KeysOperation): @attr.s class _VerificationMessage(Message): + message_id = attr.ib() pan_user = attr.ib() user_id = attr.ib() device_id = attr.ib() @@ -52,20 +57,43 @@ class DeviceUnverifyMessage(_VerificationMessage): @attr.s -class DeviceStartSasMessage(_VerificationMessage): +class SasMessage(_VerificationMessage): pass @attr.s -class DeviceAcceptSasMessage(_VerificationMessage): +class DeviceConfirmSasMessage(SasMessage): pass @attr.s -class DeviceConfirmSasMessage(_VerificationMessage): +class AcceptSasMessage(SasMessage): pass @attr.s -class DeviceAuthStringMessage(_VerificationMessage): - short_string = attr.ib() +class _SasSignal: + pan_user = attr.ib() + user_id = attr.ib() + device_id = attr.ib() + transaction_id = attr.ib() + + +@attr.s +class StartSasSignal(_SasSignal): + pass + + +@attr.s +class InviteSasSignal(_SasSignal): + pass + + +@attr.s +class ShowSasSignal(_SasSignal): + emoji = attr.ib() + + +@attr.s +class SasDoneSignal(_SasSignal): + pass diff --git a/pantalaimon/ui.py b/pantalaimon/ui.py index db5375b..77a78b4 100644 --- a/pantalaimon/ui.py +++ b/pantalaimon/ui.py @@ -12,16 +12,31 @@ from pantalaimon.thread_messages import ( DeviceVerifyMessage, DeviceUnverifyMessage, DevicesMessage, - InfoMessage, - DeviceAcceptSasMessage, + AcceptSasMessage, DeviceConfirmSasMessage, - DeviceAuthStringMessage, ImportKeysMessage, ExportKeysMessage, + StartSasSignal, + ShowSasSignal, + InviteSasSignal, + SasDoneSignal, + DaemonResponse ) from pantalaimon.log import logger +class IdCounter: + def __init__(self): + self._message_id = 0 + + @property + def message_id(self): + ret = self._message_id + self._message_id += 1 + + return ret + + class Control: """ @@ -34,40 +49,59 @@ class Control: + + - - + + + + """ - def __init__(self, queue, user_list=None): + Response = signal() + + def __init__(self, queue, user_list, id_counter): self.users = user_list self.queue = queue + self.id_counter = id_counter + + @property + def message_id(self): + return self.id_counter.message_id def ListUsers(self): """Return the list of pan users.""" return self.users def ExportKeys(self, pan_user, filepath, passphrase): - message = ExportKeysMessage(pan_user, filepath, passphrase) + message = ExportKeysMessage( + self.message_id, + pan_user, + filepath, + passphrase + ) self.queue.put(message) - return + return message.message_id def ImportKeys(self, pan_user, filepath, passphrase): - message = ImportKeysMessage(pan_user, filepath, passphrase) + message = ImportKeysMessage( + self.message_id, + pan_user, + filepath, + passphrase + ) self.queue.put(message) - return - - Info = signal() + return message.message_id class Devices: @@ -89,30 +123,64 @@ class Devices: + + - + + + + + + + + + + + + + + + + + + + + + + + + + """ - SasReceived = signal() + VerificationInvite = signal() + VerificationCancel = signal() + VerificationString = signal() + VerificationDone = signal() - def __init__(self, queue, device_list): + def __init__(self, queue, device_list, id_counter): self.device_list = device_list self.queue = queue + self.id_counter = id_counter + + @property + def message_id(self): + return self.id_counter.message_id def List(self, pan_user): device_store = self.device_list.get(pan_user, None) @@ -170,14 +238,25 @@ class Devices: return def ConfirmKeyVerification(self, pan_user, user_id, device_id): - message = DeviceConfirmSasMessage(pan_user, user_id, device_id) + message = DeviceConfirmSasMessage( + self.message_id, + pan_user, + user_id, + device_id + ) + print("HEEEELOOO {}".format(message.message_id)) self.queue.put(message) - return + return message.message_id def AcceptKeyVerification(self, pan_user, user_id, device_id): - message = DeviceAcceptSasMessage(pan_user, user_id, device_id) + message = AcceptSasMessage( + self.message_id, + pan_user, + user_id, + device_id + ) self.queue.put(message) - return + return message.message_id def update_devices(self, message): device_store = self.device_list[message.user_id] @@ -217,8 +296,10 @@ class GlibT: self.users = self.store.load_all_users() self.devices = self.store.load_all_devices() - self.control_if = Control(self.send_queue, self.users) - self.device_if = Devices(self.send_queue, self.devices) + id_counter = IdCounter() + + self.control_if = Control(self.send_queue, self.users, id_counter) + self.device_if = Devices(self.send_queue, self.devices, id_counter) self.bus = SessionBus() self.bus.publish("org.pantalaimon1", self.control_if, self.device_if) @@ -234,16 +315,40 @@ class GlibT: if isinstance(message, DevicesMessage): self.device_if.update_devices(message) - elif isinstance(message, DeviceAuthStringMessage): - self.device_if.SasReceived( + elif isinstance(message, InviteSasSignal): + self.device_if.VerificationInvite( message.pan_user, message.user_id, message.device_id, - message.short_string + message.transaction_id ) - elif isinstance(message, InfoMessage): - self.control_if.Info(message.string) + elif isinstance(message, ShowSasSignal): + self.device_if.VerificationString( + message.pan_user, + message.user_id, + message.device_id, + message.transaction_id, + message.emoji, + ) + + elif isinstance(message, SasDoneSignal): + self.device_if.VerificationDone( + message.pan_user, + message.user_id, + message.device_id, + message.transaction_id, + ) + + elif isinstance(message, DaemonResponse): + self.control_if.Response( + message.message_id, + message.pan_user, + { + "code": message.code, + "message": message.message + } + ) self.receive_queue.task_done() return True