Mercurial > libervia-backend
comparison sat/plugins/plugin_sec_aesgcm.py @ 3922:0ff265725489
plugin XEP-0447: handle attachment and download:
- plugin XEP-0447 can now be used in message attachments and to retrieve an attachment
- plugin attach: `attachment` being processed is added to `extra` so the handler can inspect it
- plugin attach: `size` is added to attachment
- plugin download: a whole attachment dict is now used in `download` and
`file_download`/`file_download_complete`. `download_uri` can be used as a shortcut when
just a URI is used. In addition to URI scheme handler, whole attachment handlers can now
be registered with `register_download_handler`
- plugin XEP-0363: `file_http_upload` `XEP-0363_upload_size` triggers have been renamed to
`XEP-0363_upload_pre_slot` and is now using a dict with arguments, allowing for the size
but also the filename to be modified, which is necessary for encryption (filename may
be hidden from URL this way).
- plugin XEP-0446: fix wrong element name
- plugin XEP-0447: source handler can now be registered (`url-data` is registered by
default)
- plugin XEP-0447: source parsing has been put in a separated `parse_sources_elt` method,
as it may be useful to do it independently (notably with XEP-0448)
- plugin XEP-0447: parse received message and complete attachments when suitable
- plugin XEP-0447: can now be used with message attachments
- plugin XEP-0447: can now be used with attachments download
- renamed `options` arguments to `extra` for consistency
- some style change (progressive move from legacy camelCase to PEP8 snake_case)
- some typing
rel 379
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 06 Oct 2022 16:02:05 +0200 |
parents | 3ef988734869 |
children | 78b5f356900c |
comparison
equal
deleted
inserted
replaced
3921:cc2705225778 | 3922:0ff265725489 |
---|---|
61 def __init__(self, host): | 61 def __init__(self, host): |
62 self.host = host | 62 self.host = host |
63 log.info(_("AESGCM plugin initialization")) | 63 log.info(_("AESGCM plugin initialization")) |
64 self._http_upload = host.plugins['XEP-0363'] | 64 self._http_upload = host.plugins['XEP-0363'] |
65 self._attach = host.plugins["ATTACH"] | 65 self._attach = host.plugins["ATTACH"] |
66 host.plugins["DOWNLOAD"].registerScheme( | 66 host.plugins["DOWNLOAD"].register_scheme( |
67 "aesgcm", self.download | 67 "aesgcm", self.download |
68 ) | 68 ) |
69 self._attach.register( | 69 self._attach.register( |
70 self.canHandleAttachment, self.attach, encrypted=True) | 70 self.canHandleAttachment, self.attach, encrypted=True) |
71 host.trigger.add("XEP-0363_upload_size", self._uploadSizeTrigger) | 71 host.trigger.add("XEP-0363_upload_pre_slot", self._upload_pre_slot) |
72 host.trigger.add("XEP-0363_upload", self._uploadTrigger) | 72 host.trigger.add("XEP-0363_upload", self._upload_trigger) |
73 host.trigger.add("messageReceived", self._messageReceivedTrigger) | 73 host.trigger.add("messageReceived", self._messageReceivedTrigger) |
74 | 74 |
75 async def download(self, client, uri_parsed, dest_path, options): | 75 async def download(self, client, uri_parsed, dest_path, options): |
76 fragment = bytes.fromhex(uri_parsed.fragment) | 76 fragment = bytes.fromhex(uri_parsed.fragment) |
77 | 77 |
127 client=client, | 127 client=client, |
128 file_obj=file_obj, | 128 file_obj=file_obj, |
129 decryptor=decryptor)) | 129 decryptor=decryptor)) |
130 else: | 130 else: |
131 d = defer.Deferred() | 131 d = defer.Deferred() |
132 self.host.plugins["DOWNLOAD"].errbackDownload(file_obj, d, resp) | 132 self.host.plugins["DOWNLOAD"].errback_download(file_obj, d, resp) |
133 return progress_id, d | 133 return progress_id, d |
134 | 134 |
135 async def canHandleAttachment(self, client, data): | 135 async def canHandleAttachment(self, client, data): |
136 try: | 136 try: |
137 await self._http_upload.getHTTPUploadEntity(client) | 137 await self._http_upload.getHTTPUploadEntity(client) |
138 except exceptions.NotFound: | 138 except exceptions.NotFound: |
139 return False | 139 return False |
140 else: | 140 else: |
141 return True | 141 return True |
142 | 142 |
143 async def _uploadCb(self, client, filepath, filename, options): | 143 async def _upload_cb(self, client, filepath, filename, extra): |
144 options['encryption'] = C.ENC_AES_GCM | 144 extra['encryption'] = C.ENC_AES_GCM |
145 return await self._http_upload.fileHTTPUpload( | 145 return await self._http_upload.file_http_upload( |
146 client=client, | 146 client=client, |
147 filepath=filepath, | 147 filepath=filepath, |
148 filename=filename, | 148 filename=filename, |
149 options=options | 149 extra=extra |
150 ) | 150 ) |
151 | 151 |
152 async def attach(self, client, data): | 152 async def attach(self, client, data): |
153 # XXX: the attachment removal/resend code below is due to the one file per | 153 # XXX: the attachment removal/resend code below is due to the one file per |
154 # message limitation of OMEMO media sharing unofficial XEP. We have to remove | 154 # message limitation of OMEMO media sharing unofficial XEP. We have to remove |
158 # elements than body). | 158 # elements than body). |
159 attachments = data["extra"][C.MESS_KEY_ATTACHMENTS] | 159 attachments = data["extra"][C.MESS_KEY_ATTACHMENTS] |
160 if not data['message'] or data['message'] == {'': ''}: | 160 if not data['message'] or data['message'] == {'': ''}: |
161 extra_attachments = attachments[1:] | 161 extra_attachments = attachments[1:] |
162 del attachments[1:] | 162 del attachments[1:] |
163 await self._attach.uploadFiles(client, data, upload_cb=self._uploadCb) | 163 await self._attach.upload_files(client, data, upload_cb=self._upload_cb) |
164 else: | 164 else: |
165 # we have a message, we must send first attachment separately | 165 # we have a message, we must send first attachment separately |
166 extra_attachments = attachments[:] | 166 extra_attachments = attachments[:] |
167 attachments.clear() | 167 attachments.clear() |
168 del data["extra"][C.MESS_KEY_ATTACHMENTS] | 168 del data["extra"][C.MESS_KEY_ATTACHMENTS] |
169 | 169 |
170 body_elt = next(data["xml"].elements(C.NS_CLIENT, "body")) | 170 body_elt = data["xml"].body |
171 if body_elt is None: | |
172 body_elt = data["xml"].addElement("body") | |
171 | 173 |
172 for attachment in attachments: | 174 for attachment in attachments: |
173 body_elt.addContent(attachment["url"]) | 175 body_elt.addContent(attachment["url"]) |
174 | 176 |
175 for attachment in extra_attachments: | 177 for attachment in extra_attachments: |
217 file_obj.close() | 219 file_obj.close() |
218 else: | 220 else: |
219 decrypted = decryptor.update(data) | 221 decrypted = decryptor.update(data) |
220 file_obj.write(decrypted) | 222 file_obj.write(decrypted) |
221 | 223 |
222 def _uploadSizeTrigger(self, client, options, file_path, size, size_adjust): | 224 def _upload_pre_slot(self, client, extra, file_metadata): |
223 if options.get('encryption') != C.ENC_AES_GCM: | 225 if extra.get('encryption') != C.ENC_AES_GCM: |
224 return True | 226 return True |
225 # the tag is appended to the file | 227 # the tag is appended to the file |
226 size_adjust.append(16) | 228 file_metadata["size"] += 16 |
227 return True | 229 return True |
228 | 230 |
229 def _encrypt(self, data, encryptor): | 231 def _encrypt(self, data, encryptor): |
230 if data: | 232 if data: |
231 return encryptor.update(data) | 233 return encryptor.update(data) |
237 return ret + tag | 239 return ret + tag |
238 except AlreadyFinalized: | 240 except AlreadyFinalized: |
239 # as we have already finalized, we can now send EOF | 241 # as we have already finalized, we can now send EOF |
240 return b'' | 242 return b'' |
241 | 243 |
242 def _uploadTrigger(self, client, options, sat_file, file_producer, slot): | 244 def _upload_trigger(self, client, extra, sat_file, file_producer, slot): |
243 if options.get('encryption') != C.ENC_AES_GCM: | 245 if extra.get('encryption') != C.ENC_AES_GCM: |
244 return True | 246 return True |
245 log.debug("encrypting file with AES-GCM") | 247 log.debug("encrypting file with AES-GCM") |
246 iv = secrets.token_bytes(12) | 248 iv = secrets.token_bytes(12) |
247 key = secrets.token_bytes(32) | 249 key = secrets.token_bytes(32) |
248 fragment = f'{iv.hex()}{key.hex()}' | 250 fragment = f'{iv.hex()}{key.hex()}' |
253 | 255 |
254 # encrypted data size will be bigger than original file size | 256 # encrypted data size will be bigger than original file size |
255 # so we need to check with final data length to avoid a warning on close() | 257 # so we need to check with final data length to avoid a warning on close() |
256 sat_file.check_size_with_read = True | 258 sat_file.check_size_with_read = True |
257 | 259 |
258 # file_producer get length directly from file, and this cause trouble has | 260 # file_producer get length directly from file, and this cause trouble as |
259 # we have to change the size because of encryption. So we adapt it here, | 261 # we have to change the size because of encryption. So we adapt it here, |
260 # else the producer would stop reading prematurely | 262 # else the producer would stop reading prematurely |
261 file_producer.length = sat_file.size | 263 file_producer.length = sat_file.size |
262 | 264 |
263 encryptor = ciphers.Cipher( | 265 encryptor = ciphers.Cipher( |