From 3721bf9f6b19ceaae75454956571c33caf5f1e87 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 11 Feb 2017 04:02:20 +0000 Subject: [PATCH] Implement XEP-0300 (Use of Cryptographic Hash Functions in XMPP) This is used to provide hash agility support and let other XEPs select which hash function they support. --- slixmpp/plugins/xep_0300/__init__.py | 16 ++++++ slixmpp/plugins/xep_0300/hash.py | 80 ++++++++++++++++++++++++++++ slixmpp/plugins/xep_0300/stanza.py | 35 ++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 slixmpp/plugins/xep_0300/__init__.py create mode 100644 slixmpp/plugins/xep_0300/hash.py create mode 100644 slixmpp/plugins/xep_0300/stanza.py diff --git a/slixmpp/plugins/xep_0300/__init__.py b/slixmpp/plugins/xep_0300/__init__.py new file mode 100644 index 00000000..522d40e3 --- /dev/null +++ b/slixmpp/plugins/xep_0300/__init__.py @@ -0,0 +1,16 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2017 Emmanuel Gil Peyrot + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.base import register_plugin + +from slixmpp.plugins.xep_0300 import stanza +from slixmpp.plugins.xep_0300.stanza import Hash +from slixmpp.plugins.xep_0300.hash import XEP_0300 + + +register_plugin(XEP_0300) diff --git a/slixmpp/plugins/xep_0300/hash.py b/slixmpp/plugins/xep_0300/hash.py new file mode 100644 index 00000000..0c71aac1 --- /dev/null +++ b/slixmpp/plugins/xep_0300/hash.py @@ -0,0 +1,80 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2017 Emmanuel Gil Peyrot + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from base64 import b64encode +import hashlib +import logging + +from slixmpp.plugins import BasePlugin +from slixmpp.plugins.xep_0300 import stanza, Hash + + +log = logging.getLogger(__name__) + + +class XEP_0300(BasePlugin): + + name = 'xep_0300' + description = 'XEP-0300: Use of Cryptographic Hash Functions in XMPP' + dependencies = {'xep_0030'} + stanza = stanza + default_config = { + 'block_size': 1024 * 1024, # One MiB + 'prefered': 'sha-256', + 'enable_sha-1': False, + 'enable_sha-256': True, + 'enable_sha-512': True, + 'enable_sha3-256': True, + 'enable_sha3-512': True, + 'enable_BLAKE2b256': True, + 'enable_BLAKE2b512': True, + } + + _hashlib_function = { + 'sha-1': hashlib.sha1, + 'sha-256': hashlib.sha256, + 'sha-512': hashlib.sha512, + 'sha3-256': hashlib.sha3_256, + 'sha3-512': hashlib.sha3_512, + 'BLAKE2b256': lambda: hashlib.blake2b(digest_size=32), + 'BLAKE2b512': lambda: hashlib.blake2b(digest_size=64), + } + + def plugin_init(self): + namespace = 'urn:xmpp:hash-function-text-names:%s' + self.enabled_hashes = [] + for algo in self._hashlib_function: + if getattr(self, 'enable_' + algo, False): + self.enabled_hashes.append(namespace % algo) + + def session_bind(self, jid): + self.xmpp['xep_0030'].add_feature(Hash.namespace) + + for namespace in self.enabled_hashes: + self.xmpp['xep_0030'].add_feature(namespace) + + def plugin_end(self): + for namespace in self.enabled_hashes: + self.xmpp['xep_0030'].del_feature(namespace) + + self.xmpp['xep_0030'].del_feature(feature=Hash.namespace) + + def compute_hash(self, filename, function=None): + if function is None: + function = self.prefered + h = self._hashlib_function[function]() + with open(filename, 'rb') as f: + while True: + block = f.read(self.block_size) + if not block: + break + h.update(block) + hash_elem = Hash() + hash_elem['algo'] = function + hash_elem['value'] = b64encode(h.digest()) + return hash_elem diff --git a/slixmpp/plugins/xep_0300/stanza.py b/slixmpp/plugins/xep_0300/stanza.py new file mode 100644 index 00000000..f5ab483c --- /dev/null +++ b/slixmpp/plugins/xep_0300/stanza.py @@ -0,0 +1,35 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2017 Emmanuel Gil Peyrot + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase + + +class Hash(ElementBase): + name = 'hash' + namespace = 'urn:xmpp:hashes:2' + plugin_attrib = 'hash' + interfaces = {'algo', 'value'} + + allowed_algos = ['sha-1', 'sha-256', 'sha-512', 'sha3-256', 'sha3-512', 'BLAKE2b256', 'BLAKE2b512'] + + def set_algo(self, value): + if value in self.allowed_algos: + self._set_attr('algo', value) + elif value in [None, '']: + self._del_attr('algo') + else: + raise ValueError('Invalid algo: %s' % value) + + def get_value(self): + return self.xml.text + + def set_value(self, value): + self.xml.text = value + + def del_value(self): + self.xml.text = ''