Mercurial > libervia-backend
comparison sat/plugins/plugin_sec_aesgcm.py @ 3174:c90f27ce52b0
plugin aesgcm: look for "aesgcm" links in body to use them as attachments
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 18 Feb 2020 18:17:18 +0100 |
parents | 9d0df638c8b4 |
children | 98b321234068 |
comparison
equal
deleted
inserted
replaced
3173:343b8076e967 | 3174:c90f27ce52b0 |
---|---|
1 #!/usr/bin/env python3 | 1 #!/usr/bin/env python3 |
2 | 2 |
3 # SAT plugin for handling AES-GCM file encryption | 3 # SàT plugin for handling AES-GCM file encryption |
4 # Copyright (C) 2009-2020 Jérôme Poisson (goffi@goffi.org) | 4 # Copyright (C) 2009-2020 Jérôme Poisson (goffi@goffi.org) |
5 | 5 |
6 # This program is free software: you can redistribute it and/or modify | 6 # This program is free software: you can redistribute it and/or modify |
7 # it under the terms of the GNU Affero General Public License as published by | 7 # it under the terms of the GNU Affero General Public License as published by |
8 # the Free Software Foundation, either version 3 of the License, or | 8 # the Free Software Foundation, either version 3 of the License, or |
14 # GNU Affero General Public License for more details. | 14 # GNU Affero General Public License for more details. |
15 | 15 |
16 # You should have received a copy of the GNU Affero General Public License | 16 # You should have received a copy of the GNU Affero General Public License |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | 18 |
19 import re | |
19 from textwrap import dedent | 20 from textwrap import dedent |
20 from functools import partial | 21 from functools import partial |
22 from urllib.parse import urlparse | |
23 import mimetypes | |
21 import secrets | 24 import secrets |
22 from cryptography.hazmat.primitives import ciphers | 25 from cryptography.hazmat.primitives import ciphers |
23 from cryptography.hazmat.primitives.ciphers import modes | 26 from cryptography.hazmat.primitives.ciphers import modes |
24 from cryptography.hazmat import backends | 27 from cryptography.hazmat import backends |
25 from cryptography.exceptions import AlreadyFinalized | 28 from cryptography.exceptions import AlreadyFinalized |
45 Implementation of AES-GCM scheme, a way to encrypt files (not official XMPP standard). | 48 Implementation of AES-GCM scheme, a way to encrypt files (not official XMPP standard). |
46 See https://xmpp.org/extensions/inbox/omemo-media-sharing.html for details | 49 See https://xmpp.org/extensions/inbox/omemo-media-sharing.html for details |
47 """)), | 50 """)), |
48 } | 51 } |
49 | 52 |
53 AESGCM_RE = re.compile( | |
54 r'aesgcm:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9' | |
55 r'()@:%_\+.~#?&\/\/=]*)') | |
56 | |
50 | 57 |
51 class AESGCM(object): | 58 class AESGCM(object): |
52 | 59 |
53 def __init__(self, host): | 60 def __init__(self, host): |
54 self.host = host | 61 self.host = host |
56 host.plugins["DOWNLOAD"].registerScheme( | 63 host.plugins["DOWNLOAD"].registerScheme( |
57 "aesgcm", self.download | 64 "aesgcm", self.download |
58 ) | 65 ) |
59 host.trigger.add("XEP-0363_upload_size", self._uploadSizeTrigger) | 66 host.trigger.add("XEP-0363_upload_size", self._uploadSizeTrigger) |
60 host.trigger.add("XEP-0363_upload", self._uploadTrigger) | 67 host.trigger.add("XEP-0363_upload", self._uploadTrigger) |
68 host.trigger.add("messageReceived", self._messageReceivedTrigger) | |
61 | 69 |
62 async def download(self, client, uri_parsed, dest_path, options): | 70 async def download(self, client, uri_parsed, dest_path, options): |
63 fragment = bytes.fromhex(uri_parsed.fragment) | 71 fragment = bytes.fromhex(uri_parsed.fragment) |
64 | 72 |
65 # legacy method use 16 bits IV, but OMEMO media sharing published spec indicates | 73 # legacy method use 16 bits IV, but OMEMO media sharing published spec indicates |
196 f"data_cb was expected to be None, it is set to {sat_file.data_cb}") | 204 f"data_cb was expected to be None, it is set to {sat_file.data_cb}") |
197 | 205 |
198 # with data_cb we encrypt the file on the fly | 206 # with data_cb we encrypt the file on the fly |
199 sat_file.data_cb = partial(self._encrypt, encryptor=encryptor) | 207 sat_file.data_cb = partial(self._encrypt, encryptor=encryptor) |
200 return True | 208 return True |
209 | |
210 | |
211 def _popAESGCMLinks(self, match, links): | |
212 link = match.group() | |
213 if link not in links: | |
214 links.append(link) | |
215 return "" | |
216 | |
217 def _checkAESGCMAttachments(self, client, data): | |
218 if not data.get('message'): | |
219 return data | |
220 links = [] | |
221 | |
222 for lang, message in list(data['message'].items()): | |
223 message = AESGCM_RE.sub( | |
224 partial(self._popAESGCMLinks, links=links), | |
225 message) | |
226 if links: | |
227 message = message.strip() | |
228 if not message: | |
229 del data['message'][lang] | |
230 else: | |
231 data['message'][lang] = message | |
232 mess_encrypted = client.encryption.isEncrypted(data) | |
233 attachments = data['extra'].setdefault(C.MESS_KEY_ATTACHMENTS, []) | |
234 for link in links: | |
235 path = urlparse(link).path | |
236 attachment = { | |
237 "url": link, | |
238 } | |
239 media_type = mimetypes.guess_type(path, strict=False)[0] | |
240 if media_type is not None: | |
241 attachment[C.MESS_KEY_MEDIA_TYPE] = media_type | |
242 | |
243 if mess_encrypted: | |
244 # we don't add the encrypted flag if the message itself is not | |
245 # encrypted, because the decryption key is part of the link, | |
246 # so sending it over unencrypted channel is like having no | |
247 # encryption at all. | |
248 attachment['encrypted'] = True | |
249 attachments.append(attachment) | |
250 | |
251 return data | |
252 | |
253 def _messageReceivedTrigger(self, client, message_elt, post_treat): | |
254 # we use a post_treat callback instead of "message_parse" trigger because we need | |
255 # to check if the "encrypted" flag is set to decide if we add the same flag to the | |
256 # attachment | |
257 post_treat.addCallback(partial(self._checkAESGCMAttachments, client)) | |
258 return True |