From 5ab77c745270d7d5c016c1dc7ef2a82533a4b16e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 17 Jul 2014 14:19:04 +0200 Subject: Rename to slixmpp --- slixmpp/plugins/xep_0027/__init__.py | 15 ++++ slixmpp/plugins/xep_0027/gpg.py | 170 +++++++++++++++++++++++++++++++++++ slixmpp/plugins/xep_0027/stanza.py | 53 +++++++++++ 3 files changed, 238 insertions(+) create mode 100644 slixmpp/plugins/xep_0027/__init__.py create mode 100644 slixmpp/plugins/xep_0027/gpg.py create mode 100644 slixmpp/plugins/xep_0027/stanza.py (limited to 'slixmpp/plugins/xep_0027') diff --git a/slixmpp/plugins/xep_0027/__init__.py b/slixmpp/plugins/xep_0027/__init__.py new file mode 100644 index 00000000..1f510cb2 --- /dev/null +++ b/slixmpp/plugins/xep_0027/__init__.py @@ -0,0 +1,15 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.base import register_plugin + +from slixmpp.plugins.xep_0027.stanza import Signed, Encrypted +from slixmpp.plugins.xep_0027.gpg import XEP_0027 + + +register_plugin(XEP_0027) diff --git a/slixmpp/plugins/xep_0027/gpg.py b/slixmpp/plugins/xep_0027/gpg.py new file mode 100644 index 00000000..e9978416 --- /dev/null +++ b/slixmpp/plugins/xep_0027/gpg.py @@ -0,0 +1,170 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.thirdparty import GPG + +from slixmpp.stanza import Presence, Message +from slixmpp.plugins.base import BasePlugin, register_plugin +from slixmpp.xmlstream import ElementBase, register_stanza_plugin +from slixmpp.xmlstream.handler import Callback +from slixmpp.xmlstream.matcher import StanzaPath +from slixmpp.plugins.xep_0027 import stanza, Signed, Encrypted + + +def _extract_data(data, kind): + stripped = [] + begin_headers = False + begin_data = False + for line in data.split('\n'): + if not begin_headers and 'BEGIN PGP %s' % kind in line: + begin_headers = True + continue + if begin_headers and line.strip() == '': + begin_data = True + continue + if 'END PGP %s' % kind in line: + return '\n'.join(stripped) + if begin_data: + stripped.append(line) + return '' + + +class XEP_0027(BasePlugin): + + name = 'xep_0027' + description = 'XEP-0027: Current Jabber OpenPGP Usage' + dependencies = set() + stanza = stanza + default_config = { + 'gpg_binary': 'gpg', + 'gpg_home': '', + 'use_agent': True, + 'keyring': None, + 'key_server': 'pgp.mit.edu' + } + + def plugin_init(self): + self.gpg = GPG(gnupghome=self.gpg_home, + gpgbinary=self.gpg_binary, + use_agent=self.use_agent, + keyring=self.keyring) + + self.xmpp.add_filter('out', self._sign_presence) + + self._keyids = {} + + self.api.register(self._set_keyid, 'set_keyid', default=True) + self.api.register(self._get_keyid, 'get_keyid', default=True) + self.api.register(self._del_keyid, 'del_keyid', default=True) + self.api.register(self._get_keyids, 'get_keyids', default=True) + + register_stanza_plugin(Presence, Signed) + register_stanza_plugin(Message, Encrypted) + + self.xmpp.add_event_handler('unverified_signed_presence', + self._handle_unverified_signed_presence, + threaded=True) + + self.xmpp.register_handler( + Callback('Signed Presence', + StanzaPath('presence/signed'), + self._handle_signed_presence)) + + self.xmpp.register_handler( + Callback('Encrypted Message', + StanzaPath('message/encrypted'), + self._handle_encrypted_message)) + + def plugin_end(self): + self.xmpp.remove_handler('Encrypted Message') + self.xmpp.remove_handler('Signed Presence') + self.xmpp.del_filter('out', self._sign_presence) + self.xmpp.del_event_handler('unverified_signed_presence', + self._handle_unverified_signed_presence) + + def _sign_presence(self, stanza): + if isinstance(stanza, Presence): + if stanza['type'] == 'available' or \ + stanza['type'] in Presence.showtypes: + stanza['signed'] = stanza['status'] + return stanza + + def sign(self, data, jid=None): + keyid = self.get_keyid(jid) + if keyid: + signed = self.gpg.sign(data, keyid=keyid) + return _extract_data(signed.data, 'SIGNATURE') + + def encrypt(self, data, jid=None): + keyid = self.get_keyid(jid) + if keyid: + enc = self.gpg.encrypt(data, keyid) + return _extract_data(enc.data, 'MESSAGE') + + def decrypt(self, data, jid=None): + template = '-----BEGIN PGP MESSAGE-----\n' + \ + '\n' + \ + '%s\n' + \ + '-----END PGP MESSAGE-----\n' + dec = self.gpg.decrypt(template % data) + return dec.data + + def verify(self, data, sig, jid=None): + template = '-----BEGIN PGP SIGNED MESSAGE-----\n' + \ + 'Hash: SHA1\n' + \ + '\n' + \ + '%s\n' + \ + '-----BEGIN PGP SIGNATURE-----\n' + \ + '\n' + \ + '%s\n' + \ + '-----END PGP SIGNATURE-----\n' + v = self.gpg.verify(template % (data, sig)) + return v + + def set_keyid(self, jid=None, keyid=None): + self.api['set_keyid'](jid, args=keyid) + + def get_keyid(self, jid=None): + return self.api['get_keyid'](jid) + + def del_keyid(self, jid=None): + self.api['del_keyid'](jid) + + def get_keyids(self): + return self.api['get_keyids']() + + def _handle_signed_presence(self, pres): + self.xmpp.event('unverified_signed_presence', pres) + + def _handle_unverified_signed_presence(self, pres): + verified = self.verify(pres['status'], pres['signed']) + if verified.key_id: + if not self.get_keyid(pres['from']): + known_keyids = [e['keyid'] for e in self.gpg.list_keys()] + if verified.key_id not in known_keyids: + self.gpg.recv_keys(self.key_server, verified.key_id) + self.set_keyid(jid=pres['from'], keyid=verified.key_id) + self.xmpp.event('signed_presence', pres) + + def _handle_encrypted_message(self, msg): + self.xmpp.event('encrypted_message', msg) + + # ================================================================= + + def _set_keyid(self, jid, node, ifrom, keyid): + self._keyids[jid] = keyid + + def _get_keyid(self, jid, node, ifrom, keyid): + return self._keyids.get(jid, None) + + def _del_keyid(self, jid, node, ifrom, keyid): + if jid in self._keyids: + del self._keyids[jid] + + def _get_keyids(self, jid, node, ifrom, data): + return self._keyids diff --git a/slixmpp/plugins/xep_0027/stanza.py b/slixmpp/plugins/xep_0027/stanza.py new file mode 100644 index 00000000..fd41cace --- /dev/null +++ b/slixmpp/plugins/xep_0027/stanza.py @@ -0,0 +1,53 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase + + +class Signed(ElementBase): + name = 'x' + namespace = 'jabber:x:signed' + plugin_attrib = 'signed' + interfaces = set(['signed']) + is_extension = True + + def set_signed(self, value): + parent = self.parent() + xmpp = parent.stream + data = xmpp['xep_0027'].sign(value, parent['from']) + if data: + self.xml.text = data + else: + del parent['signed'] + + def get_signed(self): + return self.xml.text + + +class Encrypted(ElementBase): + name = 'x' + namespace = 'jabber:x:encrypted' + plugin_attrib = 'encrypted' + interfaces = set(['encrypted']) + is_extension = True + + def set_encrypted(self, value): + parent = self.parent() + xmpp = parent.stream + data = xmpp['xep_0027'].encrypt(value, parent['to']) + if data: + self.xml.text = data + else: + del parent['encrypted'] + + def get_encrypted(self): + parent = self.parent() + xmpp = parent.stream + if self.xml.text: + return xmpp['xep_0027'].decrypt(self.xml.text, parent['to']) + return None -- cgit v1.2.3