diff options
author | Philipp Hörist <forenjunkie@chello.at> | 2017-05-08 00:13:03 +0300 |
---|---|---|
committer | Philipp Hörist <forenjunkie@chello.at> | 2017-05-08 00:13:03 +0300 |
commit | 3242de357468784087d9094691f78bf3c292a144 (patch) | |
tree | 6d70cc2237bf7236e59fa48b15bd75c0b44aa848 | |
parent | 8b85e8a0ae33d967b0e0bee0a28ee5359e31376f (diff) | |
parent | a319b1f96d14227dd53faa132ec27ae64c4c815f (diff) |
Merge branch 'newplugins' into 'gtk3'
PGP Plugin version 1.0.0
See merge request !37
-rw-r--r-- | pgp/__init__.py | 1 | ||||
-rw-r--r-- | pgp/manifest.ini | 8 | ||||
-rw-r--r-- | pgp/pgpplugin.py | 291 |
3 files changed, 300 insertions, 0 deletions
diff --git a/pgp/__init__.py b/pgp/__init__.py new file mode 100644 index 0000000..bd90642 --- /dev/null +++ b/pgp/__init__.py @@ -0,0 +1 @@ +from .pgpplugin import OldPGPPlugin diff --git a/pgp/manifest.ini b/pgp/manifest.ini new file mode 100644 index 0000000..70f6377 --- /dev/null +++ b/pgp/manifest.ini @@ -0,0 +1,8 @@ +[info] +name: PGP +short_name: PGP +version: 1.0.0 +description: PGP encryption as per XEP-0027 +authors: Philipp Hörist <philipp@hoerist.com> +homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/pgp +min_gajim_version: 0.16.10 diff --git a/pgp/pgpplugin.py b/pgp/pgpplugin.py new file mode 100644 index 0000000..4f03601 --- /dev/null +++ b/pgp/pgpplugin.py @@ -0,0 +1,291 @@ +# -*- coding: utf-8 -*- + +''' +Copyright 2017 Philipp Hörist <philipp@hoerist.com> + +This file is part of Gajim. + +Gajim is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 3 only. + +Gajim is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Gajim. If not, see <http://www.gnu.org/licenses/>. +''' + +import os +import logging +import time +import threading +import queue + +import nbxmpp +from gi.repository import GLib + +import dialogs +from common import gajim +from common.connection_handlers_events import ( + MessageNotSentEvent, MessageReceivedEvent) +from plugins import GajimPlugin + +log = logging.getLogger('gajim.plugin_system.oldpgp') + +ERROR_MSG = '' +if not gajim.HAVE_GPG: + ERROR_MSG = 'Please install python-gnupg' + +ALLOWED_TAGS = [('request', nbxmpp.NS_RECEIPTS), + ('active', nbxmpp.NS_CHATSTATES), + ('gone', nbxmpp.NS_CHATSTATES), + ('inactive', nbxmpp.NS_CHATSTATES), + ('paused', nbxmpp.NS_CHATSTATES), + ('composing', nbxmpp.NS_CHATSTATES), + ('no-store', nbxmpp.NS_MSG_HINTS), + ('store', nbxmpp.NS_MSG_HINTS), + ('no-copy', nbxmpp.NS_MSG_HINTS), + ('no-permanent-store', nbxmpp.NS_MSG_HINTS), + ('replace', nbxmpp.NS_CORRECT)] + + +class OldPGPPlugin(GajimPlugin): + + def init(self): + if ERROR_MSG: + self.activatable = False + self.available_text = ERROR_MSG + return + self.config_dialog = None + self.encryption_name = 'PGP' + self.config_dialog = None + self.gui_extension_points = { + 'encrypt' + self.encryption_name: (self._encrypt_message, None), + 'decrypt': (self._message_received, None), + 'send_message' + self.encryption_name: ( + self._before_sendmessage, None), + 'encryption_dialog' + self.encryption_name: ( + self.on_encryption_button_clicked, None), + 'encryption_state' + self.encryption_name: ( + self.encryption_state, None)} + + self.gpg_instances = {} + self.decrypt_queue = queue.Queue() + self.thread = None + + def get_gpg(self, account): + return self.gpg_instances[account] + + def activate(self): + for account in gajim.connections: + self.gpg_instances[account] = gajim.connections[account].gpg + + def deactivate(self): + pass + + @staticmethod + def activate_encryption(chat_control): + return True + + @staticmethod + def encryption_state(chat_control, state): + key_id = chat_control.contact.keyID + account = chat_control.account + authenticated, _ = check_state(key_id, account) + state['visible'] = True + state['authenticated'] = authenticated + + @staticmethod + def on_encryption_button_clicked(chat_control): + account = chat_control.account + key_id = chat_control.contact.keyID + transient = chat_control.parent_win.window + authenticated, info = check_state(key_id, account) + dialogs.InformationDialog(authenticated, info, transient) + + @staticmethod + def _before_sendmessage(chat_control): + account = chat_control.account + if not chat_control.contact.keyID: + dialogs.ErrorDialog( + _('No OpenPGP key assigned'), + _('No OpenPGP key is assigned to this contact. So you cannot ' + 'encrypt messages with OpenPGP.')) + chat_control.sendmessage = False + elif not gajim.config.get_per('accounts', account, 'keyid'): + dialogs.ErrorDialog( + _('No OpenPGP key assigned'), + _('No OpenPGP key is assigned to your account. So you cannot ' + 'encrypt messages with OpenPGP.')) + chat_control.sendmessage = False + + @staticmethod + def _get_info_message(): + msg = '[This message is *encrypted* (See :XEP:`27`]' + lang = os.getenv('LANG') + if lang is not None and not lang.startswith('en'): + # we're not english: one in locale and one en + msg = _('[This message is *encrypted* (See :XEP:`27`]') + \ + ' (' + msg + ')' + return msg + + def _message_received(self, conn, obj, callback): + if obj.encrypted: + # Another Plugin already decrypted the message + return + account = conn.name + if isinstance(obj, MessageReceivedEvent): + enc_tag = obj.stanza.getTag('x', namespace=nbxmpp.NS_ENCRYPTED) + else: + enc_tag = obj.msg_.getTag('x', namespace=nbxmpp.NS_ENCRYPTED) + if enc_tag: + encmsg = enc_tag.getData() + key_id = gajim.config.get_per('accounts', account, 'keyid') + if key_id: + obj.encrypted = 'xep27' + self.decrypt_queue.put([encmsg, key_id, obj, conn, callback]) + if not self.thread: + self.thread = threading.Thread(target=self.worker) + self.thread.start() + return + + def worker(self): + while True: + try: + item = self.decrypt_queue.get(block=False) + encmsg, key_id, obj, conn, callback = item + account = conn.name + decmsg = self.get_gpg(account).decrypt(encmsg, key_id) + decmsg = conn.connection.Dispatcher. \ + replace_non_character(decmsg) + # \x00 chars are not allowed in C (so in GTK) + msg = decmsg.replace('\x00', '') + obj.msgtxt = msg + GLib.idle_add(callback, obj) + except queue.Empty: + self.thread = None + break + + def _encrypt_message(self, conn, obj, callback): + account = conn.name + if not obj.message: + # We only encrypt the actual message + self._finished_encrypt(obj, callback=callback) + return + + if obj.keyID == 'UNKNOWN': + error = _('Neither the remote presence is signed, nor a key was ' + 'assigned.') + elif obj.keyID.endswith('MISMATCH'): + error = _('The contact\'s key (%s) does not match the key assigned ' + 'in Gajim.' % obj.keyID[:8]) + else: + my_key_id = gajim.config.get_per('accounts', account, 'keyid') + key_list = [obj.keyID, my_key_id] + + def _on_encrypted(output): + msgenc, error = output + if error.startswith('NOT_TRUSTED'): + def on_yes(checked): + if checked: + obj.conn.gpg.always_trust.append(obj.keyID) + gajim.thread_interface( + self.get_gpg(account).encrypt, + [obj.message, key_list, True], + _on_encrypted, []) + + def on_no(): + self._finished_encrypt( + obj, msgenc=msgenc, error=error, conn=conn) + + dialogs.YesNoDialog( + _('Untrusted OpenPGP key'), + _('The OpenPGP key used to encrypt this chat is not ' + 'trusted. Do you really want to encrypt this ' + 'message?'), + checktext=_('_Do not ask me again'), + on_response_yes=on_yes, + on_response_no=on_no) + else: + self._finished_encrypt( + obj, msgenc=msgenc, error=error, conn=conn, + callback=callback) + gajim.thread_interface( + self.get_gpg(account).encrypt, + [obj.message, key_list, False], + _on_encrypted, []) + return + self._finished_encrypt(conn, obj, error=error) + + def _finished_encrypt(self, obj, msgenc=None, error=None, + conn=None, callback=None): + if error: + log.error('python-gnupg error: %s', error) + gajim.nec.push_incoming_event( + MessageNotSentEvent( + None, conn=conn, jid=obj.jid, message=obj.message, + error=error, time_=time.time(), session=obj.session)) + return + self.cleanup_stanza(obj) + + if msgenc: + obj.msg_iq.setBody(self._get_info_message()) + obj.msg_iq.setTag( + 'x', namespace=nbxmpp.NS_ENCRYPTED).setData(msgenc) + eme_node = nbxmpp.Node('encryption', + attrs={'xmlns': nbxmpp.NS_EME, + 'namespace': nbxmpp.NS_ENCRYPTED}) + obj.msg_iq.addChild(node=eme_node) + + # Set xhtml to None so it doesnt get logged + obj.xhtml = None + print_msg_to_log(obj.msg_iq) + callback(obj) + + @staticmethod + def cleanup_stanza(obj): + ''' We make sure only allowed tags are in the stanza ''' + stanza = nbxmpp.Message( + to=obj.msg_iq.getTo(), + typ=obj.msg_iq.getType()) + stanza.setThread(obj.msg_iq.getThread()) + for tag, ns in ALLOWED_TAGS: + node = obj.msg_iq.getTag(tag, namespace=ns) + if node: + stanza.addChild(node=node) + obj.msg_iq = stanza + + +def print_msg_to_log(stanza): + """ Prints a stanza in a fancy way to the log """ + stanzastr = '\n' + stanza.__str__(fancy=True) + '\n' + stanzastr = stanzastr[0:-1] + log.debug('\n' + '-'*15 + stanzastr + '-'*15) + + +def check_state(key_id, account): + error = None + if key_id.endswith('MISMATCH'): + verification_status = _('''Contact's identity NOT verified''') + info = _('The contact\'s key (%s) <b>does not match</b> the key ' + 'assigned in Gajim.') % key_id[:8] + elif not key_id: + # No key assigned nor a key is used by remote contact + verification_status = _('No OpenPGP key assigned') + info = _('No OpenPGP key is assigned to this contact. So you cannot' + ' encrypt messages.') + else: + error = gajim.connections[account].gpg.encrypt('test', [key_id])[1] + if error: + verification_status = _('''Contact's identity NOT verified''') + info = _('OpenPGP key is assigned to this contact, but <b>you ' + 'do not trust their key</b>, so message <b>cannot</b> be ' + 'encrypted. Use your OpenPGP client to trust their key.') + else: + verification_status = _('''Contact's identity verified''') + info = _('OpenPGP Key is assigned to this contact, and you ' + 'trust their key, so messages will be encrypted.') + return (verification_status, info) |