comparison src/plugins/plugin_xep_0300.py @ 1618:0de5f210fe56

plugin XEP-0300: implemented hashing: - parseHashElt do what it says - calculateHashElt can calculate a whole hash in a non-blocking way - or getHasher can be used for progressive hashing - getBestPeerAlgo return the best available hashing algorithm of an entity
author Goffi <goffi@goffi.org>
date Tue, 17 Nov 2015 19:48:19 +0100
parents bb451fd1cea3
children d17772b0fe22
comparison
equal deleted inserted replaced
1617:d05f9179fe22 1618:0de5f210fe56
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from sat.core.i18n import _ 20 from sat.core.i18n import _
21 from sat.core.log import getLogger 21 from sat.core.log import getLogger
22 log = getLogger(__name__) 22 log = getLogger(__name__)
23 from sat.core import exceptions
23 from twisted.words.xish import domish 24 from twisted.words.xish import domish
25 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
26 from twisted.internet import threads
27 from twisted.internet import defer
28 from zope.interface import implements
29 from wokkel import disco, iwokkel
30 from collections import OrderedDict
24 import hashlib 31 import hashlib
25 32
26
27 NS_HASHES = "urn:xmpp:hashes:1"
28 33
29 PLUGIN_INFO = { 34 PLUGIN_INFO = {
30 "name": "Cryptographic Hash Functions", 35 "name": "Cryptographic Hash Functions",
31 "import_name": "XEP-0300", 36 "import_name": "XEP-0300",
32 "type": "XEP", 37 "type": "XEP",
33 "protocols": ["XEP-0300"], 38 "protocols": ["XEP-0300"],
34 "main": "XEP_0300", 39 "main": "XEP_0300",
35 "handler": "no", 40 "handler": "yes",
36 "description": _("""Management of cryptographic hashes""") 41 "description": _("""Management of cryptographic hashes""")
37 } 42 }
38 43
44 NS_HASHES = "urn:xmpp:hashes:1"
45 NS_HASHES_FUNCTIONS = u"urn:xmpp:hash-function-text-names:{}"
46 BUFFER_SIZE = 2**12
47 ALGO_DEFAULT = 'sha-256'
48
39 49
40 class XEP_0300(object): 50 class XEP_0300(object):
41 ALGOS = {u'md5': hashlib.md5, 51 ALGOS = OrderedDict((
42 u'sha-1': hashlib.sha1, 52 (u'md5', hashlib.md5),
43 u'sha-256': hashlib.sha256, 53 (u'sha-1', hashlib.sha1),
44 u'sha-512': hashlib.sha512, 54 (u'sha-256', hashlib.sha256),
45 } 55 (u'sha-512', hashlib.sha512),
56 ))
46 57
47 def __init__(self, host): 58 def __init__(self, host):
48 log.info(_("plugin Hashes initialization")) 59 log.info(_("plugin Hashes initialization"))
49 60
50 def buidHash(self, file_obj=None, algo='sha-256'): 61 def getHandler(self, profile):
62 return XEP_0300_handler()
63
64 def getHasher(self, algo):
65 """Return hasher instance
66
67 /!\\ blocking method, considere using calculateHashElt
68 if you want to hash a big file
69 @param algo(unicode): one of the XEP_300.ALGOS keys
70 @return (hash object): same object s in hashlib.
71 update method need to be called for each chunh
72 diget or hexdigest can be used at the end
73 """
74 return self.ALGOS[algo]()
75
76 @defer.inlineCallbacks
77 def getBestPeerAlgo(self, to_jid, profile):
78 """Return the best available hashing algorith of other peer
79
80 @param to_jid(jid.JID): peer jid
81 @parm profile: %(doc_profile)s
82 @return (D(unicode, None)): best available algorithm,
83 or None if hashing is not possible
84 """
85 for algo in reversed(XEP_0300.ALGOS):
86 has_feature = yield self.host.hasFeature(NS_HASHES_FUNCTIONS.format(algo), to_jid, profile)
87 if has_feature:
88 log.debug(u"Best hashing algorithm found for {jid}: {algo}".format(
89 jid=to_jid.full(),
90 algo=algo))
91 defer.returnValue(algo)
92
93 def calculateHashBlocking(self, file_obj, hasher):
94 """Calculate hash in a blocking way
95
96 @param file_obj(file): a file-like object
97 @param hasher(callable): the method to call to initialise hash object
98 @return (str): the hex digest of the hash
99 """
100 hash_ = hasher()
101 while True:
102 buf = file_obj.read(BUFFER_SIZE)
103 if not buf:
104 break
105 hash_.update(buf)
106 return hash_.hexdigest()
107
108 def calculateHashElt(self, file_obj=None, algo=ALGO_DEFAULT):
51 """Compute hash and build hash element 109 """Compute hash and build hash element
52 110
53 @param file_obj(file, None): file to use to calculate the hash 111 @param file_obj(file, None): file-like object to use to calculate the hash
54 if file_obj is None, en empty hash element will be returned (useful e.g. in XEP-0234) 112 @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS
113 @return (D(domish.Element)): hash element
114 """
115 def hashCalculated(hash_):
116 return self.buildHashElt(hash_, algo)
117 hasher = self.ALGOS[algo]
118 hash_d = threads.deferToThread(self.calculateHashBlocking, file_obj, hasher)
119 hash_d.addCallback(hashCalculated)
120 return hash_d
121
122 def buildHashElt(self, hash_=None, algo=ALGO_DEFAULT):
123 """Compute hash and build hash element
124
125 @param hash_(None, str): hash to use, or None for an empty element
55 @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS 126 @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS
56 @return (domish.Element): computed hash 127 @return (domish.Element): computed hash
57 """ 128 """
58 hasher = self.ALGOS[algo]
59 hash_elt = domish.Element((NS_HASHES, 'hash')) 129 hash_elt = domish.Element((NS_HASHES, 'hash'))
130 if hash_ is not None:
131 hash_elt.addContent(hash_)
60 hash_elt['algo']=algo 132 hash_elt['algo']=algo
61 # TODO: actually hash, use deferToThread
62 return hash_elt 133 return hash_elt
63 134
135 def parseHashElt(self, parent):
136 """Find and parse a hash element
137
138 if multiple elements are found, the strongest managed one is returned
139 @param (domish.Element): parent of <hash/> element
140 @return (tuple[unicode, str]): (algo, hash) tuple
141 both values can be None if <hash/> is empty
142 @raise exceptions.NotFound: the element is not present
143 """
144 algos = XEP_0300.ALGOS.keys()
145 hash_elt = None
146 best_algo = None
147 best_value = None
148 for hash_elt in parent.elements(NS_HASHES, 'hash'):
149 algo = hash_elt.getAttribute('algo')
150 try:
151 idx = algos.index(algo)
152 except ValueError:
153 log.warning(u"Proposed {} algorithm is not managed".format(algo))
154 algo = None
155 continue
156
157 if best_algo is None or algos.index(best_algo) < idx:
158 best_algo = algo
159 best_value = str(hash_elt) or None
160
161 if not hash_elt:
162 raise exceptions.NotFound
163 return best_algo, best_value
164
165
166 class XEP_0300_handler(XMPPHandler):
167 implements(iwokkel.IDisco)
168
169 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
170 hash_functions_names = [disco.DiscoFeature(NS_HASHES_FUNCTIONS.format(algo)) for algo in XEP_0300.ALGOS]
171 return [disco.DiscoFeature(NS_HASHES)] + hash_functions_names
172
173 def getDiscoItems(self, requestor, target, nodeIdentifier=''):
174 return []