Add support for XEP-0027

This commit is contained in:
Lance Stout 2012-04-06 15:13:04 -04:00
parent e0dd9c3618
commit 9c61c2882f
7 changed files with 1284 additions and 0 deletions

29
LICENSE
View file

@ -138,3 +138,32 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
python-gnupg: A Python wrapper for the GNU Privacy Guard
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Copyright (c) 2008-2012 by Vinay Sajip.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name(s) of the copyright holder(s) may not be used to endorse or
promote products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -3,5 +3,10 @@ try:
except:
from sleekxmpp.thirdparty.ordereddict import OrderedDict
try:
from gnupg import GPG
except:
from sleekxmpp.thirdparty.gnupg import GPG
from sleekxmpp.thirdparty import suelta
from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso

1017
sleekxmpp/thirdparty/gnupg.py vendored Normal file

File diff suppressed because it is too large Load diff