Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_xep_0448.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 4b842c1fb686 |
children | 111dce64dcb5 |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
51 C.PI_NAME: "Encryption for Stateless File Sharing", | 51 C.PI_NAME: "Encryption for Stateless File Sharing", |
52 C.PI_IMPORT_NAME: IMPORT_NAME, | 52 C.PI_IMPORT_NAME: IMPORT_NAME, |
53 C.PI_TYPE: C.PLUG_TYPE_EXP, | 53 C.PI_TYPE: C.PLUG_TYPE_EXP, |
54 C.PI_PROTOCOLS: ["XEP-0448"], | 54 C.PI_PROTOCOLS: ["XEP-0448"], |
55 C.PI_DEPENDENCIES: [ | 55 C.PI_DEPENDENCIES: [ |
56 "XEP-0103", "XEP-0300", "XEP-0334", "XEP-0363", "XEP-0384", "XEP-0447", | 56 "XEP-0103", |
57 "DOWNLOAD", "ATTACH" | 57 "XEP-0300", |
58 "XEP-0334", | |
59 "XEP-0363", | |
60 "XEP-0384", | |
61 "XEP-0447", | |
62 "DOWNLOAD", | |
63 "ATTACH", | |
58 ], | 64 ], |
59 C.PI_MAIN: "XEP_0448", | 65 C.PI_MAIN: "XEP_0448", |
60 C.PI_HANDLER: "yes", | 66 C.PI_HANDLER: "yes", |
61 C.PI_DESCRIPTION: dedent(_("""\ | 67 C.PI_DESCRIPTION: dedent( |
68 _( | |
69 """\ | |
62 Implementation of e2e encryption for media sharing | 70 Implementation of e2e encryption for media sharing |
63 """)), | 71 """ |
72 ) | |
73 ), | |
64 } | 74 } |
65 | 75 |
66 NS_ESFS = "urn:xmpp:esfs:0" | 76 NS_ESFS = "urn:xmpp:esfs:0" |
67 NS_AES_128_GCM = "urn:xmpp:ciphers:aes-128-gcm-nopadding:0" | 77 NS_AES_128_GCM = "urn:xmpp:ciphers:aes-128-gcm-nopadding:0" |
68 NS_AES_256_GCM = "urn:xmpp:ciphers:aes-256-gcm-nopadding:0" | 78 NS_AES_256_GCM = "urn:xmpp:ciphers:aes-256-gcm-nopadding:0" |
135 self, | 145 self, |
136 client: SatXMPPEntity, | 146 client: SatXMPPEntity, |
137 attachment: Dict[str, Any], | 147 attachment: Dict[str, Any], |
138 source: Dict[str, Any], | 148 source: Dict[str, Any], |
139 dest_path: Union[Path, str], | 149 dest_path: Union[Path, str], |
140 extra: Optional[Dict[str, Any]] = None | 150 extra: Optional[Dict[str, Any]] = None, |
141 ) -> Tuple[str, defer.Deferred]: | 151 ) -> Tuple[str, defer.Deferred]: |
142 # TODO: check hash | 152 # TODO: check hash |
143 if extra is None: | 153 if extra is None: |
144 extra = {} | 154 extra = {} |
145 try: | 155 try: |
152 try: | 162 try: |
153 download_url = source["url"] | 163 download_url = source["url"] |
154 except KeyError: | 164 except KeyError: |
155 raise ValueError(f"{source} has missing URL") | 165 raise ValueError(f"{source} has missing URL") |
156 | 166 |
157 if extra.get('ignore_tls_errors', False): | 167 if extra.get("ignore_tls_errors", False): |
158 log.warning( | 168 log.warning("TLS certificate check disabled, this is highly insecure") |
159 "TLS certificate check disabled, this is highly insecure" | |
160 ) | |
161 treq_client = treq_client_no_ssl | 169 treq_client = treq_client_no_ssl |
162 else: | 170 else: |
163 treq_client = treq | 171 treq_client = treq |
164 | 172 |
165 try: | 173 try: |
166 file_size = int(attachment["size"]) | 174 file_size = int(attachment["size"]) |
167 except (KeyError, ValueError): | 175 except (KeyError, ValueError): |
168 head_data = await treq_client.head(download_url) | 176 head_data = await treq_client.head(download_url) |
169 content_length = int(head_data.headers.getRawHeaders('content-length')[0]) | 177 content_length = int(head_data.headers.getRawHeaders("content-length")[0]) |
170 # the 128 bits tag is put at the end | 178 # the 128 bits tag is put at the end |
171 file_size = content_length - 16 | 179 file_size = content_length - 16 |
172 | 180 |
173 file_obj = stream.SatFile( | 181 file_obj = stream.SatFile( |
174 self.host, | 182 self.host, |
175 client, | 183 client, |
176 dest_path, | 184 dest_path, |
177 mode="wb", | 185 mode="wb", |
178 size = file_size, | 186 size=file_size, |
179 ) | 187 ) |
180 | 188 |
181 if cipher in (NS_AES_128_GCM, NS_AES_256_GCM): | 189 if cipher in (NS_AES_128_GCM, NS_AES_256_GCM): |
182 decryptor = ciphers.Cipher( | 190 decryptor = ciphers.Cipher( |
183 ciphers.algorithms.AES(key), | 191 ciphers.algorithms.AES(key), |
202 decrypt_cb = partial( | 210 decrypt_cb = partial( |
203 self.cbc_decrypt, | 211 self.cbc_decrypt, |
204 client=client, | 212 client=client, |
205 file_obj=file_obj, | 213 file_obj=file_obj, |
206 decryptor=decryptor, | 214 decryptor=decryptor, |
207 unpadder=unpadder | 215 unpadder=unpadder, |
208 ) | 216 ) |
209 finalize_cb = partial( | 217 finalize_cb = partial( |
210 self.cbc_decrypt_finalize, | 218 self.cbc_decrypt_finalize, |
211 file_obj=file_obj, | 219 file_obj=file_obj, |
212 decryptor=decryptor, | 220 decryptor=decryptor, |
213 unpadder=unpadder | 221 unpadder=unpadder, |
214 ) | 222 ) |
215 else: | 223 else: |
216 msg = f"cipher {cipher!r} is not supported" | 224 msg = f"cipher {cipher!r} is not supported" |
217 file_obj.close(error=msg) | 225 file_obj.close(error=msg) |
218 log.warning(msg) | 226 log.warning(msg) |
251 "iv": secrets.token_bytes(12), | 259 "iv": secrets.token_bytes(12), |
252 "key": secrets.token_bytes(32), | 260 "key": secrets.token_bytes(32), |
253 } | 261 } |
254 attachment["filename"] = filename | 262 attachment["filename"] = filename |
255 return await self._http_upload.file_http_upload( | 263 return await self._http_upload.file_http_upload( |
256 client=client, | 264 client=client, filepath=filepath, filename="encrypted", extra=extra |
257 filepath=filepath, | |
258 filename="encrypted", | |
259 extra=extra | |
260 ) | 265 ) |
261 | 266 |
262 async def attach(self, client, data): | 267 async def attach(self, client, data): |
263 # XXX: for now, XEP-0447/XEP-0448 only allow to send one file per <message/>, thus | 268 # XXX: for now, XEP-0447/XEP-0448 only allow to send one file per <message/>, thus |
264 # we need to send each file in a separate message, in the same way as for | 269 # we need to send each file in a separate message, in the same way as for |
265 # plugin_sec_aesgcm. | 270 # plugin_sec_aesgcm. |
266 attachments = data["extra"][C.KEY_ATTACHMENTS] | 271 attachments = data["extra"][C.KEY_ATTACHMENTS] |
267 if not data['message'] or data['message'] == {'': ''}: | 272 if not data["message"] or data["message"] == {"": ""}: |
268 extra_attachments = attachments[1:] | 273 extra_attachments = attachments[1:] |
269 del attachments[1:] | 274 del attachments[1:] |
270 else: | 275 else: |
271 # we have a message, we must send first attachment separately | 276 # we have a message, we must send first attachment separately |
272 extra_attachments = attachments[:] | 277 extra_attachments = attachments[:] |
285 file_hash = (attachment["hash_algo"], attachment["hash"]) | 290 file_hash = (attachment["hash_algo"], attachment["hash"]) |
286 file_sharing_elt = self._sfs.get_file_sharing_elt( | 291 file_sharing_elt = self._sfs.get_file_sharing_elt( |
287 [], | 292 [], |
288 name=attachment["filename"], | 293 name=attachment["filename"], |
289 size=attachment["size"], | 294 size=attachment["size"], |
290 file_hash=file_hash | 295 file_hash=file_hash, |
291 ) | 296 ) |
292 encrypted_elt = file_sharing_elt.sources.addElement( | 297 encrypted_elt = file_sharing_elt.sources.addElement( |
293 (NS_ESFS, "encrypted") | 298 (NS_ESFS, "encrypted") |
294 ) | 299 ) |
295 encrypted_elt["cipher"] = NS_AES_256_GCM | 300 encrypted_elt["cipher"] = NS_AES_256_GCM |
296 encrypted_elt.addElement( | 301 encrypted_elt.addElement( |
297 "key", | 302 "key", content=base64.b64encode(encryption_data["key"]).decode() |
298 content=base64.b64encode(encryption_data["key"]).decode() | |
299 ) | 303 ) |
300 encrypted_elt.addElement( | 304 encrypted_elt.addElement( |
301 "iv", | 305 "iv", content=base64.b64encode(encryption_data["iv"]).decode() |
302 content=base64.b64encode(encryption_data["iv"]).decode() | 306 ) |
303 ) | 307 encrypted_elt.addChild( |
304 encrypted_elt.addChild(self._h.build_hash_elt( | 308 self._h.build_hash_elt( |
305 attachment["encrypted_hash"], | 309 attachment["encrypted_hash"], attachment["encrypted_hash_algo"] |
306 attachment["encrypted_hash_algo"] | 310 ) |
307 )) | 311 ) |
308 encrypted_elt.addChild( | 312 encrypted_elt.addChild( |
309 self._sfs.get_sources_elt( | 313 self._sfs.get_sources_elt( |
310 [self._u.get_url_data_elt(attachment["url"])] | 314 [self._u.get_url_data_elt(attachment["url"])] |
311 ) | 315 ) |
312 ) | 316 ) |
313 data["xml"].addChild(file_sharing_elt) | 317 data["xml"].addChild(file_sharing_elt) |
314 | 318 |
315 for attachment in extra_attachments: | 319 for attachment in extra_attachments: |
316 # we send all remaining attachment in a separate message | 320 # we send all remaining attachment in a separate message |
317 await client.sendMessage( | 321 await client.sendMessage( |
318 to_jid=data['to'], | 322 to_jid=data["to"], |
319 message={'': ''}, | 323 message={"": ""}, |
320 subject=data['subject'], | 324 subject=data["subject"], |
321 mess_type=data['type'], | 325 mess_type=data["type"], |
322 extra={C.KEY_ATTACHMENTS: [attachment]}, | 326 extra={C.KEY_ATTACHMENTS: [attachment]}, |
323 ) | 327 ) |
324 | 328 |
325 if ((not data['extra'] | 329 if ( |
326 and (not data['message'] or data['message'] == {'': ''}) | 330 not data["extra"] |
327 and not data['subject'])): | 331 and (not data["message"] or data["message"] == {"": ""}) |
332 and not data["subject"] | |
333 ): | |
328 # nothing left to send, we can cancel the message | 334 # nothing left to send, we can cancel the message |
329 raise exceptions.CancelError("Cancelled by XEP_0448 attachment handling") | 335 raise exceptions.CancelError("Cancelled by XEP_0448 attachment handling") |
330 | 336 |
331 def gcm_decrypt( | 337 def gcm_decrypt( |
332 self, | 338 self, |
333 data: bytes, | 339 data: bytes, |
334 client: SatXMPPEntity, | 340 client: SatXMPPEntity, |
335 file_obj: stream.SatFile, | 341 file_obj: stream.SatFile, |
336 decryptor: CipherContext | 342 decryptor: CipherContext, |
337 ) -> None: | 343 ) -> None: |
338 if file_obj.tell() + len(data) > file_obj.size: # type: ignore | 344 if file_obj.tell() + len(data) > file_obj.size: # type: ignore |
339 # we're reaching end of file with this bunch of data | 345 # we're reaching end of file with this bunch of data |
340 # we may still have a last bunch if the tag is incomplete | 346 # we may still have a last bunch if the tag is incomplete |
341 bytes_left = file_obj.size - file_obj.tell() # type: ignore | 347 bytes_left = file_obj.size - file_obj.tell() # type: ignore |
369 self, | 375 self, |
370 data: bytes, | 376 data: bytes, |
371 client: SatXMPPEntity, | 377 client: SatXMPPEntity, |
372 file_obj: stream.SatFile, | 378 file_obj: stream.SatFile, |
373 decryptor: CipherContext, | 379 decryptor: CipherContext, |
374 unpadder: PaddingContext | 380 unpadder: PaddingContext, |
375 ) -> None: | 381 ) -> None: |
376 decrypted = decryptor.update(data) | 382 decrypted = decryptor.update(data) |
377 file_obj.write(unpadder.update(decrypted)) | 383 file_obj.write(unpadder.update(decrypted)) |
378 | 384 |
379 def cbc_decrypt_finalize( | 385 def cbc_decrypt_finalize( |
380 self, | 386 self, file_obj: stream.SatFile, decryptor: CipherContext, unpadder: PaddingContext |
381 file_obj: stream.SatFile, | |
382 decryptor: CipherContext, | |
383 unpadder: PaddingContext | |
384 ) -> None: | 387 ) -> None: |
385 decrypted = decryptor.finalize() | 388 decrypted = decryptor.finalize() |
386 file_obj.write(unpadder.update(decrypted)) | 389 file_obj.write(unpadder.update(decrypted)) |
387 file_obj.write(unpadder.finalize()) | 390 file_obj.write(unpadder.finalize()) |
388 file_obj.close() | 391 file_obj.close() |
389 | 392 |
390 def _upload_pre_slot(self, client, extra, file_metadata): | 393 def _upload_pre_slot(self, client, extra, file_metadata): |
391 if extra.get('encryption') != IMPORT_NAME: | 394 if extra.get("encryption") != IMPORT_NAME: |
392 return True | 395 return True |
393 # the tag is appended to the file | 396 # the tag is appended to the file |
394 file_metadata["size"] += 16 | 397 file_metadata["size"] += 16 |
395 return True | 398 return True |
396 | 399 |
412 encrypted_hasher.update(ret) | 415 encrypted_hasher.update(ret) |
413 attachment["encrypted_hash"] = encrypted_hasher.hexdigest() | 416 attachment["encrypted_hash"] = encrypted_hasher.hexdigest() |
414 return ret | 417 return ret |
415 except AlreadyFinalized: | 418 except AlreadyFinalized: |
416 # as we have already finalized, we can now send EOF | 419 # as we have already finalized, we can now send EOF |
417 return b'' | 420 return b"" |
418 | 421 |
419 def _upload_trigger(self, client, extra, sat_file, file_producer, slot): | 422 def _upload_trigger(self, client, extra, sat_file, file_producer, slot): |
420 if extra.get('encryption') != IMPORT_NAME: | 423 if extra.get("encryption") != IMPORT_NAME: |
421 return True | 424 return True |
422 attachment = extra["attachment"] | 425 attachment = extra["attachment"] |
423 encryption_data = extra["encryption_data"] | 426 encryption_data = extra["encryption_data"] |
424 log.debug("encrypting file with AES-GCM") | 427 log.debug("encrypting file with AES-GCM") |
425 iv = encryption_data["iv"] | 428 iv = encryption_data["iv"] |
440 backend=backends.default_backend(), | 443 backend=backends.default_backend(), |
441 ).encryptor() | 444 ).encryptor() |
442 | 445 |
443 if sat_file.data_cb is not None: | 446 if sat_file.data_cb is not None: |
444 raise exceptions.InternalError( | 447 raise exceptions.InternalError( |
445 f"data_cb was expected to be None, it is set to {sat_file.data_cb}") | 448 f"data_cb was expected to be None, it is set to {sat_file.data_cb}" |
446 | 449 ) |
447 attachment.update({ | 450 |
448 "hash_algo": self._h.ALGO_DEFAULT, | 451 attachment.update( |
449 "hasher": self._h.get_hasher(), | 452 { |
450 "encrypted_hash_algo": self._h.ALGO_DEFAULT, | 453 "hash_algo": self._h.ALGO_DEFAULT, |
451 "encrypted_hasher": self._h.get_hasher(), | 454 "hasher": self._h.get_hasher(), |
452 }) | 455 "encrypted_hash_algo": self._h.ALGO_DEFAULT, |
456 "encrypted_hasher": self._h.get_hasher(), | |
457 } | |
458 ) | |
453 | 459 |
454 # with data_cb we encrypt the file on the fly | 460 # with data_cb we encrypt the file on the fly |
455 sat_file.data_cb = partial( | 461 sat_file.data_cb = partial( |
456 self._encrypt, encryptor=encryptor, attachment=attachment | 462 self._encrypt, encryptor=encryptor, attachment=attachment |
457 ) | 463 ) |