From ebbdbc2cbdc6d0aeaf7c4b1ea87af5049882564a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Wed, 23 May 2018 14:05:02 +0100 Subject: OMEMO plugin: first draft MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 124 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 plugins/omemo_plugin.py diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py new file mode 100644 index 00000000..8852aa2b --- /dev/null +++ b/plugins/omemo_plugin.py @@ -0,0 +1,124 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2018 Maxime “pep” Buquet +# +# Distributed under terms of the zlib license. +""" + OMEMO Plugin. +""" + +import os +import asyncio +import textwrap +from poezio.plugin import BasePlugin +from poezio.tabs import ConversationTab +from poezio.config import CACHE_DIR + + +class Plugin(BasePlugin): + def init(self): + self.info = lambda i: self.api.information(i, 'Info') + self.xmpp = self.core.xmpp + + self.info('CACHE_DIR: %r' % CACHE_DIR) + + self.xmpp.register_plugin( + 'xep_0384', { + 'cache_dir': os.path.join(CACHE_DIR, 'omemo.sqlite'), + }) + self.info('FOO') + + self.api.add_command( + 'omemo', + self.command_status, + help='Display contextual information status', + ) + + self.api.add_tab_command( + ConversationTab, + 'omemo_enable', + self.command_enable, + help='Enable OMEMO encryption', + ) + + self.api.add_tab_command( + ConversationTab, + 'omemo_disable', + self.command_disable, + help='Disable OMEMO encryption', + ) + + self.api.add_tab_command( + ConversationTab, + 'omemo_toggle', + self.command_toggle, + help='Toggle OMEMO encryption state', + ) + + self.api.add_command( + 'omemo_clear_devices', + self.command_clear_devices, + help='Clear all other OMEMO devices', + ) + + self.api.add_event_handler( + 'conversation_say_after', + self.on_conversation_say_after, + ) + + self.api.add_event_handler( + 'conversation_msg', + self.on_conversation_msg, + ) + + def command_status(self, _args): + """Display contextual information depending on currenttab.""" + tab = self.api.current_tab() + self.info('OMEMO!') + + def command_enable(self, _args): + pass + + def command_disable(self, args): + pass + + def command_toggle(self, _args): + pass + + def command_clear_devices(self, _args): + asyncio.ensure_future(self.xmpp['xep_0384'].clear_device_list()) + info = """ + Device list has been reset. + Your other devices will reannounce themselves next time they get + online, but they might not be able to read encrypted messages in the + meantime. + """ + self.info(textwrap.dedent(info).strip()) + + def on_conversation_say_after(self, message, tab): + """ + Outbound messages + """ + + # Check encryption status globally and to the contact, if enabled, add + # ['omemo_encrypt'] attribute to message and send. Maybe delete + # ['body'] and tab.add_message ourselves to specify typ=0 so messages + # are not logged. + + fromjid = message['from'] + self.xmpp['xep_0384'].encrypt_message(message) + self.info('Foo1') + + def on_conversation_msg(self, message, _tab): + """ + Inbound messages + """ + + # Check if encrypted, and if so replace message['body'] with + # plaintext. + + self.info('Foo2') + if self.xmpp['xep_0384'].is_encrypted(message): + self.xmpp['xep_0384'].decrypt_message(message) -- cgit v1.2.3 From 2ff48b7adf3e022e6809ce59f6429a387f0f3ffa Mon Sep 17 00:00:00 2001 From: lumi Date: Wed, 23 May 2018 23:08:57 +0200 Subject: Expand the status command, make decryption work, put a hack into the core so I can handle messages without bodies. --- plugins/omemo_plugin.py | 4 +++- poezio/core/core.py | 1 + poezio/core/handlers.py | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 8852aa2b..fa5dab67 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -77,6 +77,7 @@ class Plugin(BasePlugin): """Display contextual information depending on currenttab.""" tab = self.api.current_tab() self.info('OMEMO!') + self.info("My device id: %d" % self.xmpp['xep_0384'].my_device_id()) def command_enable(self, _args): pass @@ -121,4 +122,5 @@ class Plugin(BasePlugin): self.info('Foo2') if self.xmpp['xep_0384'].is_encrypted(message): - self.xmpp['xep_0384'].decrypt_message(message) + _always_none, body = self.xmpp['xep_0384'].decrypt_message(message) + message['body'] = body.decode("utf8") diff --git a/poezio/core/core.py b/poezio/core/core.py index 717ee305..fe6a9d78 100644 --- a/poezio/core/core.py +++ b/poezio/core/core.py @@ -240,6 +240,7 @@ class Core: ('groupchat_subject', self.handler.on_groupchat_subject), ('http_confirm', self.handler.http_confirm), ('message', self.handler.on_message), + ('message_encryption', self.handler.on_encrypted_message), ('message_error', self.handler.on_error_message), ('message_xform', self.handler.on_data_form), ('no_auth', self.handler.on_no_auth), diff --git a/poezio/core/handlers.py b/poezio/core/handlers.py index fc5938f3..8f2f2584 100644 --- a/poezio/core/handlers.py +++ b/poezio/core/handlers.py @@ -271,6 +271,14 @@ class HandlerCore: return self.on_normal_message(message) + def on_encrypted_message(self, message): + """ + When receiving an encrypted message + """ + if message["body"]: + return # Already being handled by on_message. + self.on_message(message) + def on_error_message(self, message): """ When receiving any message with type="error" -- cgit v1.2.3 From 2348f3705c400aec330caef5c0f56c27fa5765d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 18 Nov 2018 11:50:29 +0100 Subject: omemo: Update plugin to using poezio.xdg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index fa5dab67..8759957b 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -14,7 +14,7 @@ import asyncio import textwrap from poezio.plugin import BasePlugin from poezio.tabs import ConversationTab -from poezio.config import CACHE_DIR +from poezio.xdg import CACHE_HOME class Plugin(BasePlugin): @@ -22,13 +22,13 @@ class Plugin(BasePlugin): self.info = lambda i: self.api.information(i, 'Info') self.xmpp = self.core.xmpp - self.info('CACHE_DIR: %r' % CACHE_DIR) + cache_dir = os.path.join(CACHE_HOME, 'omemo') + os.makedirs(cache_dir, exist_ok=True) self.xmpp.register_plugin( 'xep_0384', { - 'cache_dir': os.path.join(CACHE_DIR, 'omemo.sqlite'), + 'cache_dir': cache_dir, }) - self.info('FOO') self.api.add_command( 'omemo', -- cgit v1.2.3 From fdf9b74056ce11c3278ddf9ccdcd0d4820ee7afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Mon, 19 Nov 2018 01:44:48 +0100 Subject: omemo: don't deconstruct the result from the decrypt call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 8759957b..4d3eabec 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -122,5 +122,7 @@ class Plugin(BasePlugin): self.info('Foo2') if self.xmpp['xep_0384'].is_encrypted(message): - _always_none, body = self.xmpp['xep_0384'].decrypt_message(message) + body = self.xmpp['xep_0384'].decrypt_message(message) + if body is None: # Message wasn't decrypted + return None message['body'] = body.decode("utf8") -- cgit v1.2.3 From bab9d1625b27d6622b5566937488172df8e58ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Tue, 20 Nov 2018 13:20:17 +0000 Subject: omemo: catch xep_0384.MissingOwnKey exception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 4d3eabec..efd64722 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -15,7 +15,10 @@ import textwrap from poezio.plugin import BasePlugin from poezio.tabs import ConversationTab from poezio.xdg import CACHE_HOME +from slixmpp.plugins.xep_0384.plugin import MissingOwnKey +import logging +log = logging.getLogger(__name__) class Plugin(BasePlugin): def init(self): @@ -122,7 +125,10 @@ class Plugin(BasePlugin): self.info('Foo2') if self.xmpp['xep_0384'].is_encrypted(message): - body = self.xmpp['xep_0384'].decrypt_message(message) - if body is None: # Message wasn't decrypted + try: + body = self.xmpp['xep_0384'].decrypt_message(message) + except (MissingOwnKey,): + log.debug("The following message is missing our key;" + "Couldn't decrypt: %r", message) return None message['body'] = body.decode("utf8") -- cgit v1.2.3 From e8450bcf518040dbc69fc0e55b3bfc5e8612aaa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 1 Dec 2018 02:15:35 +0000 Subject: omemo: Add dummy sending command for testing purposes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index efd64722..c25e5fea 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -15,8 +15,11 @@ import textwrap from poezio.plugin import BasePlugin from poezio.tabs import ConversationTab from poezio.xdg import CACHE_HOME +from slixmpp import JID from slixmpp.plugins.xep_0384.plugin import MissingOwnKey +from typing import List, Optional + import logging log = logging.getLogger(__name__) @@ -66,6 +69,12 @@ class Plugin(BasePlugin): help='Clear all other OMEMO devices', ) + self.api.add_command( + 'encrypted_message', + self.send_message, + help='Send OMEMO encrypted message', + ) + self.api.add_event_handler( 'conversation_say_after', self.on_conversation_say_after, @@ -101,6 +110,31 @@ class Plugin(BasePlugin): """ self.info(textwrap.dedent(info).strip()) + def send_message(self, _args): + asyncio.ensure_future( + self._send_message( + "Hello Encrypted World!", + [JID('some@jid')], + mto=JID('some@jid'), + mtype='chat', + ), + ) + + async def _send_message( + self, + payload: str, + recipients: List[JID], + mto: Optional[JID] = None, + mtype: Optional[str] = 'chat', + ) -> None: + encrypted = await self.xmpp['xep_0384'].encrypt_message(payload, recipients) + msg = self.core.xmpp.make_message(mto, mtype=mtype) + msg['body'] = 'This message is encrypted with Legacy OMEMO (eu.siacs.conversations.axolotl)' + msg['eme']['namespace'] = 'eu.siacs.conversations.axolotl' + msg.append(encrypted) + log.debug('BAR: message: %r', msg) + msg.send() + def on_conversation_say_after(self, message, tab): """ Outbound messages -- cgit v1.2.3 From 5fb819d1ed7a2b6d942c493ca0234c70b6b2cf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 1 Dec 2018 19:00:43 +0000 Subject: omemo: Use DATA_HOME instead of CACHE_HOME MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index c25e5fea..302a3f00 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -14,7 +14,7 @@ import asyncio import textwrap from poezio.plugin import BasePlugin from poezio.tabs import ConversationTab -from poezio.xdg import CACHE_HOME +from poezio.xdg import DATA_HOME from slixmpp import JID from slixmpp.plugins.xep_0384.plugin import MissingOwnKey @@ -28,12 +28,12 @@ class Plugin(BasePlugin): self.info = lambda i: self.api.information(i, 'Info') self.xmpp = self.core.xmpp - cache_dir = os.path.join(CACHE_HOME, 'omemo') - os.makedirs(cache_dir, exist_ok=True) + data_dir = os.path.join(DATA_HOME, 'omemo') + os.makedirs(data_dir, exist_ok=True) self.xmpp.register_plugin( 'xep_0384', { - 'cache_dir': cache_dir, + 'data_dir': data_dir, }) self.api.add_command( -- cgit v1.2.3 From 87c0ff63640956a1c255e8cf0dede14059d69e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 1 Dec 2018 23:27:06 +0000 Subject: omemo: remove useless log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 302a3f00..e3356b03 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -147,7 +147,6 @@ class Plugin(BasePlugin): fromjid = message['from'] self.xmpp['xep_0384'].encrypt_message(message) - self.info('Foo1') def on_conversation_msg(self, message, _tab): """ -- cgit v1.2.3 From c9091dfac84367c0e418bffc5c5814737a03f505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 1 Dec 2018 23:27:27 +0000 Subject: omemo: consistent returns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index e3356b03..3e1a658f 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -165,3 +165,4 @@ class Plugin(BasePlugin): "Couldn't decrypt: %r", message) return None message['body'] = body.decode("utf8") + return None -- cgit v1.2.3 From 43a97d64303271a8829dde63936d51d7a7e14cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 1 Dec 2018 23:27:56 +0000 Subject: omemo: change /omemo description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 3e1a658f..db1f2919 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -39,7 +39,7 @@ class Plugin(BasePlugin): self.api.add_command( 'omemo', self.command_status, - help='Display contextual information status', + help='Display contextual information', ) self.api.add_tab_command( -- cgit v1.2.3 From 2c720c9d9b69700a42f55c02e4c9be07cbc2bc23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 1 Dec 2018 23:28:49 +0000 Subject: omemo: remove unused commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index db1f2919..6e7aca94 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -56,19 +56,6 @@ class Plugin(BasePlugin): help='Disable OMEMO encryption', ) - self.api.add_tab_command( - ConversationTab, - 'omemo_toggle', - self.command_toggle, - help='Toggle OMEMO encryption state', - ) - - self.api.add_command( - 'omemo_clear_devices', - self.command_clear_devices, - help='Clear all other OMEMO devices', - ) - self.api.add_command( 'encrypted_message', self.send_message, @@ -97,19 +84,6 @@ class Plugin(BasePlugin): def command_disable(self, args): pass - def command_toggle(self, _args): - pass - - def command_clear_devices(self, _args): - asyncio.ensure_future(self.xmpp['xep_0384'].clear_device_list()) - info = """ - Device list has been reset. - Your other devices will reannounce themselves next time they get - online, but they might not be able to read encrypted messages in the - meantime. - """ - self.info(textwrap.dedent(info).strip()) - def send_message(self, _args): asyncio.ensure_future( self._send_message( -- cgit v1.2.3 From 4ab6e64437123c56336eec5d51d079d83f966a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 1 Dec 2018 23:33:00 +0000 Subject: omemo: Add omemo status in the tab infobar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 6e7aca94..df839fc4 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -42,6 +42,10 @@ class Plugin(BasePlugin): help='Display contextual information', ) + ConversationTab.add_information_element('omemo', self.display_encryption_status) + # Waiting for https://lab.louiz.org/poezio/poezio/merge_requests/17 + # MucTab.add_information_element('omemo', self.display_encryption_status) + self.api.add_tab_command( ConversationTab, 'omemo_enable', @@ -72,6 +76,9 @@ class Plugin(BasePlugin): self.on_conversation_msg, ) + def display_encryption_status(self, *_args): + return " OMEMO" + def command_status(self, _args): """Display contextual information depending on currenttab.""" tab = self.api.current_tab() -- cgit v1.2.3 From f13587a386d8d10629bce00c4d81bf11e2d08c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 16 Dec 2018 14:57:35 +0000 Subject: omemo: remove unused imports and reorder them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index df839fc4..10988435 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -11,18 +11,19 @@ import os import asyncio -import textwrap +import logging +from typing import List, Optional + from poezio.plugin import BasePlugin from poezio.tabs import ConversationTab from poezio.xdg import DATA_HOME + from slixmpp import JID from slixmpp.plugins.xep_0384.plugin import MissingOwnKey -from typing import List, Optional - -import logging log = logging.getLogger(__name__) + class Plugin(BasePlugin): def init(self): self.info = lambda i: self.api.information(i, 'Info') -- cgit v1.2.3 From d30c39c6a92118d12cbee979a62eda83a8ea5251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 16 Dec 2018 14:58:18 +0000 Subject: omemo: add docstring on the plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 10988435..f2030905 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -25,6 +25,8 @@ log = logging.getLogger(__name__) class Plugin(BasePlugin): + """OMEMO (XEP-0384) Plugin""" + def init(self): self.info = lambda i: self.api.information(i, 'Info') self.xmpp = self.core.xmpp -- cgit v1.2.3 From 42fa792aa5354d8a387c78514bf16ef857932345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 16 Dec 2018 15:00:23 +0000 Subject: omemo: Add omemo_enable and omemo_disable commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 107 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 14 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index f2030905..daca9e8d 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -12,10 +12,10 @@ import os import asyncio import logging -from typing import List, Optional +from typing import Callable, List, Optional, Set, Tuple, Union from poezio.plugin import BasePlugin -from poezio.tabs import ConversationTab +from poezio.tabs import DynamicConversationTab, StaticConversationTab, ConversationTab, MucTab from poezio.xdg import DATA_HOME from slixmpp import JID @@ -26,6 +26,7 @@ log = logging.getLogger(__name__) class Plugin(BasePlugin): """OMEMO (XEP-0384) Plugin""" + _enabled_jids = set() # type: Set[JID] def init(self): self.info = lambda i: self.api.information(i, 'Info') @@ -46,18 +47,15 @@ class Plugin(BasePlugin): ) ConversationTab.add_information_element('omemo', self.display_encryption_status) - # Waiting for https://lab.louiz.org/poezio/poezio/merge_requests/17 - # MucTab.add_information_element('omemo', self.display_encryption_status) + MucTab.add_information_element('omemo', self.display_encryption_status) - self.api.add_tab_command( - ConversationTab, + self.api.add_command( 'omemo_enable', self.command_enable, help='Enable OMEMO encryption', ) - self.api.add_tab_command( - ConversationTab, + self.api.add_command( 'omemo_disable', self.command_disable, help='Disable OMEMO encryption', @@ -79,8 +77,19 @@ class Plugin(BasePlugin): self.on_conversation_msg, ) - def display_encryption_status(self, *_args): - return " OMEMO" + def cleanup(self) -> None: + ConversationTab.remove_information_element('omemo') + MucTab.remove_information_element('omemo') + + def display_encryption_status(self, jid: JID) -> str: + """ + Return information to display in the infobar if OMEMO is enabled + for the JID. + """ + + if jid in self._enabled_jids: + return " OMEMO" + return "" def command_status(self, _args): """Display contextual information depending on currenttab.""" @@ -88,11 +97,81 @@ class Plugin(BasePlugin): self.info('OMEMO!') self.info("My device id: %d" % self.xmpp['xep_0384'].my_device_id()) - def command_enable(self, _args): - pass + def _jid_from_context(self, jid: Optional[Union[str, JID]]) -> Tuple[Optional[JID], bool]: + """ + Get bare JID from context if not specified + + Return a tuple with the JID and a bool specifying that the JID + corresponds to the current tab. + """ + + tab = self.api.current_tab() + + tab_jid = None + chat_tabs = (DynamicConversationTab, StaticConversationTab, ConversationTab, MucTab) + if isinstance(tab, chat_tabs): + tab_jid = JID(tab.name).bare + + # If current tab has a JID, use it if none is specified + if not jid and tab_jid is not None: + jid = tab_jid + + # We might not have found a JID at this stage. No JID provided and not + # in a tab with a JID (InfoTab etc.). + # If we do, make we + if jid: + # XXX: Ugly. We don't know if 'jid' is a str or a JID. And we want + # to return a bareJID. We could change the JID API to allow us to + # clear the resource one way or another. + jid = JID(JID(jid).bare) + else: + jid = None + + return (jid, tab_jid is not None and tab_jid == jid) + + def command_enable(self, jid: Optional[str]) -> None: + """ + Enable JID to use OMEMO with. + + Use current tab JID is none is specified. Refresh the tab if JID + corresponds to the one being added. + """ + + jid, current_tab = self._jid_from_context(jid) + if jid is None: + return None + + if jid not in self._enabled_jids: + self.info('OMEMO enabled for %s' % jid) + self._enabled_jids.add(jid) + + # Refresh tab if JID matches + if current_tab: + self.api.current_tab().refresh() + + return None + + def command_disable(self, jid: Optional[str]) -> None: + """ + Enable JID to use OMEMO with. + + Use current tab JID is none is specified. Refresh the tab if JID + corresponds to the one being added. + """ + + jid, current_tab = self._jid_from_context(jid) + if jid is None: + return None + + if jid in self._enabled_jids: + self.info('OMEMO disabled for %s' % jid) + self._enabled_jids.remove(jid) + + # Refresh tab if JID matches + if current_tab: + self.api.current_tab().refresh() - def command_disable(self, args): - pass + return None def send_message(self, _args): asyncio.ensure_future( -- cgit v1.2.3 From c8f88658b4202f3f27da6066bb26e64cde07c390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Tue, 2 Jul 2019 00:35:58 +0200 Subject: omemo: adapt to new E2EE API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 366 ++++++++++++++++++++++-------------------------- poezio/plugin_e2ee.py | 6 +- 2 files changed, 167 insertions(+), 205 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index daca9e8d..b396422e 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -10,222 +10,184 @@ """ import os -import asyncio import logging -from typing import Callable, List, Optional, Set, Tuple, Union -from poezio.plugin import BasePlugin -from poezio.tabs import DynamicConversationTab, StaticConversationTab, ConversationTab, MucTab +from poezio.plugin_e2ee import E2EEPlugin from poezio.xdg import DATA_HOME -from slixmpp import JID -from slixmpp.plugins.xep_0384.plugin import MissingOwnKey +from slixmpp.stanza import Message +from slixmpp_omemo import PluginCouldNotLoad, MissingOwnKey, NoAvailableSession +from slixmpp_omemo import UndecidedException, UntrustedException, EncryptionPrepareException +import slixmpp_omemo log = logging.getLogger(__name__) -class Plugin(BasePlugin): +class Plugin(E2EEPlugin): """OMEMO (XEP-0384) Plugin""" - _enabled_jids = set() # type: Set[JID] - def init(self): - self.info = lambda i: self.api.information(i, 'Info') - self.xmpp = self.core.xmpp - - data_dir = os.path.join(DATA_HOME, 'omemo') - os.makedirs(data_dir, exist_ok=True) - - self.xmpp.register_plugin( - 'xep_0384', { - 'data_dir': data_dir, - }) - - self.api.add_command( - 'omemo', - self.command_status, - help='Display contextual information', - ) - - ConversationTab.add_information_element('omemo', self.display_encryption_status) - MucTab.add_information_element('omemo', self.display_encryption_status) - - self.api.add_command( - 'omemo_enable', - self.command_enable, - help='Enable OMEMO encryption', - ) - - self.api.add_command( - 'omemo_disable', - self.command_disable, - help='Disable OMEMO encryption', - ) - - self.api.add_command( - 'encrypted_message', - self.send_message, - help='Send OMEMO encrypted message', - ) - - self.api.add_event_handler( - 'conversation_say_after', - self.on_conversation_say_after, - ) - - self.api.add_event_handler( - 'conversation_msg', - self.on_conversation_msg, - ) - - def cleanup(self) -> None: - ConversationTab.remove_information_element('omemo') - MucTab.remove_information_element('omemo') - - def display_encryption_status(self, jid: JID) -> str: - """ - Return information to display in the infobar if OMEMO is enabled - for the JID. - """ - - if jid in self._enabled_jids: - return " OMEMO" - return "" - - def command_status(self, _args): - """Display contextual information depending on currenttab.""" - tab = self.api.current_tab() - self.info('OMEMO!') - self.info("My device id: %d" % self.xmpp['xep_0384'].my_device_id()) - - def _jid_from_context(self, jid: Optional[Union[str, JID]]) -> Tuple[Optional[JID], bool]: - """ - Get bare JID from context if not specified - - Return a tuple with the JID and a bool specifying that the JID - corresponds to the current tab. - """ - - tab = self.api.current_tab() - - tab_jid = None - chat_tabs = (DynamicConversationTab, StaticConversationTab, ConversationTab, MucTab) - if isinstance(tab, chat_tabs): - tab_jid = JID(tab.name).bare - - # If current tab has a JID, use it if none is specified - if not jid and tab_jid is not None: - jid = tab_jid - - # We might not have found a JID at this stage. No JID provided and not - # in a tab with a JID (InfoTab etc.). - # If we do, make we - if jid: - # XXX: Ugly. We don't know if 'jid' is a str or a JID. And we want - # to return a bareJID. We could change the JID API to allow us to - # clear the resource one way or another. - jid = JID(JID(jid).bare) - else: - jid = None - - return (jid, tab_jid is not None and tab_jid == jid) - - def command_enable(self, jid: Optional[str]) -> None: - """ - Enable JID to use OMEMO with. - - Use current tab JID is none is specified. Refresh the tab if JID - corresponds to the one being added. - """ - - jid, current_tab = self._jid_from_context(jid) - if jid is None: - return None - - if jid not in self._enabled_jids: - self.info('OMEMO enabled for %s' % jid) - self._enabled_jids.add(jid) - - # Refresh tab if JID matches - if current_tab: - self.api.current_tab().refresh() - - return None + encryption_name = 'omemo' + eme_ns = slixmpp_omemo.OMEMO_BASE_NS + replace_body_with_eme = True + stanza_encryption = False - def command_disable(self, jid: Optional[str]) -> None: - """ - Enable JID to use OMEMO with. + encrypted_tags = [ + (slixmpp_omemo.OMEMO_BASE_NS, 'payload'), + ] - Use current tab JID is none is specified. Refresh the tab if JID - corresponds to the one being added. - """ + def init(self) -> None: + super().init() - jid, current_tab = self._jid_from_context(jid) - if jid is None: - return None + self.info = lambda i: self.api.information(i, 'Info') - if jid in self._enabled_jids: - self.info('OMEMO disabled for %s' % jid) - self._enabled_jids.remove(jid) + data_dir = os.path.join(DATA_HOME, 'omemo') + os.makedirs(data_dir, exist_ok=True) - # Refresh tab if JID matches - if current_tab: - self.api.current_tab().refresh() + try: + self.core.xmpp.register_plugin( + 'xep_0384', { + 'data_dir': data_dir, + }, + module=slixmpp_omemo, + ) # OMEMO + except (PluginCouldNotLoad,): + log.exception('And error occured when loading the omemo plugin.') + +# def send_message(self, _args): +# asyncio.ensure_future( +# self._send_encrypted_message( +# "Hello Encrypted World!", +# [JID('pep@bouah.net'), JID('test@bouah.net')], +# mto=JID('test@bouah.net'), +# mtype='chat', +# ), +# ) +# +# async def _send_encrypted_message( +# self, +# payload: str, +# recipients: List[JID], +# mto: Optional[JID] = None, +# mtype: Optional[str] = 'chat', +# ) -> None: +# try: +# encrypted = await self.xmpp['xep_0384'].encrypt_message(payload, recipients) +# except EncryptionPrepareException as e: +# log.debug('Failed to encrypt message: %r', e) +# return None +# msg = self.core.xmpp.make_message(mto, mtype=mtype) +# msg['body'] = 'This message is encrypted with Legacy OMEMO (eu.siacs.conversations.axolotl)' +# msg['eme']['namespace'] = 'eu.siacs.conversations.axolotl' +# msg.append(encrypted) +# log.debug('BAR: message: %r', msg) +# msg.send() +# return None +# +# def on_conversation_say_after( +# self, +# message: Message, +# tabs: Union[DynamicConversationTab, StaticConversationTab, ConversationTab, MucTab], +# ) -> None: +# """ +# Outbound messages +# """ +# +# # Check encryption status with the contact, if enabled, add +# # ['omemo_encrypt'] attribute to message and send. Maybe delete +# # ['body'] and tab.add_message ourselves to specify typ=0 so messages +# # are not logged. +# +# fromjid = message['from'] +# if fromjid not in self._enabled_jids: +# return None +# +# self.xmpp['xep_0384'].encrypt_message(message) +# return None + + def display_error(self, txt) -> None: + self.api.information(txt, 'Error') + + def decrypt(self, message: Message, tab, allow_untrusted=False) -> None: + + body = None + allow_untrusted = False + try: + body = self.core.xmpp['xep_0384'].decrypt_message(message, allow_untrusted) + except (MissingOwnKey,): + # The message is missing our own key, it was not encrypted for + # us, and we can't decrypt it. + self.display_error( + 'I can\'t decrypt this message as it is not encrypted for me.' + ) + except (NoAvailableSession,) as exn: + # We received a message from that contained a session that we + # don't know about (deleted session storage, etc.). We can't + # decrypt the message, and it's going to be lost. + # Here, as we need to initiate a new encrypted session, it is + # best if we send an encrypted message directly. XXX: Is it + # where we talk about self-healing messages? + self.display_error( + 'I can\'t decrypt this message as it uses an encrypted ' + 'session I don\'t know about.', + ) + except (UndecidedException, UntrustedException) as exn: + # We received a message from an untrusted device. We can + # choose to decrypt the message nonetheless, with the + # `allow_untrusted` flag on the `decrypt_message` call, which + # we will do here. This is only possible for decryption, + # encryption will require us to decide if we trust the device + # or not. Clients _should_ indicate that the message was not + # trusted, or in undecided state, if they decide to decrypt it + # anyway. + self.display_error( + "Your device '%s' is not in my trusted devices." % exn.device, + ) + # We resend, setting the `allow_untrusted` parameter to True. + self.decrypt(message, tab, allow_untrusted=True) + except (EncryptionPrepareException,): + # Slixmpp tried its best, but there were errors it couldn't + # resolve. At this point you should have seen other exceptions + # and given a chance to resolve them already. + self.display_error('I was not able to decrypt the message.') + except (Exception,) as exn: + self.display_error('An error occured while attempting decryption.\n%r' % exn) + raise + + if body is not None: + message['body'] = body + + async def encrypt(self, message: Message, _tab) -> None: + mto = message['from'] + mtype = message['type'] + body = message['body'] + + while True: + try: + # `encrypt_message` excepts the plaintext to be sent, a list of + # bare JIDs to encrypt to, and optionally a dict of problems to + # expect per bare JID. + # + # Note that this function returns an `` object, + # and not a full Message stanza. This combined with the + # `recipients` parameter that requires for a list of JIDs, + # allows you to encrypt for 1:1 as well as groupchats (MUC). + # + # TODO: Document expect_problems + # TODO: Handle multiple recipients (MUCs) + recipients = [mto] + encrypt = await self.core.xmpp['xep_0384'].encrypt_message(body, recipients) + message.append(encrypt) + except UndecidedException as exn: + # The library prevents us from sending a message to an + # untrusted/undecided barejid, so we need to make a decision here. + # This is where you prompt your user to ask what to do. In + # this bot we will automatically trust undecided recipients. + self.core.xmpp['xep_0384'].trust(exn.bare_jid, exn.device, exn.ik) + # TODO: catch NoEligibleDevicesException and MissingBundleException + except Exception as exn: + await self.display_error( + 'An error occured while attempting to encrypt.\n%r' % exn, + ) + raise return None - - def send_message(self, _args): - asyncio.ensure_future( - self._send_message( - "Hello Encrypted World!", - [JID('some@jid')], - mto=JID('some@jid'), - mtype='chat', - ), - ) - - async def _send_message( - self, - payload: str, - recipients: List[JID], - mto: Optional[JID] = None, - mtype: Optional[str] = 'chat', - ) -> None: - encrypted = await self.xmpp['xep_0384'].encrypt_message(payload, recipients) - msg = self.core.xmpp.make_message(mto, mtype=mtype) - msg['body'] = 'This message is encrypted with Legacy OMEMO (eu.siacs.conversations.axolotl)' - msg['eme']['namespace'] = 'eu.siacs.conversations.axolotl' - msg.append(encrypted) - log.debug('BAR: message: %r', msg) - msg.send() - - def on_conversation_say_after(self, message, tab): - """ - Outbound messages - """ - - # Check encryption status globally and to the contact, if enabled, add - # ['omemo_encrypt'] attribute to message and send. Maybe delete - # ['body'] and tab.add_message ourselves to specify typ=0 so messages - # are not logged. - - fromjid = message['from'] - self.xmpp['xep_0384'].encrypt_message(message) - - def on_conversation_msg(self, message, _tab): - """ - Inbound messages - """ - - # Check if encrypted, and if so replace message['body'] with - # plaintext. - - self.info('Foo2') - if self.xmpp['xep_0384'].is_encrypted(message): - try: - body = self.xmpp['xep_0384'].decrypt_message(message) - except (MissingOwnKey,): - log.debug("The following message is missing our key;" - "Couldn't decrypt: %r", message) - return None - message['body'] = body.decode("utf8") - return None diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index ab7df662..7adca206 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -219,7 +219,7 @@ class E2EEPlugin(BasePlugin): log.debug('Decrypted %s message: %r', self.encryption_name, message['body']) return None - def _encrypt(self, stanza: StanzaBase) -> Optional[StanzaBase]: + async def _encrypt(self, stanza: StanzaBase) -> Optional[StanzaBase]: if not isinstance(stanza, Message) or stanza['type'] not in ('chat', 'groupchat'): return stanza message = stanza @@ -274,7 +274,7 @@ class E2EEPlugin(BasePlugin): log.debug('Encrypted %s message: %r', self.encryption_name, message) return message - def decrypt(self, _message: Message, tab: ChatTabs): + async def decrypt(self, _message: Message, tab: ChatTabs): """Decryption method This is a method the plugin must implement. It is expected that this @@ -288,7 +288,7 @@ class E2EEPlugin(BasePlugin): raise NotImplementedError - def encrypt(self, _message: Message, tab: ChatTabs): + async def encrypt(self, _message: Message, tab: ChatTabs): """Encryption method This is a method the plugin must implement. It is expected that this -- cgit v1.2.3 From 95a9858717fc4feaac3a7f8635d7613dfc2acd09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Thu, 22 Aug 2019 22:15:42 +0200 Subject: omemo: the container tag for OMEMO is 'encrypted' not 'payload' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 56 +++---------------------------------------------- 1 file changed, 3 insertions(+), 53 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index b396422e..3908d935 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -32,7 +32,7 @@ class Plugin(E2EEPlugin): stanza_encryption = False encrypted_tags = [ - (slixmpp_omemo.OMEMO_BASE_NS, 'payload'), + (slixmpp_omemo.OMEMO_BASE_NS, 'encrypted'), ] def init(self) -> None: @@ -53,56 +53,6 @@ class Plugin(E2EEPlugin): except (PluginCouldNotLoad,): log.exception('And error occured when loading the omemo plugin.') -# def send_message(self, _args): -# asyncio.ensure_future( -# self._send_encrypted_message( -# "Hello Encrypted World!", -# [JID('pep@bouah.net'), JID('test@bouah.net')], -# mto=JID('test@bouah.net'), -# mtype='chat', -# ), -# ) -# -# async def _send_encrypted_message( -# self, -# payload: str, -# recipients: List[JID], -# mto: Optional[JID] = None, -# mtype: Optional[str] = 'chat', -# ) -> None: -# try: -# encrypted = await self.xmpp['xep_0384'].encrypt_message(payload, recipients) -# except EncryptionPrepareException as e: -# log.debug('Failed to encrypt message: %r', e) -# return None -# msg = self.core.xmpp.make_message(mto, mtype=mtype) -# msg['body'] = 'This message is encrypted with Legacy OMEMO (eu.siacs.conversations.axolotl)' -# msg['eme']['namespace'] = 'eu.siacs.conversations.axolotl' -# msg.append(encrypted) -# log.debug('BAR: message: %r', msg) -# msg.send() -# return None -# -# def on_conversation_say_after( -# self, -# message: Message, -# tabs: Union[DynamicConversationTab, StaticConversationTab, ConversationTab, MucTab], -# ) -> None: -# """ -# Outbound messages -# """ -# -# # Check encryption status with the contact, if enabled, add -# # ['omemo_encrypt'] attribute to message and send. Maybe delete -# # ['body'] and tab.add_message ourselves to specify typ=0 so messages -# # are not logged. -# -# fromjid = message['from'] -# if fromjid not in self._enabled_jids: -# return None -# -# self.xmpp['xep_0384'].encrypt_message(message) -# return None def display_error(self, txt) -> None: self.api.information(txt, 'Error') @@ -110,7 +60,6 @@ class Plugin(E2EEPlugin): def decrypt(self, message: Message, tab, allow_untrusted=False) -> None: body = None - allow_untrusted = False try: body = self.core.xmpp['xep_0384'].decrypt_message(message, allow_untrusted) except (MissingOwnKey,): @@ -177,6 +126,7 @@ class Plugin(E2EEPlugin): recipients = [mto] encrypt = await self.core.xmpp['xep_0384'].encrypt_message(body, recipients) message.append(encrypt) + return None except UndecidedException as exn: # The library prevents us from sending a message to an # untrusted/undecided barejid, so we need to make a decision here. @@ -185,7 +135,7 @@ class Plugin(E2EEPlugin): self.core.xmpp['xep_0384'].trust(exn.bare_jid, exn.device, exn.ik) # TODO: catch NoEligibleDevicesException and MissingBundleException except Exception as exn: - await self.display_error( + self.display_error( 'An error occured while attempting to encrypt.\n%r' % exn, ) raise -- cgit v1.2.3 From 1be00f5e20094a2ebfce08ab0352b0580a6d472b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Thu, 22 Aug 2019 22:16:44 +0200 Subject: omemo: put OMEMO data into a folder specific to the account MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 3908d935..b5a513b1 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -40,7 +40,7 @@ class Plugin(E2EEPlugin): self.info = lambda i: self.api.information(i, 'Info') - data_dir = os.path.join(DATA_HOME, 'omemo') + data_dir = os.path.join(DATA_HOME, 'omemo', self.core.xmpp.boundjid.bare) os.makedirs(data_dir, exist_ok=True) try: -- cgit v1.2.3 From fa3c96a602ccac64d3b742c8b9b7376e67525cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Thu, 22 Aug 2019 22:17:34 +0200 Subject: omemo: Ensure session_start is published MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index b5a513b1..652fb018 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -53,6 +53,7 @@ class Plugin(E2EEPlugin): except (PluginCouldNotLoad,): log.exception('And error occured when loading the omemo plugin.') + self.core.xmpp['xep_0384'].session_start(self.core.xmpp.boundjid) def display_error(self, txt) -> None: self.api.information(txt, 'Error') -- cgit v1.2.3 From 7609a56e4820906efee6792bbee12267060ef6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Thu, 22 Aug 2019 22:18:34 +0200 Subject: omemo: handle StaticConversationTab as well 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 | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 7adca206..e626f3ab 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -21,12 +21,7 @@ from typing import ( from slixmpp import InvalidJID, JID, Message from slixmpp.xmlstream import StanzaBase -from poezio.tabs import ( - ConversationTab, - DynamicConversationTab, - PrivateTab, - MucTab, -) +from poezio.tabs import ConversationTab, DynamicConversationTab, StaticConversationTab, PrivateTab, MucTab from poezio.plugin import BasePlugin import logging @@ -36,6 +31,7 @@ log = logging.getLogger(__name__) ChatTabs = Union[ MucTab, DynamicConversationTab, + StaticConversationTab, PrivateTab, ] @@ -133,7 +129,7 @@ class E2EEPlugin(BasePlugin): # sure poezio is not sneaking anything past us. self.core.xmpp.add_filter('out', self._encrypt) - for tab_t in (DynamicConversationTab, PrivateTab, MucTab): + for tab_t in (DynamicConversationTab, StaticConversationTab, PrivateTab, MucTab): self.api.add_tab_command( tab_t, self.encryption_short_name, -- cgit v1.2.3 From 50833050c777b08f08d2fd22a19987814b8634eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Thu, 22 Aug 2019 22:18:58 +0200 Subject: omemo: handle async encryption methods 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 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index e626f3ab..624a36f2 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -24,6 +24,8 @@ from slixmpp.xmlstream import StanzaBase from poezio.tabs import ConversationTab, DynamicConversationTab, StaticConversationTab, PrivateTab, MucTab from poezio.plugin import BasePlugin +from asyncio import iscoroutinefunction + import logging log = logging.getLogger(__name__) @@ -241,7 +243,11 @@ class E2EEPlugin(BasePlugin): return None # Call the enabled encrypt method - self._enabled_tabs[jid](message, tab) + func = self._enabled_tabs[jid] + if iscoroutinefunction(func): + await func(message, tab) + else: + func(message, tab) if has_body: # Only add EME tag if the message has a body. -- cgit v1.2.3 From ec846f222e1ed294cbd57ada33c61f38d782c7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Thu, 22 Aug 2019 23:38:55 +0200 Subject: omemo: decode decrypted body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 652fb018..e9f2e1b8 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -63,6 +63,7 @@ class Plugin(E2EEPlugin): body = None try: body = self.core.xmpp['xep_0384'].decrypt_message(message, allow_untrusted) + body = body.decode('utf-8') except (MissingOwnKey,): # The message is missing our own key, it was not encrypted for # us, and we can't decrypt it. -- cgit v1.2.3 From 83e83055540f9d6cdda1b4e4ee7fdd0057d885cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Thu, 22 Aug 2019 23:57:12 +0200 Subject: omemo: use @to instead of @from for message recipients MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index e9f2e1b8..6ef104ba 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -108,7 +108,7 @@ class Plugin(E2EEPlugin): message['body'] = body async def encrypt(self, message: Message, _tab) -> None: - mto = message['from'] + mto = message['to'] mtype = message['type'] body = message['body'] -- cgit v1.2.3 From 687f360797248d80cc17d1311d03697d8773cd23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Fri, 23 Aug 2019 10:13:05 +0200 Subject: omemo: ensure whitelist includes encryption tag in the correct format 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 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 624a36f2..75ce6fd0 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -70,7 +70,7 @@ class E2EEPlugin(BasePlugin): stanza_encryption = False #: Whitelist applied to messages when `stanza_encryption` is `False`. - tag_whitelist = list(map(lambda x: '{%s}%s' % (x[0], x[1]), [ + tag_whitelist = [ (JCLIENT_NS, 'body'), (EME_NS, EME_TAG), (HINTS_NS, 'store'), @@ -78,7 +78,7 @@ class E2EEPlugin(BasePlugin): (HINTS_NS, 'no-store'), (HINTS_NS, 'no-permanent-store'), # TODO: Add other encryption mechanisms tags here - ])) + ] #: Replaces body with `eme `_ #: if set. Should be suitable for most plugins except those using @@ -269,6 +269,8 @@ class E2EEPlugin(BasePlugin): if self.encrypted_tags is not None: whitelist += self.encrypted_tags + whitelist = {'{%s}%s' % tag for tag in whitelist} + for elem in message.xml[:]: if elem.tag not in whitelist: message.xml.remove(elem) -- cgit v1.2.3 From cbe96a6f77e54fc0c4c40d1e81faab29c9de3946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Fri, 23 Aug 2019 11:27:28 +0200 Subject: omemo: Skip devices on MissingBundleException. Encrypt to the rest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 6ef104ba..c676267a 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -15,6 +15,7 @@ import logging from poezio.plugin_e2ee import E2EEPlugin from poezio.xdg import DATA_HOME +from omemo.exceptions import MissingBundleException from slixmpp.stanza import Message from slixmpp_omemo import PluginCouldNotLoad, MissingOwnKey, NoAvailableSession from slixmpp_omemo import UndecidedException, UntrustedException, EncryptionPrepareException @@ -111,6 +112,7 @@ class Plugin(E2EEPlugin): mto = message['to'] mtype = message['type'] body = message['body'] + expect_problems = {} # type: Optional[Dict[JID, List[int]]] while True: try: @@ -126,7 +128,7 @@ class Plugin(E2EEPlugin): # TODO: Document expect_problems # TODO: Handle multiple recipients (MUCs) recipients = [mto] - encrypt = await self.core.xmpp['xep_0384'].encrypt_message(body, recipients) + encrypt = await self.core.xmpp['xep_0384'].encrypt_message(body, recipients, expect_problems) message.append(encrypt) return None except UndecidedException as exn: @@ -136,6 +138,12 @@ class Plugin(E2EEPlugin): # this bot we will automatically trust undecided recipients. self.core.xmpp['xep_0384'].trust(exn.bare_jid, exn.device, exn.ik) # TODO: catch NoEligibleDevicesException and MissingBundleException + except MissingBundleException as exn: + self.display_error( + 'Could not find keys for device "%d" of recipient "%s". Skipping.' % (exn.device, exn.bare_jid), + ) + device_list = expect_problems.setdefault(exn.bare_jid, []) + device_list.append(exn.device) except Exception as exn: self.display_error( 'An error occured while attempting to encrypt.\n%r' % exn, -- cgit v1.2.3 From 33a1519a3914906566de656daa63a5a778cf3a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Fri, 23 Aug 2019 11:27:58 +0200 Subject: omemo: Catch IqError and IqTimeout and display generic message for now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index c676267a..345925f6 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -17,6 +17,7 @@ from poezio.xdg import DATA_HOME from omemo.exceptions import MissingBundleException from slixmpp.stanza import Message +from slixmpp.exceptions import IqError, IqTimeout from slixmpp_omemo import PluginCouldNotLoad, MissingOwnKey, NoAvailableSession from slixmpp_omemo import UndecidedException, UntrustedException, EncryptionPrepareException import slixmpp_omemo @@ -144,6 +145,11 @@ class Plugin(E2EEPlugin): ) device_list = expect_problems.setdefault(exn.bare_jid, []) device_list.append(exn.device) + except (IqError, IqTimeout) as exn: + self.display_error( + 'An error occured while fetching information on a recipient.\n%r' % exn, + ) + return None except Exception as exn: self.display_error( 'An error occured while attempting to encrypt.\n%r' % exn, -- cgit v1.2.3 From 5a1a2e6c18280cf505bacc051e9b539bdd5fcf0c Mon Sep 17 00:00:00 2001 From: mathieui Date: Fri, 23 Aug 2019 12:31:20 +0200 Subject: Add a "passthrough" parameter for calls through the safetymetaclass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So errors don’t get caught --- poezio/plugin.py | 6 ++++++ poezio/plugin_e2ee.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/poezio/plugin.py b/poezio/plugin.py index c3d10b38..61e0ea87 100644 --- a/poezio/plugin.py +++ b/poezio/plugin.py @@ -75,9 +75,12 @@ class SafetyMetaclass(type): @staticmethod def safe_func(f): def helper(*args, **kwargs): + passthrough = kwargs.pop('passthrough', False) try: return f(*args, **kwargs) except: + if passthrough: + raise if inspect.stack()[1][1] == inspect.getfile(f): raise elif SafetyMetaclass.core: @@ -86,9 +89,12 @@ class SafetyMetaclass(type): 'Error') return None async def async_helper(*args, **kwargs): + passthrough = kwargs.pop('passthrough', False) try: return await f(*args, **kwargs) except: + if passthrough: + raise if inspect.stack()[1][1] == inspect.getfile(f): raise elif SafetyMetaclass.core: diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 75ce6fd0..ab644f7a 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -245,9 +245,9 @@ class E2EEPlugin(BasePlugin): # Call the enabled encrypt method func = self._enabled_tabs[jid] if iscoroutinefunction(func): - await func(message, tab) + await func(message, tab, passthrough=True) else: - func(message, tab) + func(message, tab, passthrough=True) if has_body: # Only add EME tag if the message has a body. -- cgit v1.2.3 From 0c86e9a52f9e251b21696f023ea67a3bd7c55450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Fri, 23 Aug 2019 12:41:07 +0200 Subject: omemo: Edit and move TODO comment for NoEligibleDevicesException MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 345925f6..7fd352fa 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -138,13 +138,13 @@ class Plugin(E2EEPlugin): # This is where you prompt your user to ask what to do. In # this bot we will automatically trust undecided recipients. self.core.xmpp['xep_0384'].trust(exn.bare_jid, exn.device, exn.ik) - # TODO: catch NoEligibleDevicesException and MissingBundleException except MissingBundleException as exn: self.display_error( 'Could not find keys for device "%d" of recipient "%s". Skipping.' % (exn.device, exn.bare_jid), ) device_list = expect_problems.setdefault(exn.bare_jid, []) device_list.append(exn.device) + # TODO: catch NoEligibleDevicesException except (IqError, IqTimeout) as exn: self.display_error( 'An error occured while fetching information on a recipient.\n%r' % exn, -- cgit v1.2.3 From 97d29bc1cdb66f3f3c8f0a66acb124ee62e190b0 Mon Sep 17 00:00:00 2001 From: mathieui Date: Fri, 23 Aug 2019 12:51:19 +0200 Subject: Fix potential race condition when encrypting (do not try to rely on the current tab) --- poezio/plugin_e2ee.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index ab644f7a..b18aae3b 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -21,7 +21,14 @@ from typing import ( from slixmpp import InvalidJID, JID, Message from slixmpp.xmlstream import StanzaBase -from poezio.tabs import ConversationTab, DynamicConversationTab, StaticConversationTab, PrivateTab, MucTab +from poezio.tabs import ( + ChatTab, + ConversationTab, + DynamicConversationTab, + MucTab, + PrivateTab, + StaticConversationTab, +) from poezio.plugin import BasePlugin from asyncio import iscoroutinefunction @@ -222,8 +229,8 @@ class E2EEPlugin(BasePlugin): return stanza message = stanza - tab = self.api.current_tab() - jid = tab.jid + jid = stanza['to'] + tab = self.core.tabs.by_name_and_class(jid, ChatTab) if not self._encryption_enabled(jid): return message -- cgit v1.2.3 From e94ee52c107ed42478f086f40dd2f7f99e4b2f22 Mon Sep 17 00:00:00 2001 From: mathieui Date: Fri, 23 Aug 2019 23:25:33 +0200 Subject: omemo: add missing typing import --- poezio/plugin_e2ee.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index b18aae3b..9c0fa39c 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -10,14 +10,7 @@ Interface for E2EE (End-to-end Encryption) plugins. """ -from typing import ( - Callable, - Dict, - List, - Optional, - Union, - Tuple, -) +from typing import Callable, Dict, List, Optional, Union, Tuple from slixmpp import InvalidJID, JID, Message from slixmpp.xmlstream import StanzaBase -- cgit v1.2.3 From b1e913d772cf748719ef5313e6655f46855f180d Mon Sep 17 00:00:00 2001 From: mathieui Date: Fri, 23 Aug 2019 23:25:59 +0200 Subject: omemo: fix indentation of one line --- 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 9c0fa39c..ebb31078 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -197,7 +197,7 @@ class E2EEPlugin(BasePlugin): has_eme = False if message.xml.find('{%s}%s' % (EME_NS, EME_TAG)) is not None and \ - message['eme']['namespace'] == self.eme_ns: + message['eme']['namespace'] == self.eme_ns: has_eme = True has_encrypted_tag = False -- cgit v1.2.3 From 1dd713b8577658710e767c9b171d073b2b2877b4 Mon Sep 17 00:00:00 2001 From: mathieui Date: Fri, 23 Aug 2019 23:26:19 +0200 Subject: omemo: add a wrapper around _encrypt() to handle user feedback (yes, another one) --- poezio/plugin_e2ee.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index ebb31078..94084d41 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -23,6 +23,7 @@ from poezio.tabs import ( StaticConversationTab, ) from poezio.plugin import BasePlugin +from poezio.theming import get_theme, dump_tuple from asyncio import iscoroutinefunction @@ -43,6 +44,12 @@ EME_TAG = 'encryption' JCLIENT_NS = 'jabber:client' HINTS_NS = 'urn:xmpp:hints' +class NothingToEncrypt(Exception): + """ + Exception to raise inside the _encrypt filter on stanzas that do not need + to be processed. + """ + class E2EEPlugin(BasePlugin): """Interface for E2EE plugins. @@ -129,7 +136,7 @@ class E2EEPlugin(BasePlugin): # encrypted is encrypted, and no plain element slips in. # Using a stream filter might be a bit too much, but at least we're # sure poezio is not sneaking anything past us. - self.core.xmpp.add_filter('out', self._encrypt) + self.core.xmpp.add_filter('out', self._encrypt_wrapper) for tab_t in (DynamicConversationTab, StaticConversationTab, PrivateTab, MucTab): self.api.add_tab_command( @@ -193,6 +200,27 @@ class E2EEPlugin(BasePlugin): def _encryption_enabled(self, jid: JID) -> bool: return jid in self._enabled_tabs and self._enabled_tabs[jid] == self.encrypt + async def _encrypt_wrapper(self, stanza: StanzaBase) -> Optional[StanzaBase]: + """ + Wrapper around _encrypt() to handle errors and display the message after encryption. + """ + try: + result = await self._encrypt(stanza, passthrough=True) + except NothingToEncrypt: + return stanza + except Exception as exc: + jid = stanza['to'] + tab = self.core.tabs.by_name_and_class(jid, ChatTab) + msg = ' \n\x19%s}Could not send message: %s' % ( + dump_tuple(get_theme().COLOR_CHAR_NACK), + exc, + ) + 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 + return result + def _decrypt(self, message: Message, tab: ChatTabs) -> None: has_eme = False @@ -219,13 +247,13 @@ class E2EEPlugin(BasePlugin): async def _encrypt(self, stanza: StanzaBase) -> Optional[StanzaBase]: if not isinstance(stanza, Message) or stanza['type'] not in ('chat', 'groupchat'): - return stanza + raise NothingToEncrypt() message = stanza jid = stanza['to'] tab = self.core.tabs.by_name_and_class(jid, ChatTab) if not self._encryption_enabled(jid): - return message + raise NothingToEncrypt() log.debug('Sending %s message: %r', self.encryption_name, message) -- cgit v1.2.3 From 4586765793deaf299bbbeb6da34f692f54e2d48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 24 Aug 2019 00:17:03 +0200 Subject: omemo: session_start is now async MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 7fd352fa..fff10cbf 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -10,6 +10,7 @@ """ import os +import asyncio import logging from poezio.plugin_e2ee import E2EEPlugin @@ -55,7 +56,9 @@ class Plugin(E2EEPlugin): except (PluginCouldNotLoad,): log.exception('And error occured when loading the omemo plugin.') - self.core.xmpp['xep_0384'].session_start(self.core.xmpp.boundjid) + asyncio.ensure_future( + self.core.xmpp['xep_0384'].session_start(self.core.xmpp.boundjid) + ) def display_error(self, txt) -> None: self.api.information(txt, 'Error') -- cgit v1.2.3 From 25dae11eb303c47915b1b2805d7cad2b37f92409 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Aug 2019 00:42:36 +0200 Subject: omemo: add supported_tab_types and trust_states plugin attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - supported tab types is a list of tabs this plugin should be active in (only chattabs) - trust_states is a dict[str → set] containing only two keys: accepted and rejected, whose values are the internal plugin states that should allow encryption and the ones that should not --- poezio/plugin_e2ee.py | 77 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 94084d41..8206dc4a 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -10,7 +10,7 @@ Interface for E2EE (End-to-end Encryption) plugins. """ -from typing import Callable, Dict, List, Optional, Union, Tuple +from typing import Callable, Dict, List, Optional, Union, Tuple, Set from slixmpp import InvalidJID, JID, Message from slixmpp.xmlstream import StanzaBase @@ -24,6 +24,8 @@ from poezio.tabs import ( ) from poezio.plugin import BasePlugin from poezio.theming import get_theme, dump_tuple +from poezio.config import config +from poezio.decorators import command_args_parser from asyncio import iscoroutinefunction @@ -114,7 +116,16 @@ class E2EEPlugin(BasePlugin): # time _enabled_tabs = {} # type: Dict[JID, Callable] + # Tabs that support this encryption mechanism + supported_tab_types = [] # type: List[ChatTabs] + + # States for each remote entity + trust_states = {'accepted': set(), 'rejected': set()} # type: Dict[str, Set[str]] + def init(self): + self._all_trust_states = self.trust_states['accepted'].union( + self.trust_states['rejected'] + ) if self.encryption_name is None and self.encryption_short_name is None: raise NotImplementedError @@ -138,7 +149,7 @@ class E2EEPlugin(BasePlugin): # sure poezio is not sneaking anything past us. self.core.xmpp.add_filter('out', self._encrypt_wrapper) - for tab_t in (DynamicConversationTab, StaticConversationTab, PrivateTab, MucTab): + for tab_t in self.supported_tab_types: self.api.add_tab_command( tab_t, self.encryption_short_name, @@ -148,6 +159,25 @@ class E2EEPlugin(BasePlugin): help='Toggle automatic {} encryption for tab.'.format(self.encryption_name), ) + trust_msg = 'Set {name} state to {state} for this fingerprint on this JID.' + for state in self._all_trust_states: + for tab_t in self.supported_tab_types: + self.api.add_tab_command( + tab_t, + self.encryption_short_name + '_' + state, + lambda args: self.__command_set_state_local(args, state), + usage='', + short=trust_msg.format(name=self.encryption_short_name, state=state), + help=trust_msg.format(name=self.encryption_short_name, state=state), + ) + self.api.add_command( + self.encryption_short_name + '_' + state, + lambda args: self.__command_set_state_global(args, state), + usage=' ', + short=trust_msg.format(name=self.encryption_short_name, state=state), + help=trust_msg.format(name=self.encryption_short_name, state=state), + ) + ConversationTab.add_information_element( self.encryption_short_name, self._display_encryption_status, @@ -161,6 +191,15 @@ class E2EEPlugin(BasePlugin): self._display_encryption_status, ) + self.__load_encrypted_states() + + def __load_encrypted_states(self) -> None: + """Load previously stored encryption states for jids.""" + for section in config.sections(): + value = config.get('encryption', section=section) + if value and value == self.encryption_short_name: + self._enabled_tabs[section] = self.encrypt + def cleanup(self): ConversationTab.remove_information_element(self.encryption_short_name) MucTab.remove_information_element(self.encryption_short_name) @@ -184,19 +223,40 @@ class E2EEPlugin(BasePlugin): def _toggle_tab(self, _input: str) -> None: jid = self.api.current_tab().jid # type: JID - if self._encryption_enabled(jid): + if self._enabled_tabs.get(jid) == self.encrypt: del self._enabled_tabs[jid] + config.remove_and_save('encryption', section=jid) self.api.information( '{} encryption disabled for {}'.format(self.encryption_name, jid), 'Info', ) else: self._enabled_tabs[jid] = self.encrypt + config.set_and_save('encryption', self.encryption_short_name, section=jid) self.api.information( '{} encryption enabled for {}'.format(self.encryption_name, jid), 'Info', ) + @command_args_parser.quoted(2) + def __command_set_state_global(self, args, state='') -> None: + jid, fpr = args + if state not in self._all_trust_states: + self.api.information('Unknown state for plugin %s: %s' % (self.encryption_short_name, state), 'Error') + return + self.store_trust(jid, state, fpr) + + @command_args_parser.quoted(1) + def __command_set_state_local(self, args, state='') -> None: + if isinstance(self.api.current_tab(), MucTab): + return + jid = self.api.get_current_tab().name + fpr = args[0] + if state not in self._all_trust_states: + self.api.information('Unknown state for plugin %s: %s' % (self.encryption_short_name, state), 'Error') + return + self.store_trust(jid, state, fpr) + def _encryption_enabled(self, jid: JID) -> bool: return jid in self._enabled_tabs and self._enabled_tabs[jid] == self.encrypt @@ -306,6 +366,17 @@ class E2EEPlugin(BasePlugin): log.debug('Encrypted %s message: %r', self.encryption_name, message) return message + def store_trust(self, jid: JID, state: str, fingerprint: str) -> None: + """Store trust for a fingerprint and a jid.""" + option_name = '%s:%s' % (self.encryption_short_name, fingerprint) + config.silent_set(option=option_name, value=state, section=jid) + + def fetch_trust(self, jid: JID, fingerprint: str) -> str: + """Fetch trust of a fingerprint and a jid. + """ + 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): """Decryption method -- cgit v1.2.3 From c85c8f6c7729b94d06a15614240e848592c60ef0 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Aug 2019 00:44:42 +0200 Subject: omemo: add supported_tab_types and trust_states into the omemo plugin --- plugins/omemo_plugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index fff10cbf..6a2f0640 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -15,6 +15,7 @@ import logging from poezio.plugin_e2ee import E2EEPlugin from poezio.xdg import DATA_HOME +from poezio.tabs import DynamicConversationTab, StaticConversationTab, MucTab from omemo.exceptions import MissingBundleException from slixmpp.stanza import Message @@ -37,6 +38,8 @@ class Plugin(E2EEPlugin): encrypted_tags = [ (slixmpp_omemo.OMEMO_BASE_NS, 'encrypted'), ] + trust_state = {'accepted': {'verified', 'unverified'}, 'rejected': {'untrusted'}} + supported_tab_types = [DynamicConversationTab, StaticConversationTab, MucTab] def init(self) -> None: super().init() -- cgit v1.2.3 From cd3f2a197b07016165899506dfc8b00a859ce24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 25 Aug 2019 00:46:10 +0200 Subject: omemo: Remove unused variable in encrypt method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 6a2f0640..b8ec293e 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -117,7 +117,6 @@ class Plugin(E2EEPlugin): async def encrypt(self, message: Message, _tab) -> None: mto = message['to'] - mtype = message['type'] body = message['body'] expect_problems = {} # type: Optional[Dict[JID, List[int]]] -- cgit v1.2.3 From 0e27485c36de12bb4831e2a48c63a3a4e56d3da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 25 Aug 2019 00:47:50 +0200 Subject: Remove exception catchall in omemo plugin as it's now handled in plugin_e2ee MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index b8ec293e..6efa8c08 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -155,10 +155,5 @@ class Plugin(E2EEPlugin): 'An error occured while fetching information on a recipient.\n%r' % exn, ) return None - except Exception as exn: - self.display_error( - 'An error occured while attempting to encrypt.\n%r' % exn, - ) - raise return None -- cgit v1.2.3 From 03499a2d2c0a657a886913d3988d69ca08ceca3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 25 Aug 2019 00:48:57 +0200 Subject: omemo: handle MissingBundleException when it comes from EncryptionPrepareException MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We're not supposed to see MissingBundleException directly as it's handled by slixmpp-omemo. Slixmpp-omemo will give us all the remaining exceptions via EncryptionPrepareException when it doesn't know what to do anymore. Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 6efa8c08..538be98e 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -18,6 +18,7 @@ from poezio.xdg import DATA_HOME from poezio.tabs import DynamicConversationTab, StaticConversationTab, MucTab from omemo.exceptions import MissingBundleException +from slixmpp import JID from slixmpp.stanza import Message from slixmpp.exceptions import IqError, IqTimeout from slixmpp_omemo import PluginCouldNotLoad, MissingOwnKey, NoAvailableSession @@ -143,13 +144,18 @@ class Plugin(E2EEPlugin): # This is where you prompt your user to ask what to do. In # this bot we will automatically trust undecided recipients. self.core.xmpp['xep_0384'].trust(exn.bare_jid, exn.device, exn.ik) - except MissingBundleException as exn: - self.display_error( - 'Could not find keys for device "%d" of recipient "%s". Skipping.' % (exn.device, exn.bare_jid), - ) - device_list = expect_problems.setdefault(exn.bare_jid, []) - device_list.append(exn.device) # TODO: catch NoEligibleDevicesException + except EncryptionPrepareException as exn: + log.debug('FOO: EncryptionPrepareException: %r', exn.errors) + for error in exn.errors: + if isinstance(error, MissingBundleException): + self.display_error( + 'Could not find keys for device "%d" of recipient "%s". Skipping.' % + (error.device, error.bare_jid), + ) + jid = JID(error.bare_jid) + device_list = expect_problems.setdefault(jid, []) + device_list.append(error.device) except (IqError, IqTimeout) as exn: self.display_error( 'An error occured while fetching information on a recipient.\n%r' % exn, -- cgit v1.2.3 From 07447a465aab6836f8d1aded58e47251c3c7e6e2 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Aug 2019 01:27:55 +0200 Subject: omemo: fix the type of the supported tab tyoes --- plugins/omemo_plugin.py | 4 ++-- poezio/plugin_e2ee.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 538be98e..8af2402d 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -39,8 +39,8 @@ class Plugin(E2EEPlugin): encrypted_tags = [ (slixmpp_omemo.OMEMO_BASE_NS, 'encrypted'), ] - trust_state = {'accepted': {'verified', 'unverified'}, 'rejected': {'untrusted'}} - supported_tab_types = [DynamicConversationTab, StaticConversationTab, MucTab] + trust_states = {'accepted': {'verified', 'unverified'}, 'rejected': {'untrusted'}} + supported_tab_types = (DynamicConversationTab, StaticConversationTab, MucTab) def init(self) -> None: super().init() diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 8206dc4a..2e982baf 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -117,7 +117,7 @@ class E2EEPlugin(BasePlugin): _enabled_tabs = {} # type: Dict[JID, Callable] # Tabs that support this encryption mechanism - supported_tab_types = [] # type: List[ChatTabs] + supported_tab_types = tuple() # type: Tuple[ChatTabs] # States for each remote entity trust_states = {'accepted': set(), 'rejected': set()} # type: Dict[str, Set[str]] -- cgit v1.2.3 From b40de0bcbe1c380c24ad82d4e9d6f771baea9c8c Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Aug 2019 01:33:53 +0200 Subject: omemo: fix some errors / feedback in trust setting --- poezio/plugin_e2ee.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 2e982baf..f4d75def 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -242,18 +242,36 @@ class E2EEPlugin(BasePlugin): def __command_set_state_global(self, args, state='') -> None: jid, fpr = args if state not in self._all_trust_states: - self.api.information('Unknown state for plugin %s: %s' % (self.encryption_short_name, state), 'Error') + self.api.information( + 'Unknown state for plugin %s: %s' % ( + self.encryption_short_name, state), + 'Error' + ) return self.store_trust(jid, state, fpr) @command_args_parser.quoted(1) def __command_set_state_local(self, args, state='') -> None: if isinstance(self.api.current_tab(), MucTab): + self.api.information( + 'You can only trust each participant of a MUC individually.', + 'Info', + ) + return + jid = self.api.current_tab().name + if not args: + self.api.information( + 'No fingerprint provided to the command..', + 'Error', + ) return - jid = self.api.get_current_tab().name fpr = args[0] if state not in self._all_trust_states: - self.api.information('Unknown state for plugin %s: %s' % (self.encryption_short_name, state), 'Error') + self.api.information( + 'Unknown state for plugin %s: %s' % ( + self.encryption_short_name, state), + 'Error', + ) return self.store_trust(jid, state, fpr) -- cgit v1.2.3 From 5c4571751ec790f9788e558d9c40017ef872b862 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Aug 2019 01:37:25 +0200 Subject: omemo: fix an indent error and make mypy happy --- poezio/plugin_e2ee.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index f4d75def..910e3d01 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -290,8 +290,8 @@ class E2EEPlugin(BasePlugin): jid = stanza['to'] tab = self.core.tabs.by_name_and_class(jid, ChatTab) msg = ' \n\x19%s}Could not send message: %s' % ( - dump_tuple(get_theme().COLOR_CHAR_NACK), - exc, + dump_tuple(get_theme().COLOR_CHAR_NACK), + exc, ) tab.nack_message(msg, stanza['id'], stanza['from']) # TODO: display exceptions to the user properly @@ -375,10 +375,10 @@ class E2EEPlugin(BasePlugin): if self.encrypted_tags is not None: whitelist += self.encrypted_tags - whitelist = {'{%s}%s' % tag for tag in whitelist} + tag_whitelist = {'{%s}%s' % tag for tag in whitelist} for elem in message.xml[:]: - if elem.tag not in whitelist: + if elem.tag not in tag_whitelist: message.xml.remove(elem) log.debug('Encrypted %s message: %r', self.encryption_name, message) -- cgit v1.2.3 From 518ba47e2aaea628e38132a826101a8c17801a79 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Aug 2019 01:39:59 +0200 Subject: omemo: add an api in plugin_e2ee to get the fingerprint(s) of a jid --- poezio/plugin_e2ee.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 910e3d01..fde7f10c 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -178,6 +178,14 @@ class E2EEPlugin(BasePlugin): help=trust_msg.format(name=self.encryption_short_name, state=state), ) + self.api.add_command( + self.encryption_short_name + '_fingerprint', + self._command_show_fingerprints, + usage='[jid]', + short='Show %s fingerprint(s) for a JID.' % self.encryption_short_name, + help='Show %s fingerprint(s) for a JID.' % self.encryption_short_name, + ) + ConversationTab.add_information_element( self.encryption_short_name, self._display_encryption_status, @@ -238,6 +246,33 @@ class E2EEPlugin(BasePlugin): 'Info', ) + def _show_fingerprints(self, jid: JID) -> None: + """Display encryption fingerprints for a JID.""" + fprs = self.get_fingerprints(jid) + if len(fprs) == 1: + self.api.information( + 'Fingerprint for %s: %s' % (jid, fprs[0]), + 'Info', + ) + elif fprs: + self.api.information( + 'Fingerprints for %s:\n\t%s' % (jid, '\n\t'.join(fprs)), + 'Info', + ) + else: + self.api.information( + 'No fingerprints to display', + 'Info', + ) + + @command_args_parser.quoted(0, 1) + def _command_show_fingerprints(self, args: List[str]) -> None: + if not args and isinstance(self.api.current_tab(), self.supported_tab_types): + jid = self.api.current_tab().name + else: + jid = args[0] + self._show_fingerprints(jid) + @command_args_parser.quoted(2) def __command_set_state_global(self, args, state='') -> None: jid, fpr = args @@ -422,3 +457,12 @@ class E2EEPlugin(BasePlugin): """ raise NotImplementedError + + def get_fingerprints(self, jid: JID) -> List[str]: + """Show fingerprint(s) for this encryption method and JID. + + To overload in plugins. + + :returns: A list of fingerprints to display + """ + return [] -- cgit v1.2.3 From 04efd8ee07b4ea34f080570b1878d52ac9e2cc4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 25 Aug 2019 02:11:12 +0200 Subject: omemo: implement get_fingerprints method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 8af2402d..55adda14 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -12,6 +12,7 @@ import os import asyncio import logging +from typing import List from poezio.plugin_e2ee import E2EEPlugin from poezio.xdg import DATA_HOME @@ -67,6 +68,20 @@ class Plugin(E2EEPlugin): def display_error(self, txt) -> None: self.api.information(txt, 'Error') + def get_fingerprints(self, jid: JID) -> List[str]: + devices = self.core.xmpp['xep_0384'].get_trust_for_jid(jid) + + # XXX: What to do with did -> None entries? + # XXX: What to do with the active/inactive devices differenciation? + # For now I'll merge both. We should probably display them separately + # later on. + devices['active'].update(devices['inactive']) + return [ + trust['fingerprint'] + for trust in devices['active'].values() + if trust is not None + ] + def decrypt(self, message: Message, tab, allow_untrusted=False) -> None: body = None -- cgit v1.2.3 From e8dd6f0a91bfe59836bef58588e90b5c6fb1a1ed Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Aug 2019 02:14:35 +0200 Subject: omemo: fix show_fingerprints (JID instead of str) --- 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 fde7f10c..71f37f45 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -271,7 +271,7 @@ class E2EEPlugin(BasePlugin): jid = self.api.current_tab().name else: jid = args[0] - self._show_fingerprints(jid) + self._show_fingerprints(JID(jid)) @command_args_parser.quoted(2) def __command_set_state_global(self, args, state='') -> None: -- cgit v1.2.3 From ecdd036237b4a78a08cb0a72a651b2077f6c721e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Tue, 27 Aug 2019 23:41:13 +0200 Subject: omemo: Update get_fingerprints with slixmpp-omemo changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit slixmpp-omemo's get_trust_for_jid doesn't provide fingerprints directly anymore, it simply wraps the omemo lib method. Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 55adda14..c59e0219 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -77,7 +77,7 @@ class Plugin(E2EEPlugin): # later on. devices['active'].update(devices['inactive']) return [ - trust['fingerprint'] + slixmpp_omemo.fp_from_ik(trust['key']) for trust in devices['active'].values() if trust is not None ] -- cgit v1.2.3 From 931fc581992fe0efeefece259673852fc7d0dfb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Tue, 27 Aug 2019 23:45:21 +0200 Subject: omemo: Update decrypt_message with slixmpp-omemo changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit decrypt_message now takes an Encrypted container and a sender (JID) instead of a Message object. Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index c59e0219..496e0a12 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -86,7 +86,9 @@ class Plugin(E2EEPlugin): body = None try: - body = self.core.xmpp['xep_0384'].decrypt_message(message, allow_untrusted) + mfrom = message['from'] + encrypted = message['omemo_encrypted'] + body = self.core.xmpp['xep_0384'].decrypt_message(encrypted, mfrom, allow_untrusted) body = body.decode('utf-8') except (MissingOwnKey,): # The message is missing our own key, it was not encrypted for -- cgit v1.2.3 From 3697e308c29a9470675a7fdba743f2751797ae57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 13 Oct 2019 13:31:15 +0200 Subject: omemo: use jid property instead of name on chat tab 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 71f37f45..90222313 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -268,7 +268,7 @@ class E2EEPlugin(BasePlugin): @command_args_parser.quoted(0, 1) def _command_show_fingerprints(self, args: List[str]) -> None: if not args and isinstance(self.api.current_tab(), self.supported_tab_types): - jid = self.api.current_tab().name + jid = self.api.current_tab().jid else: jid = args[0] self._show_fingerprints(JID(jid)) @@ -293,7 +293,7 @@ class E2EEPlugin(BasePlugin): 'Info', ) return - jid = self.api.current_tab().name + jid = self.api.current_tab().jid if not args: self.api.information( 'No fingerprint provided to the command..', -- cgit v1.2.3 From c610a76fd9eed3ff86762c44275cb089f85f950e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 13 Oct 2019 13:31:43 +0200 Subject: omemo: use dedicated method to check if encryption is enabled 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 90222313..da6aec9a 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -231,7 +231,7 @@ class E2EEPlugin(BasePlugin): def _toggle_tab(self, _input: str) -> None: jid = self.api.current_tab().jid # type: JID - if self._enabled_tabs.get(jid) == self.encrypt: + if self._encryption_enabled(jid): del self._enabled_tabs[jid] config.remove_and_save('encryption', section=jid) self.api.information( @@ -311,7 +311,7 @@ class E2EEPlugin(BasePlugin): self.store_trust(jid, state, fpr) def _encryption_enabled(self, jid: JID) -> bool: - return jid in self._enabled_tabs and self._enabled_tabs[jid] == self.encrypt + return self._enabled_tabs.get(jid) == self.encrypt async def _encrypt_wrapper(self, stanza: StanzaBase) -> Optional[StanzaBase]: """ -- cgit v1.2.3 From fb637a597b5ba396a6e82d0060c9fdde4d9e971c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 13 Oct 2019 13:32:11 +0200 Subject: omemo: prevent traceback when no JID is specified in non-supported tab 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 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index da6aec9a..9c8fd224 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -269,8 +269,15 @@ class E2EEPlugin(BasePlugin): def _command_show_fingerprints(self, args: List[str]) -> None: if not args and isinstance(self.api.current_tab(), self.supported_tab_types): jid = self.api.current_tab().jid - else: + elif args: jid = args[0] + else: + self.api.information( + '%s_fingerprint: Couldn\'t deduce JID from context' % ( + self.encryption_short_name), + 'Error', + ) + return None self._show_fingerprints(JID(jid)) @command_args_parser.quoted(2) -- cgit v1.2.3 From 96acbaa13d2cbc008bf29847ded0f24ab8cbfb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 13 Oct 2019 13:32:39 +0200 Subject: omemo: remove unnecessary newline in docstring 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 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 9c8fd224..410fc82e 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -432,8 +432,7 @@ class E2EEPlugin(BasePlugin): config.silent_set(option=option_name, value=state, section=jid) def fetch_trust(self, jid: JID, fingerprint: str) -> str: - """Fetch trust of a fingerprint and a jid. - """ + """Fetch trust of a fingerprint and a jid.""" option_name = '%s:%s' % (self.encryption_short_name, fingerprint) return config.get(option=option_name, section=jid) -- cgit v1.2.3 From 39e756cb35c6d28403bac94e06cef37bf81db043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sun, 13 Oct 2019 13:36:04 +0200 Subject: omemo: Add TODO for unverified state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py index 496e0a12..897a1ad6 100644 --- a/plugins/omemo_plugin.py +++ b/plugins/omemo_plugin.py @@ -40,6 +40,8 @@ class Plugin(E2EEPlugin): encrypted_tags = [ (slixmpp_omemo.OMEMO_BASE_NS, 'encrypted'), ] + # TODO: Make "unverified" state depend on a config option that includes it + # either in accepted or rejected states. trust_states = {'accepted': {'verified', 'unverified'}, 'rejected': {'untrusted'}} supported_tab_types = (DynamicConversationTab, StaticConversationTab, MucTab) -- cgit v1.2.3 From 9e390085a163c3c053aa5b572537172344619226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Fri, 27 Dec 2019 19:02:09 +0100 Subject: omemo: omemo_plugin moved to its own repository for licensing purposes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- plugins/omemo_plugin.py | 184 ------------------------------------------------ 1 file changed, 184 deletions(-) delete mode 100644 plugins/omemo_plugin.py diff --git a/plugins/omemo_plugin.py b/plugins/omemo_plugin.py deleted file mode 100644 index 897a1ad6..00000000 --- a/plugins/omemo_plugin.py +++ /dev/null @@ -1,184 +0,0 @@ -#! /usr/bin/env python3 -# -*- coding: utf-8 -*- -# vim:fenc=utf-8 -# -# Copyright © 2018 Maxime “pep” Buquet -# -# Distributed under terms of the zlib license. -""" - OMEMO Plugin. -""" - -import os -import asyncio -import logging -from typing import List - -from poezio.plugin_e2ee import E2EEPlugin -from poezio.xdg import DATA_HOME -from poezio.tabs import DynamicConversationTab, StaticConversationTab, MucTab - -from omemo.exceptions import MissingBundleException -from slixmpp import JID -from slixmpp.stanza import Message -from slixmpp.exceptions import IqError, IqTimeout -from slixmpp_omemo import PluginCouldNotLoad, MissingOwnKey, NoAvailableSession -from slixmpp_omemo import UndecidedException, UntrustedException, EncryptionPrepareException -import slixmpp_omemo - -log = logging.getLogger(__name__) - - -class Plugin(E2EEPlugin): - """OMEMO (XEP-0384) Plugin""" - - encryption_name = 'omemo' - eme_ns = slixmpp_omemo.OMEMO_BASE_NS - replace_body_with_eme = True - stanza_encryption = False - - encrypted_tags = [ - (slixmpp_omemo.OMEMO_BASE_NS, 'encrypted'), - ] - # TODO: Make "unverified" state depend on a config option that includes it - # either in accepted or rejected states. - trust_states = {'accepted': {'verified', 'unverified'}, 'rejected': {'untrusted'}} - supported_tab_types = (DynamicConversationTab, StaticConversationTab, MucTab) - - def init(self) -> None: - super().init() - - self.info = lambda i: self.api.information(i, 'Info') - - data_dir = os.path.join(DATA_HOME, 'omemo', self.core.xmpp.boundjid.bare) - os.makedirs(data_dir, exist_ok=True) - - try: - self.core.xmpp.register_plugin( - 'xep_0384', { - 'data_dir': data_dir, - }, - module=slixmpp_omemo, - ) # OMEMO - except (PluginCouldNotLoad,): - log.exception('And error occured when loading the omemo plugin.') - - asyncio.ensure_future( - self.core.xmpp['xep_0384'].session_start(self.core.xmpp.boundjid) - ) - - def display_error(self, txt) -> None: - self.api.information(txt, 'Error') - - def get_fingerprints(self, jid: JID) -> List[str]: - devices = self.core.xmpp['xep_0384'].get_trust_for_jid(jid) - - # XXX: What to do with did -> None entries? - # XXX: What to do with the active/inactive devices differenciation? - # For now I'll merge both. We should probably display them separately - # later on. - devices['active'].update(devices['inactive']) - return [ - slixmpp_omemo.fp_from_ik(trust['key']) - for trust in devices['active'].values() - if trust is not None - ] - - def decrypt(self, message: Message, tab, allow_untrusted=False) -> None: - - body = None - try: - mfrom = message['from'] - encrypted = message['omemo_encrypted'] - body = self.core.xmpp['xep_0384'].decrypt_message(encrypted, mfrom, allow_untrusted) - body = body.decode('utf-8') - except (MissingOwnKey,): - # The message is missing our own key, it was not encrypted for - # us, and we can't decrypt it. - self.display_error( - 'I can\'t decrypt this message as it is not encrypted for me.' - ) - except (NoAvailableSession,) as exn: - # We received a message from that contained a session that we - # don't know about (deleted session storage, etc.). We can't - # decrypt the message, and it's going to be lost. - # Here, as we need to initiate a new encrypted session, it is - # best if we send an encrypted message directly. XXX: Is it - # where we talk about self-healing messages? - self.display_error( - 'I can\'t decrypt this message as it uses an encrypted ' - 'session I don\'t know about.', - ) - except (UndecidedException, UntrustedException) as exn: - # We received a message from an untrusted device. We can - # choose to decrypt the message nonetheless, with the - # `allow_untrusted` flag on the `decrypt_message` call, which - # we will do here. This is only possible for decryption, - # encryption will require us to decide if we trust the device - # or not. Clients _should_ indicate that the message was not - # trusted, or in undecided state, if they decide to decrypt it - # anyway. - self.display_error( - "Your device '%s' is not in my trusted devices." % exn.device, - ) - # We resend, setting the `allow_untrusted` parameter to True. - self.decrypt(message, tab, allow_untrusted=True) - except (EncryptionPrepareException,): - # Slixmpp tried its best, but there were errors it couldn't - # resolve. At this point you should have seen other exceptions - # and given a chance to resolve them already. - self.display_error('I was not able to decrypt the message.') - except (Exception,) as exn: - self.display_error('An error occured while attempting decryption.\n%r' % exn) - raise - - if body is not None: - message['body'] = body - - async def encrypt(self, message: Message, _tab) -> None: - mto = message['to'] - body = message['body'] - expect_problems = {} # type: Optional[Dict[JID, List[int]]] - - while True: - try: - # `encrypt_message` excepts the plaintext to be sent, a list of - # bare JIDs to encrypt to, and optionally a dict of problems to - # expect per bare JID. - # - # Note that this function returns an `` object, - # and not a full Message stanza. This combined with the - # `recipients` parameter that requires for a list of JIDs, - # allows you to encrypt for 1:1 as well as groupchats (MUC). - # - # TODO: Document expect_problems - # TODO: Handle multiple recipients (MUCs) - recipients = [mto] - encrypt = await self.core.xmpp['xep_0384'].encrypt_message(body, recipients, expect_problems) - message.append(encrypt) - return None - except UndecidedException as exn: - # The library prevents us from sending a message to an - # untrusted/undecided barejid, so we need to make a decision here. - # This is where you prompt your user to ask what to do. In - # this bot we will automatically trust undecided recipients. - self.core.xmpp['xep_0384'].trust(exn.bare_jid, exn.device, exn.ik) - # TODO: catch NoEligibleDevicesException - except EncryptionPrepareException as exn: - log.debug('FOO: EncryptionPrepareException: %r', exn.errors) - for error in exn.errors: - if isinstance(error, MissingBundleException): - self.display_error( - 'Could not find keys for device "%d" of recipient "%s". Skipping.' % - (error.device, error.bare_jid), - ) - jid = JID(error.bare_jid) - device_list = expect_problems.setdefault(jid, []) - device_list.append(error.device) - except (IqError, IqTimeout) as exn: - self.display_error( - 'An error occured while fetching information on a recipient.\n%r' % exn, - ) - return None - - return None -- cgit v1.2.3 From abbb6a714ef425351c884ae4cc48b7353324e971 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sat, 28 Dec 2019 16:47:18 +0100 Subject: Disable a pylint error on a line (due to __getattr__ override) --- poezio/plugin_e2ee.py | 1 + 1 file changed, 1 insertion(+) diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py index 410fc82e..d4b26d46 100644 --- a/poezio/plugin_e2ee.py +++ b/poezio/plugin_e2ee.py @@ -325,6 +325,7 @@ class E2EEPlugin(BasePlugin): Wrapper around _encrypt() to handle errors and display the message after encryption. """ try: + # pylint: disable=unexpected-keyword-arg result = await self._encrypt(stanza, passthrough=True) except NothingToEncrypt: return stanza -- cgit v1.2.3