comparison sat/plugins/plugin_sec_aesgcm.py @ 3219:2ba602aef90e

plugin attach, aesgcm: attachments refactoring: attachment handling has been simplified, and now use a "register" method similar as the ones used for download or upload. A default method (for unencrypted messages) will try a simple upload and will copy the links to body. AESGCM plugin has been adapted to be used for encrypted files. If more than one file is sent with AESGCM plugin, they will be split in several messages as current de-facto standard (OMEMO media sharing) doesn't support several files per message.
author Goffi <goffi@goffi.org>
date Wed, 18 Mar 2020 20:25:02 +0100
parents 2c0628f3927e
children 653fa213d2f8
comparison
equal deleted inserted replaced
3218:806a7936a591 3219:2ba602aef90e
40 PLUGIN_INFO = { 40 PLUGIN_INFO = {
41 C.PI_NAME: "AES-GCM", 41 C.PI_NAME: "AES-GCM",
42 C.PI_IMPORT_NAME: "AES-GCM", 42 C.PI_IMPORT_NAME: "AES-GCM",
43 C.PI_TYPE: "SEC", 43 C.PI_TYPE: "SEC",
44 C.PI_PROTOCOLS: ["OMEMO Media sharing"], 44 C.PI_PROTOCOLS: ["OMEMO Media sharing"],
45 C.PI_DEPENDENCIES: ["XEP-0363", "XEP-0384", "DOWNLOAD"], 45 C.PI_DEPENDENCIES: ["XEP-0363", "XEP-0384", "DOWNLOAD", "ATTACH"],
46 C.PI_MAIN: "AESGCM", 46 C.PI_MAIN: "AESGCM",
47 C.PI_HANDLER: "no", 47 C.PI_HANDLER: "no",
48 C.PI_DESCRIPTION: dedent(_("""\ 48 C.PI_DESCRIPTION: dedent(_("""\
49 Implementation of AES-GCM scheme, a way to encrypt files (not official XMPP standard). 49 Implementation of AES-GCM scheme, a way to encrypt files (not official XMPP standard).
50 See https://xmpp.org/extensions/inbox/omemo-media-sharing.html for details 50 See https://xmpp.org/extensions/inbox/omemo-media-sharing.html for details
59 class AESGCM(object): 59 class AESGCM(object):
60 60
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']
65 self._attach = host.plugins["ATTACH"]
64 host.plugins["DOWNLOAD"].registerScheme( 66 host.plugins["DOWNLOAD"].registerScheme(
65 "aesgcm", self.download 67 "aesgcm", self.download
66 ) 68 )
69 self._attach.register(
70 self.canHandleAttachment, self.attach, encrypted=True)
67 host.trigger.add("XEP-0363_upload_size", self._uploadSizeTrigger) 71 host.trigger.add("XEP-0363_upload_size", self._uploadSizeTrigger)
68 host.trigger.add("XEP-0363_upload", self._uploadTrigger) 72 host.trigger.add("XEP-0363_upload", self._uploadTrigger)
69 host.trigger.add("messageReceived", self._messageReceivedTrigger) 73 host.trigger.add("messageReceived", self._messageReceivedTrigger)
70 74
71 async def download(self, client, uri_parsed, dest_path, options): 75 async def download(self, client, uri_parsed, dest_path, options):
125 decryptor=decryptor)) 129 decryptor=decryptor))
126 else: 130 else:
127 d = defer.Deferred() 131 d = defer.Deferred()
128 self.host.plugins["DOWNLOAD"].errbackDownload(file_obj, d, resp) 132 self.host.plugins["DOWNLOAD"].errbackDownload(file_obj, d, resp)
129 return progress_id, d 133 return progress_id, d
134
135 async def canHandleAttachment(self, client, data):
136 try:
137 await self._http_upload.getHTTPUploadEntity(client)
138 except exceptions.NotFound:
139 return False
140 else:
141 return True
142
143 async def _uploadCb(self, client, filepath, filename, options):
144 options['encryption'] = C.ENC_AES_GCM
145 return await self._http_upload.fileHTTPUpload(
146 client=client,
147 filepath=filepath,
148 filename=filename,
149 options=options
150 )
151
152 async def attach(self, client, data):
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
155 # attachments from original message, and send them one by one.
156 # TODO: this is to be removed when a better mechanism is available with OMEMO (now
157 # possible with the 0.4 version of OMEMO, it's possible to encrypt other stanza
158 # elements than body).
159 attachments = data["extra"][C.MESS_KEY_ATTACHMENTS]
160 if not data['message'] or data['message'] == {'': ''}:
161 extra_attachments = attachments[1:]
162 del attachments[1:]
163 await self._attach.uploadFiles(client, data, upload_cb=self._uploadCb)
164 else:
165 # we have a message, we must send first attachment separately
166 extra_attachments = attachments[:]
167 attachments.clear()
168 del data["extra"][C.MESS_KEY_ATTACHMENTS]
169
170 body_elt = next(data["xml"].elements((C.NS_CLIENT, "body")))
171
172 for attachment in attachments:
173 body_elt.addContent(attachment["url"])
174
175 for attachment in extra_attachments:
176 # we send all remaining attachment in a separate message
177 client.sendMessage(
178 to_jid=data['to'],
179 message={'': ''},
180 subject=data['subject'],
181 mess_type=data['type'],
182 extra={C.MESS_KEY_ATTACHMENTS: [attachment]},
183 )
184
185 if ((not data['extra']
186 and (not data['message'] or data['message'] == {'': ''})
187 and not data['subject'])):
188 # nothing left to send, we can cancel the message
189 raise exceptions.CancelError("Cancelled by AESGCM attachment handling")
130 190
131 def onDataDownload(self, data, client, file_obj, decryptor): 191 def onDataDownload(self, data, client, file_obj, decryptor):
132 if file_obj.tell() + len(data) > file_obj.size: 192 if file_obj.tell() + len(data) > file_obj.size:
133 # we're reaching end of file with this bunch of data 193 # we're reaching end of file with this bunch of data
134 # we may still have a last bunch if the tag is incomplete 194 # we may still have a last bunch if the tag is incomplete