Add support for XEP-0027
This commit is contained in:
parent
e0dd9c3618
commit
9c61c2882f
7 changed files with 1284 additions and 0 deletions
29
LICENSE
29
LICENSE
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
15
sleekxmpp/plugins/xep_0027/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0027/__init__.py
Normal 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)
|
161
sleekxmpp/plugins/xep_0027/gpg.py
Normal file
161
sleekxmpp/plugins/xep_0027/gpg.py
Normal 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
|
56
sleekxmpp/plugins/xep_0027/stanza.py
Normal file
56
sleekxmpp/plugins/xep_0027/stanza.py
Normal 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
|
||||
|
||||
|
||||
|
5
sleekxmpp/thirdparty/__init__.py
vendored
5
sleekxmpp/thirdparty/__init__.py
vendored
|
@ -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
1017
sleekxmpp/thirdparty/gnupg.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue