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(