Mercurial > libervia-backend
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 [] |