From 28be8ab0cdd8939f5aed05a4d9688a9f62afc910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Mon, 30 Dec 2019 05:39:08 +0100 Subject: Add TODO in plugin_e2ee _decrypt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- poezio/plugin_e2ee.py | 1 + 1 file changed, 1 insertion(+) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index d4b26d46..2104a628 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -353,6 +353,7 @@ class E2EEPlugin(BasePlugin): if not has_eme and self.encrypted_tags is not None: for (namespace, tag) in self.encrypted_tags: if message.xml.find('{%s}%s' % (namespace, tag)) is not None: + # TODO: count all encrypted tags. has_encrypted_tag = True break -- cgit v1.2.3 From 323d39f932a79915ffd45ff5ab6ab70658d1bd6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Mon, 30 Dec 2019 17:03:57 +0100 Subject: Tabs: add by_jid search method for tabs who have a jid attr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- poezio/core/tabs.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/poezio/core/tabs.py b/poezio/core/tabs.py index 3d4db8b0..abea7313 100644 --- a/poezio/core/tabs.py +++ b/poezio/core/tabs.py @@ -26,6 +26,7 @@ disabled. from typing import List, Dict, Type, Optional, Union from collections import defaultdict +from slixmpp import JID from poezio import tabs from poezio.events import EventHandler @@ -38,6 +39,7 @@ class Tabs: '_current_index', '_current_tab', '_tabs', + '_tab_jids', '_tab_types', '_tab_names', '_previous_tab', @@ -56,6 +58,7 @@ class Tabs: self._previous_tab = None # type: Optional[tabs.Tab] self._tabs = [] # type: List[tabs.Tab] + self._tab_jids = dict() # type: Dict[JID, tabs.Tab] self._tab_types = defaultdict( list) # type: Dict[Type[tabs.Tab], List[tabs.Tab]] self._tab_names = dict() # type: Dict[str, tabs.Tab] @@ -111,6 +114,10 @@ class Tabs: """Return the tab list""" return self._tabs + def by_jid(self, jid: JID) -> Optional[tabs.Tab]: + """Get a tab with a specific jid""" + return self._tab_jids.get(jid) + def by_name(self, name: str) -> Optional[tabs.Tab]: """Get a tab with a specific name""" return self._tab_names.get(name) @@ -142,11 +149,14 @@ class Tabs: return None def _rebuild(self): + self._tab_jids = dict() self._tab_types = defaultdict(list) self._tab_names = dict() for tab in self._tabs: for cls in _get_tab_types(tab): self._tab_types[cls].append(tab) + if hasattr(tab, 'jid'): + self._tab_jids[tab.jid] = tab self._tab_names[tab.name] = tab self._update_numbers() @@ -206,6 +216,8 @@ class Tabs: self._tabs.append(tab) for cls in _get_tab_types(tab): self._tab_types[cls].append(tab) + if hasattr(tab, 'jid'): + self._tab_jids[tab.jid] = tab self._tab_names[tab.name] = tab def delete(self, tab: tabs.Tab, gap=False): @@ -222,6 +234,8 @@ class Tabs: for cls in _get_tab_types(tab): self._tab_types[cls].remove(tab) + if hasattr(tab, 'jid'): + del self._tab_jids[tab.jid] del self._tab_names[tab.name] if gap: -- cgit v1.2.3 From 238eb8a0a1e02bbe491ebcfd34c3bda1b5f19cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Mon, 30 Dec 2019 17:16:15 +0100 Subject: E2EEPlugin: Mute some lint warnings because of metaclass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- poezio/plugin_e2ee.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 2104a628..bcaba5e3 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -395,8 +395,10 @@ class E2EEPlugin(BasePlugin): # Call the enabled encrypt method func = self._enabled_tabs[jid] if iscoroutinefunction(func): + # pylint: disable=unexpected-keyword-arg await func(message, tab, passthrough=True) else: + # pylint: disable=unexpected-keyword-arg func(message, tab, passthrough=True) if has_body: -- cgit v1.2.3 From 92e81d8f877bc69f83a4673695ccb712d5542891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Tue, 31 Dec 2019 09:05:29 +0100 Subject: Allow encryption in normal messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- poezio/plugin_e2ee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index bcaba5e3..e9593777 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -368,7 +368,7 @@ class E2EEPlugin(BasePlugin): return None async def _encrypt(self, stanza: StanzaBase) -> Optional[StanzaBase]: - if not isinstance(stanza, Message) or stanza['type'] not in ('chat', 'groupchat'): + if not isinstance(stanza, Message) or stanza['type'] not in ('normal', 'chat', 'groupchat'): raise NothingToEncrypt() message = stanza -- cgit v1.2.3 From 713471634340cfb170becf9a210baa547ff2707c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Tue, 31 Dec 2019 10:26:59 +0100 Subject: E2EE MUC support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change transmits the original JID of the sender (in decrypt) or receiver(s) (in encrypt). Handling of MUC is not complete. It is possible that some participants have access to realjids while others don't (e.g., moderators in semi-anon MUCs). The code currently doesn't handle this and this will cause at least two issues: - Sending an encrypted message in a semi-anon MUC would reveal the sender's identity (public key) - Recipients wouldn't be able to decrypt this message as they don't have access to the sender's realjid. Unless they already have the bundle available and then they could associate the public key with a jid (another privacy issue/defeating the point of semi-anon rooms). TODO: Fix this ^ Signed-off-by: Maxime “pep” Buquet --- poezio/plugin.py | 1 + poezio/plugin_e2ee.py | 80 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/poezio/plugin.py b/poezio/plugin.py index 61e0ea87..94cb330f 100644 --- a/poezio/plugin.py +++ b/poezio/plugin.py @@ -91,6 +91,7 @@ class SafetyMetaclass(type): async def async_helper(*args, **kwargs): passthrough = kwargs.pop('passthrough', False) try: + log.debug('FOO: %r, %r', args, kwargs) return await f(*args, **kwargs) except: if passthrough: diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index e9593777..daf00818 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -336,7 +336,9 @@ class E2EEPlugin(BasePlugin): dump_tuple(get_theme().COLOR_CHAR_NACK), exc, ) - tab.nack_message(msg, stanza['id'], stanza['from']) + # XXX: check before commit. Do we not nack in MUCs? + if not isinstance(tab, MucTab): + tab.nack_message(msg, stanza['id'], stanza['from']) # TODO: display exceptions to the user properly log.error('Exception in encrypt:', exc_info=True) return None @@ -362,7 +364,24 @@ class E2EEPlugin(BasePlugin): log.debug('Received %s message: %r', self.encryption_name, message['body']) - self.decrypt(message, tab) + # Get the original JID of the sender. The JID might be None if it + # comes from a semi-anonymous MUC for example. Some plugins might be + # fine with this so let them handle it. + jid = message['from'] + muctab = tab + + if isinstance(muctab, PrivateTab): + muctab = tab.parent_muc + jid = None + + if isinstance(muctab, MucTab): + nick = message['from'].resource + for user in muctab.users: + if user.nick == nick: + jid = user.jid or None + break + + self.decrypt(message, jid, tab) log.debug('Decrypted %s message: %r', self.encryption_name, message['body']) return None @@ -372,9 +391,46 @@ class E2EEPlugin(BasePlugin): raise NothingToEncrypt() message = stanza - jid = stanza['to'] - tab = self.core.tabs.by_name_and_class(jid, ChatTab) - if not self._encryption_enabled(jid): + # Find who to encrypt to. If in a groupchat this can be multiple JIDs. + # It is possible that we are not able to find a jid (e.g., semi-anon + # MUCs). Let the plugin decide what to do with this information. + jids = [message['to']] # type: Optional[List[JID]] + tab = self.core.tabs.by_jid(message['to']) + if tab is None: # When does that ever happen? + log.debug('Attempting to encrypt a message to \'%s\' ' + 'that is not attached to a Tab. ?! Aborting ' + 'encryption.', message['to']) + return None + + parent = None + if isinstance(tab, PrivateTab): + parent = tab.parent_muc + nick = tab.jid.resource + jids = None + + for user in parent.users: + if user.nick == nick: + jids = user.jid or None + break + + if isinstance(tab, MucTab): + jids = [] + for user in tab.users: + # If the JID of a user is None, assume all others are None and + # we are in a (at least) semi-anon room. TODO: Really check if + # the room is semi-anon. Currently a moderator of a semi-anon + # room will possibly encrypt to everybody, leaking their + # public key/identity, and they wouldn't be able to decrypt it + # anyway if they don't know the moderator's JID. + # TODO: Change MUC to give easier access to this information. + if user.jid is None: + jids = None + break + # If we encrypt to all of these JIDs is up to the plugin, we + # just tell it who is in the room. + jids.append(user.jid) + + if not self._encryption_enabled(tab.jid): raise NothingToEncrypt() log.debug('Sending %s message: %r', self.encryption_name, message) @@ -393,13 +449,13 @@ class E2EEPlugin(BasePlugin): return None # Call the enabled encrypt method - func = self._enabled_tabs[jid] + func = self._enabled_tabs[tab.jid] if iscoroutinefunction(func): # pylint: disable=unexpected-keyword-arg - await func(message, tab, passthrough=True) + await func(message, jids, tab, passthrough=True) else: # pylint: disable=unexpected-keyword-arg - func(message, tab, passthrough=True) + func(message, jids, tab, passthrough=True) if has_body: # Only add EME tag if the message has a body. @@ -440,13 +496,16 @@ class E2EEPlugin(BasePlugin): option_name = '%s:%s' % (self.encryption_short_name, fingerprint) return config.get(option=option_name, section=jid) - async def decrypt(self, _message: Message, tab: ChatTabs): + async def decrypt(self, message: Message, jid: Optional[JID], tab: ChatTab): """Decryption method This is a method the plugin must implement. It is expected that this method will edit the received message and return nothing. :param message: Message to be decrypted. + :param jid: Real Jid of the sender if available. We might be + talking through a semi-anonymous MUC where real JIDs are + not available. :param tab: Tab the message is coming from. :returns: None @@ -454,13 +513,14 @@ class E2EEPlugin(BasePlugin): raise NotImplementedError - async def encrypt(self, _message: Message, tab: ChatTabs): + async def encrypt(self, message: Message, jids: Optional[List[JID]], tab: ChatTabs): """Encryption method This is a method the plugin must implement. It is expected that this method will edit the received message and return nothing. :param message: Message to be encrypted. + :param jids: Real Jids of all possible recipients. :param tab: Tab the message is going to. :returns: None -- cgit v1.2.3