summaryrefslogtreecommitdiff
path: root/sleekxmpp/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp/plugins')
-rw-r--r--sleekxmpp/plugins/__init__.py1
-rw-r--r--sleekxmpp/plugins/xep_0027/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0027/gpg.py161
-rw-r--r--sleekxmpp/plugins/xep_0027/stanza.py56
4 files changed, 233 insertions, 0 deletions
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index c374f27b..6913a49a 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -18,6 +18,7 @@ __all__ = [
'xep_0004', # Data Forms
'xep_0009', # Jabber-RPC
'xep_0012', # Last Activity
+ 'xep_0027', # Current Jabber OpenPGP Usage
'xep_0030', # Service Discovery
'xep_0033', # Extended Stanza Addresses
'xep_0045', # Multi-User Chat (Client)
diff --git a/sleekxmpp/plugins/xep_0027/__init__.py b/sleekxmpp/plugins/xep_0027/__init__.py
new file mode 100644
index 00000000..b6ed9676
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0027/__init__.py
@@ -0,0 +1,15 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0027.stanza import Signed, Encrypted
+from sleekxmpp.plugins.xep_0027.gpg import XEP_0027
+
+
+register_plugin(XEP_0027)
diff --git a/sleekxmpp/plugins/xep_0027/gpg.py b/sleekxmpp/plugins/xep_0027/gpg.py
new file mode 100644
index 00000000..ccc7c400
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0027/gpg.py
@@ -0,0 +1,161 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.thirdparty import GPG
+
+from sleekxmpp.stanza import Presence, Message
+from sleekxmpp.plugins.base import BasePlugin, register_plugin
+from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
+from sleekxmpp.xmlstream.handler import Callback
+from sleekxmpp.xmlstream.matcher import StanzaPath
+from sleekxmpp.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 == '':
+ 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
+
+ def plugin_init(self):
+ self.gpg_binary = self.config.get('gpg_binary', 'gpg')
+ self.gpg_home = self.config.get('gpg_home', '')
+ self.use_agent = self.config.get('use_agent', True)
+ self.keyring = self.config.get('keyring', None)
+ self.key_server = self.config.get('key_server', 'pgp.mit.edu')
+
+ 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 _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/sleekxmpp/plugins/xep_0027/stanza.py b/sleekxmpp/plugins/xep_0027/stanza.py
new file mode 100644
index 00000000..927693ad
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0027/stanza.py
@@ -0,0 +1,56 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.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'].bare)
+ 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
+
+
+