Daniel Brötzmann pushed to branch omemo_integration at gajim / gajim
Commits:
-
3e82098d
by wurstsalat at 2022-10-15T13:09:02+02:00
-
615f25f7
by wurstsalat at 2022-10-15T13:09:31+02:00
7 changed files:
- gajim/common/modules/omemo.py
- gajim/common/modules/omemobackend/aes.py
- gajim/common/modules/omemobackend/devices.py
- gajim/common/modules/omemobackend/liteaxolotlstore.py
- gajim/common/modules/omemobackend/state.py
- gajim/common/modules/omemobackend/util.py
- gajim/common/modules/util.py
Changes:
| ... | ... | @@ -16,25 +16,40 @@ |
| 16 | 16 | |
| 17 | 17 | # XEP-0384: OMEMO Encryption
|
| 18 | 18 | |
| 19 | +from __future__ import annotations
|
|
| 20 | + |
|
| 21 | +from typing import Any
|
|
| 22 | +from typing import Optional
|
|
| 23 | + |
|
| 19 | 24 | from pathlib import Path
|
| 20 | 25 | |
| 21 | 26 | from nbxmpp.namespaces import Namespace
|
| 27 | +from nbxmpp.protocol import Message
|
|
| 22 | 28 | from nbxmpp.protocol import NodeProcessed
|
| 29 | +from nbxmpp.protocol import Presence
|
|
| 23 | 30 | from nbxmpp.errors import StanzaError
|
| 24 | 31 | from nbxmpp.const import PresenceType
|
| 25 | 32 | from nbxmpp.const import Affiliation
|
| 33 | +from nbxmpp.structs import MessageProperties
|
|
| 34 | +from nbxmpp.structs import OMEMOMessage
|
|
| 35 | +from nbxmpp.structs import PresenceProperties
|
|
| 26 | 36 | from nbxmpp.structs import StanzaHandler
|
| 27 | 37 | from nbxmpp.modules.omemo import create_omemo_message
|
| 28 | 38 | from nbxmpp.modules.omemo import get_key_transport_message
|
| 29 | 39 | from nbxmpp.modules.util import is_error
|
| 40 | +from nbxmpp.task import Task
|
|
| 30 | 41 | |
| 31 | 42 | from gajim.common import app
|
| 32 | 43 | from gajim.common import configpaths
|
| 33 | 44 | from gajim.common import ged
|
| 45 | +from gajim.common import types
|
|
| 34 | 46 | from gajim.common.structs import OutgoingMessage
|
| 35 | 47 | from gajim.common.const import EncryptionData
|
| 36 | 48 | from gajim.common.const import Trust as GajimTrust
|
| 49 | +from gajim.common.events import MucDiscoUpdate
|
|
| 50 | +from gajim.common.events import SignedIn
|
|
| 37 | 51 | from gajim.common.modules.base import BaseModule
|
| 52 | +from gajim.common.modules.contacts import GroupchatContact
|
|
| 38 | 53 | from gajim.common.modules.util import event_node
|
| 39 | 54 | from gajim.common.modules.util import as_task
|
| 40 | 55 | |
| ... | ... | @@ -85,8 +100,8 @@ class OMEMO(BaseModule): |
| 85 | 100 | 'request_bundle',
|
| 86 | 101 | ]
|
| 87 | 102 | |
| 88 | - def __init__(self, con):
|
|
| 89 | - BaseModule.__init__(self, con)
|
|
| 103 | + def __init__(self, client: types.Client) -> None:
|
|
| 104 | + BaseModule.__init__(self, client)
|
|
| 90 | 105 | |
| 91 | 106 | self.handlers = [
|
| 92 | 107 | StanzaHandler(name='message',
|
| ... | ... | @@ -101,50 +116,46 @@ def __init__(self, con): |
| 101 | 116 | |
| 102 | 117 | self._register_pubsub_handler(self._devicelist_notification_received)
|
| 103 | 118 | self.register_event('signed-in', ged.CORE, self._on_signed_in)
|
| 104 | - self.register_event('muc-disco-update', ged.GUI1, self._on_muc_disco_update)
|
|
| 119 | + self.register_event(
|
|
| 120 | + 'muc-disco-update', ged.GUI1, self._on_muc_disco_update)
|
|
| 105 | 121 | |
| 106 | 122 | self.encryption_name = 'OMEMO'
|
| 107 | 123 | self.allow_groupchat = True
|
| 108 | 124 | |
| 109 | - self._own_jid = self._con.get_own_jid().bare
|
|
| 125 | + self._own_jid = self._client.get_own_jid().bare
|
|
| 110 | 126 | self._backend = self._get_backend()
|
| 111 | 127 | |
| 112 | - self._omemo_groupchats = set()
|
|
| 113 | - self._muc_temp_store = {}
|
|
| 114 | - self._query_for_bundles = []
|
|
| 115 | - self._device_bundle_querys = []
|
|
| 116 | - self._query_for_devicelists = []
|
|
| 128 | + self._omemo_groupchats: set[str] = set()
|
|
| 129 | + self._muc_temp_store: dict[bytes, str] = {}
|
|
| 130 | + self._query_for_bundles: list[str] = []
|
|
| 131 | + self._device_bundle_querys: list[int] = []
|
|
| 132 | + self._query_for_devicelists: list[str] = []
|
|
| 117 | 133 | |
| 118 | - def _on_signed_in(self, event) -> None:
|
|
| 134 | + def _on_signed_in(self, _event: SignedIn) -> None:
|
|
| 119 | 135 | self._log.info('Announce Support after Sign In')
|
| 120 | 136 | self._query_for_bundles = []
|
| 121 | 137 | self.set_bundle()
|
| 122 | 138 | self.request_devicelist()
|
| 123 | 139 | |
| 124 | - def on_muc_disco_update(self, event):
|
|
| 125 | - self._check_if_omemo_capable(event.jid)
|
|
| 126 | - |
|
| 127 | - def get_own_jid(self, stripped=False):
|
|
| 128 | - if stripped:
|
|
| 129 | - return self._con.get_own_jid().bare
|
|
| 130 | - return self._con.get_own_jid()
|
|
| 140 | + def _on_muc_disco_update(self, event: MucDiscoUpdate) -> None:
|
|
| 141 | + self._check_if_omemo_capable(str(event.jid))
|
|
| 131 | 142 | |
| 132 | 143 | @property
|
| 133 | - def backend(self):
|
|
| 144 | + def backend(self) -> OmemoState:
|
|
| 134 | 145 | return self._backend
|
| 135 | 146 | |
| 136 | - def _get_backend(self):
|
|
| 147 | + def _get_backend(self) -> OmemoState:
|
|
| 137 | 148 | data_dir = Path(configpaths.get('MY_DATA'))
|
| 138 | 149 | db_path = data_dir / f'omemo_{self._own_jid}.db'
|
| 139 | 150 | return OmemoState(self._own_jid, db_path, self._account, self)
|
| 140 | 151 | |
| 141 | - def is_omemo_groupchat(self, room_jid):
|
|
| 152 | + def is_omemo_groupchat(self, room_jid: str) -> bool:
|
|
| 142 | 153 | return room_jid in self._omemo_groupchats
|
| 143 | 154 | |
| 144 | 155 | def activate(self):
|
| 145 | 156 | """ Method called when the Plugin is activated in the PluginManager
|
| 146 | 157 | """
|
| 147 | - self._con.get_module('Caps').update_caps()
|
|
| 158 | + self._client.get_module('Caps').update_caps()
|
|
| 148 | 159 | |
| 149 | 160 | if app.account_is_connected(self._account):
|
| 150 | 161 | self._log.info('Announce Support after Plugin Activation')
|
| ... | ... | @@ -161,7 +172,7 @@ def encrypt_message(self, event: OutgoingMessage) -> None: |
| 161 | 172 | if not event.message:
|
| 162 | 173 | return
|
| 163 | 174 | |
| 164 | - omemo_message = self.backend.encrypt(event.jid, event.message)
|
|
| 175 | + omemo_message = self.backend.encrypt(str(event.jid), event.message)
|
|
| 165 | 176 | if omemo_message is None:
|
| 166 | 177 | raise Exception('Encryption error')
|
| 167 | 178 | |
| ... | ... | @@ -179,7 +190,12 @@ def encrypt_message(self, event: OutgoingMessage) -> None: |
| 179 | 190 | |
| 180 | 191 | self._debug_print_stanza(event.stanza)
|
| 181 | 192 | |
| 182 | - def _send_key_transport_message(self, typ, jid, devices):
|
|
| 193 | + def _send_key_transport_message(self,
|
|
| 194 | + typ: str,
|
|
| 195 | + jid: str,
|
|
| 196 | + devices: list[int]
|
|
| 197 | + ) -> None:
|
|
| 198 | + |
|
| 183 | 199 | omemo_message = self.backend.encrypt_key_transport(jid, devices)
|
| 184 | 200 | if omemo_message is None:
|
| 185 | 201 | self._log.warning('Key transport message to %s (%s) failed',
|
| ... | ... | @@ -188,9 +204,14 @@ def _send_key_transport_message(self, typ, jid, devices): |
| 188 | 204 | |
| 189 | 205 | transport_message = get_key_transport_message(typ, jid, omemo_message)
|
| 190 | 206 | self._log.info('Send key transport message %s (%s)', jid, devices)
|
| 191 | - self._con.connection.send(transport_message)
|
|
| 207 | + self._client.connection.send(transport_message)
|
|
| 208 | + |
|
| 209 | + def _message_received(self,
|
|
| 210 | + _client: types.xmppClient,
|
|
| 211 | + stanza: Message,
|
|
| 212 | + properties: MessageProperties
|
|
| 213 | + ) -> None:
|
|
| 192 | 214 | |
| 193 | - def _message_received(self, _con, stanza, properties):
|
|
| 194 | 215 | if not properties.is_omemo:
|
| 195 | 216 | return
|
| 196 | 217 | |
| ... | ... | @@ -211,6 +232,7 @@ def _message_received(self, _con, stanza, properties): |
| 211 | 232 | |
| 212 | 233 | self._log.info('Message received from: %s', from_jid)
|
| 213 | 234 | |
| 235 | + assert isinstance(properties.omemo, OMEMOMessage)
|
|
| 214 | 236 | try:
|
| 215 | 237 | plaintext, fingerprint, trust = self.backend.decrypt_message(
|
| 216 | 238 | properties.omemo, from_jid)
|
| ... | ... | @@ -247,28 +269,35 @@ def _message_received(self, _con, stanza, properties): |
| 247 | 269 | |
| 248 | 270 | prepare_stanza(stanza, plaintext)
|
| 249 | 271 | self._debug_print_stanza(stanza)
|
| 250 | - properties.encrypted = EncryptionData({'name': ENCRYPTION_NAME,
|
|
| 251 | - 'fingerprint': fingerprint,
|
|
| 252 | - 'trust': GajimTrust[trust.name]})
|
|
| 272 | + properties.encrypted = EncryptionData({
|
|
| 273 | + 'name': ENCRYPTION_NAME,
|
|
| 274 | + 'fingerprint': fingerprint,
|
|
| 275 | + 'trust': GajimTrust[trust.name]})
|
|
| 276 | + |
|
| 277 | + def _process_muc_message(self,
|
|
| 278 | + properties: MessageProperties
|
|
| 279 | + ) -> Optional[str]:
|
|
| 253 | 280 | |
| 254 | - def _process_muc_message(self, properties):
|
|
| 255 | 281 | resource = properties.jid.resource
|
| 256 | 282 | if properties.muc_ofrom is not None:
|
| 257 | 283 | # History Message from MUC
|
| 258 | 284 | return properties.muc_ofrom.bare
|
| 259 | 285 | |
| 260 | - contact = self._con.get_module('Contacts').get_contact(properties.jid)
|
|
| 286 | + contact = self._client.get_module('Contacts').get_contact(
|
|
| 287 | + properties.jid)
|
|
| 261 | 288 | if contact.real_jid is not None:
|
| 262 | 289 | return contact.real_jid.bare
|
| 263 | 290 | |
| 291 | + assert isinstance(properties.omemo, OMEMOMessage)
|
|
| 264 | 292 | self._log.info('Groupchat: Last resort trying to find SID in DB')
|
| 265 | 293 | from_jid = self.backend.storage.getJidFromDevice(properties.omemo.sid)
|
| 266 | 294 | if not from_jid:
|
| 267 | - self._log.error("Can't decrypt GroupChat Message from %s", resource)
|
|
| 295 | + self._log.error(
|
|
| 296 | + "Can't decrypt GroupChat Message from %s", resource)
|
|
| 268 | 297 | return
|
| 269 | 298 | return from_jid
|
| 270 | 299 | |
| 271 | - def _process_mam_message(self, properties):
|
|
| 300 | + def _process_mam_message(self, properties: MessageProperties) -> str:
|
|
| 272 | 301 | self._log.info('Message received, archive: %s', properties.mam.archive)
|
| 273 | 302 | if properties.from_muc:
|
| 274 | 303 | self._log.info('MUC MAM Message received')
|
| ... | ... | @@ -279,7 +308,12 @@ def _process_mam_message(self, properties): |
| 279 | 308 | return properties.muc_user.jid.bare
|
| 280 | 309 | return properties.from_.bare
|
| 281 | 310 | |
| 282 | - def _on_muc_user_presence(self, _con, _stanza, properties):
|
|
| 311 | + def _on_muc_user_presence(self,
|
|
| 312 | + _client: types.xmppClient,
|
|
| 313 | + _stanza: Presence,
|
|
| 314 | + properties: PresenceProperties
|
|
| 315 | + ) -> None:
|
|
| 316 | + |
|
| 283 | 317 | if properties.type == PresenceType.ERROR:
|
| 284 | 318 | return
|
| 285 | 319 | |
| ... | ... | @@ -305,7 +339,7 @@ def _on_muc_user_presence(self, _con, _stanza, properties): |
| 305 | 339 | self._log.info('%s not in Roster, query devicelist...', jid)
|
| 306 | 340 | self.request_devicelist(jid)
|
| 307 | 341 | |
| 308 | - def get_affiliation_list(self, room_jid):
|
|
| 342 | + def get_affiliation_list(self, room_jid: str) -> None:
|
|
| 309 | 343 | for affiliation in ('owner', 'admin', 'member'):
|
| 310 | 344 | self._nbxmpp('MUC').get_affiliation(
|
| 311 | 345 | room_jid,
|
| ... | ... | @@ -313,7 +347,7 @@ def get_affiliation_list(self, room_jid): |
| 313 | 347 | callback=self._on_affiliations_received,
|
| 314 | 348 | user_data=room_jid)
|
| 315 | 349 | |
| 316 | - def _on_affiliations_received(self, task):
|
|
| 350 | + def _on_affiliations_received(self, task: Task) -> None:
|
|
| 317 | 351 | room_jid = task.get_user_data()
|
| 318 | 352 | try:
|
| 319 | 353 | result = task.finish()
|
| ... | ... | @@ -330,24 +364,24 @@ def _on_affiliations_received(self, task): |
| 330 | 364 | self._log.info('%s not in Roster, query devicelist...', jid)
|
| 331 | 365 | self.request_devicelist(jid)
|
| 332 | 366 | |
| 333 | - def is_contact_in_roster(self, jid):
|
|
| 367 | + def is_contact_in_roster(self, jid: str) -> bool:
|
|
| 334 | 368 | if jid == self._own_jid:
|
| 335 | 369 | return True
|
| 336 | 370 | |
| 337 | - roster_item = self._con.get_module('Roster').get_item(jid)
|
|
| 371 | + roster_item = self._client.get_module('Roster').get_item(jid)
|
|
| 338 | 372 | if roster_item is None:
|
| 339 | 373 | return False
|
| 340 | 374 | |
| 341 | - contact = self._con.get_module('Contacts').get_contact(jid)
|
|
| 375 | + contact = self._client.get_module('Contacts').get_contact(jid)
|
|
| 342 | 376 | return contact.subscription == 'both'
|
| 343 | 377 | |
| 344 | - def on_room_joined(self, contact):
|
|
| 378 | + def on_room_joined(self, contact: GroupchatContact) -> None:
|
|
| 345 | 379 | jid = str(contact.jid)
|
| 346 | 380 | self._check_if_omemo_capable(jid)
|
| 347 | 381 | if self.is_omemo_groupchat(jid):
|
| 348 | 382 | self.get_affiliation_list(jid)
|
| 349 | 383 | |
| 350 | - def _check_if_omemo_capable(self, jid):
|
|
| 384 | + def _check_if_omemo_capable(self, jid: str) -> None:
|
|
| 351 | 385 | disco_info = app.storage.cache.get_last_disco_info(jid)
|
| 352 | 386 | if disco_info.muc_is_members_only and disco_info.muc_is_nonanonymous:
|
| 353 | 387 | self._log.info('OMEMO room discovered: %s', jid)
|
| ... | ... | @@ -356,7 +390,7 @@ def _check_if_omemo_capable(self, jid): |
| 356 | 390 | self._log.info('OMEMO room removed due to config change: %s', jid)
|
| 357 | 391 | self._omemo_groupchats.discard(jid)
|
| 358 | 392 | |
| 359 | - def _check_for_missing_sessions(self, jid):
|
|
| 393 | + def _check_for_missing_sessions(self, jid: str) -> None:
|
|
| 360 | 394 | devices_without_session = self.backend.devices_without_sessions(jid)
|
| 361 | 395 | for device_id in devices_without_session:
|
| 362 | 396 | if device_id in self._device_bundle_querys:
|
| ... | ... | @@ -364,7 +398,7 @@ def _check_for_missing_sessions(self, jid): |
| 364 | 398 | self._device_bundle_querys.append(device_id)
|
| 365 | 399 | self.request_bundle(jid, device_id)
|
| 366 | 400 | |
| 367 | - def are_keys_missing(self, contact_jid):
|
|
| 401 | + def are_keys_missing(self, contact_jid: str) -> bool:
|
|
| 368 | 402 | """ Checks if devicekeys are missing and queries the
|
| 369 | 403 | bundles
|
| 370 | 404 | |
| ... | ... | @@ -407,12 +441,12 @@ def are_keys_missing(self, contact_jid): |
| 407 | 441 | return False
|
| 408 | 442 | return True
|
| 409 | 443 | |
| 410 | - def set_bundle(self):
|
|
| 444 | + def set_bundle(self) -> None:
|
|
| 411 | 445 | self._nbxmpp('OMEMO').set_bundle(self.backend.bundle,
|
| 412 | 446 | self.backend.own_device)
|
| 413 | 447 | |
| 414 | 448 | @as_task
|
| 415 | - def request_bundle(self, jid, device_id):
|
|
| 449 | + def request_bundle(self, jid: str, device_id: int):
|
|
| 416 | 450 | _task = yield
|
| 417 | 451 | |
| 418 | 452 | self._log.info('Fetch device bundle %s %s', device_id, jid)
|
| ... | ... | @@ -437,19 +471,20 @@ def request_bundle(self, jid, device_id): |
| 437 | 471 | if ctrl:
|
| 438 | 472 | app.ged.raise_event(OMEMONewFingerprint(chat_control=ctrl))
|
| 439 | 473 | |
| 440 | - def set_devicelist(self, devicelist=None):
|
|
| 441 | - devicelist_ = set([self.backend.own_device])
|
|
| 474 | + def set_devicelist(self, devicelist: Optional[list[int]] = None) -> None:
|
|
| 475 | + devicelist_: set[int] = set([self.backend.own_device])
|
|
| 442 | 476 | if devicelist is not None:
|
| 443 | 477 | devicelist_.update(devicelist)
|
| 444 | 478 | self._log.info('Publishing own devicelist: %s', devicelist_)
|
| 445 | 479 | self._nbxmpp('OMEMO').set_devicelist(devicelist_)
|
| 446 | 480 | |
| 447 | - def clear_devicelist(self):
|
|
| 448 | - self.backend.update_devicelist(self._own_jid, [self.backend.own_device])
|
|
| 481 | + def clear_devicelist(self) -> None:
|
|
| 482 | + self.backend.update_devicelist(
|
|
| 483 | + self._own_jid, [self.backend.own_device])
|
|
| 449 | 484 | self.set_devicelist()
|
| 450 | 485 | |
| 451 | 486 | @as_task
|
| 452 | - def request_devicelist(self, jid=None):
|
|
| 487 | + def request_devicelist(self, jid: Optional[str] = None):
|
|
| 453 | 488 | _task = yield
|
| 454 | 489 | |
| 455 | 490 | if jid is None:
|
| ... | ... | @@ -468,7 +503,12 @@ def request_devicelist(self, jid=None): |
| 468 | 503 | self._process_devicelist_update(jid, devicelist)
|
| 469 | 504 | |
| 470 | 505 | @event_node(Namespace.OMEMO_TEMP_DL)
|
| 471 | - def _devicelist_notification_received(self, _con, _stanza, properties):
|
|
| 506 | + def _devicelist_notification_received(self,
|
|
| 507 | + _client: types.xmppClient,
|
|
| 508 | + _stanza: Message,
|
|
| 509 | + properties: MessageProperties
|
|
| 510 | + ) -> None:
|
|
| 511 | + |
|
| 472 | 512 | if properties.pubsub_event.retracted:
|
| 473 | 513 | return
|
| 474 | 514 | |
| ... | ... | @@ -476,8 +516,12 @@ def _devicelist_notification_received(self, _con, _stanza, properties): |
| 476 | 516 | |
| 477 | 517 | self._process_devicelist_update(str(properties.jid), devicelist)
|
| 478 | 518 | |
| 479 | - def _process_devicelist_update(self, jid, devicelist):
|
|
| 480 | - own_devices = jid is None or self._con.get_own_jid().bare_match(jid)
|
|
| 519 | + def _process_devicelist_update(self,
|
|
| 520 | + jid: str,
|
|
| 521 | + devicelist: list[int]
|
|
| 522 | + ) -> None:
|
|
| 523 | + |
|
| 524 | + own_devices = jid is None or self._client.get_own_jid().bare_match(jid)
|
|
| 481 | 525 | if own_devices:
|
| 482 | 526 | jid = self._own_jid
|
| 483 | 527 | |
| ... | ... | @@ -496,7 +540,7 @@ def _process_devicelist_update(self, jid, devicelist): |
| 496 | 540 | |
| 497 | 541 | self._check_for_missing_sessions(jid)
|
| 498 | 542 | |
| 499 | - def _debug_print_stanza(self, stanza):
|
|
| 543 | + def _debug_print_stanza(self, stanza: Any) -> None:
|
|
| 500 | 544 | stanzastr = '\n' + stanza.__str__(fancy=True)
|
| 501 | 545 | stanzastr = stanzastr[0:-1]
|
| 502 | 546 | self._log.debug(stanzastr) |
| ... | ... | @@ -14,10 +14,13 @@ |
| 14 | 14 | # You should have received a copy of the GNU General Public License
|
| 15 | 15 | # along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
| 16 | 16 | |
| 17 | +from __future__ import annotations
|
|
| 18 | + |
|
| 19 | +from typing import NamedTuple
|
|
| 20 | +from typing import Union
|
|
| 17 | 21 | |
| 18 | 22 | import os
|
| 19 | 23 | import logging
|
| 20 | -from collections import namedtuple
|
|
| 21 | 24 | |
| 22 | 25 | from cryptography.hazmat.primitives.ciphers import Cipher
|
| 23 | 26 | from cryptography.hazmat.primitives.ciphers import algorithms
|
| ... | ... | @@ -26,12 +29,16 @@ |
| 26 | 29 | |
| 27 | 30 | log = logging.getLogger('gajim.p.omemo')
|
| 28 | 31 | |
| 29 | -EncryptionResult = namedtuple('EncryptionResult', 'payload key iv')
|
|
| 30 | - |
|
| 31 | 32 | IV_SIZE = 12
|
| 32 | 33 | |
| 33 | 34 | |
| 34 | -def _decrypt(key, iv, tag, data):
|
|
| 35 | +class EncryptionResult(NamedTuple):
|
|
| 36 | + payload: bytes
|
|
| 37 | + key: bytes
|
|
| 38 | + iv: bytes
|
|
| 39 | + |
|
| 40 | + |
|
| 41 | +def _decrypt(key: bytes, iv: bytes, tag: bytes, data: bytes) -> bytes:
|
|
| 35 | 42 | decryptor = Cipher(
|
| 36 | 43 | algorithms.AES(key),
|
| 37 | 44 | GCM(iv, tag=tag),
|
| ... | ... | @@ -39,7 +46,7 @@ def _decrypt(key, iv, tag, data): |
| 39 | 46 | return decryptor.update(data) + decryptor.finalize()
|
| 40 | 47 | |
| 41 | 48 | |
| 42 | -def aes_decrypt(_key, iv, payload):
|
|
| 49 | +def aes_decrypt(_key: bytes, iv: bytes, payload: bytes) -> str:
|
|
| 43 | 50 | if len(_key) >= 32:
|
| 44 | 51 | # XEP-0384
|
| 45 | 52 | log.debug('XEP Compliant Key/Tag')
|
| ... | ... | @@ -56,13 +63,17 @@ def aes_decrypt(_key, iv, payload): |
| 56 | 63 | return _decrypt(key, iv, tag, data).decode()
|
| 57 | 64 | |
| 58 | 65 | |
| 59 | -def aes_decrypt_file(key, iv, payload):
|
|
| 66 | +def aes_decrypt_file(key: bytes, iv: bytes, payload: bytes) -> bytes:
|
|
| 60 | 67 | data = payload[:-16]
|
| 61 | 68 | tag = payload[-16:]
|
| 62 | 69 | return _decrypt(key, iv, tag, data)
|
| 63 | 70 | |
| 64 | 71 | |
| 65 | -def _encrypt(data, key_size, iv_size=IV_SIZE):
|
|
| 72 | +def _encrypt(data: Union[str, bytes],
|
|
| 73 | + key_size: int,
|
|
| 74 | + iv_size: int = IV_SIZE
|
|
| 75 | + ) -> tuple[bytes, bytes, bytes, bytes]:
|
|
| 76 | + |
|
| 66 | 77 | if isinstance(data, str):
|
| 67 | 78 | data = data.encode()
|
| 68 | 79 | key = os.urandom(key_size)
|
| ... | ... | @@ -76,21 +87,21 @@ def _encrypt(data, key_size, iv_size=IV_SIZE): |
| 76 | 87 | return key, iv, encryptor.tag, payload
|
| 77 | 88 | |
| 78 | 89 | |
| 79 | -def aes_encrypt(plaintext):
|
|
| 90 | +def aes_encrypt(plaintext: str) -> EncryptionResult:
|
|
| 80 | 91 | key, iv, tag, payload = _encrypt(plaintext, 16)
|
| 81 | 92 | key += tag
|
| 82 | 93 | return EncryptionResult(payload=payload, key=key, iv=iv)
|
| 83 | 94 | |
| 84 | 95 | |
| 85 | -def aes_encrypt_file(data):
|
|
| 96 | +def aes_encrypt_file(data: bytes) -> EncryptionResult:
|
|
| 86 | 97 | key, iv, tag, payload, = _encrypt(data, 32)
|
| 87 | 98 | payload += tag
|
| 88 | 99 | return EncryptionResult(payload=payload, key=key, iv=iv)
|
| 89 | 100 | |
| 90 | 101 | |
| 91 | -def get_new_key():
|
|
| 102 | +def get_new_key() -> bytes:
|
|
| 92 | 103 | return os.urandom(16)
|
| 93 | 104 | |
| 94 | 105 | |
| 95 | -def get_new_iv():
|
|
| 106 | +def get_new_iv() -> bytes:
|
|
| 96 | 107 | return os.urandom(IV_SIZE) |
| ... | ... | @@ -14,17 +14,19 @@ |
| 14 | 14 | # You should have received a copy of the GNU General Public License
|
| 15 | 15 | # along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
| 16 | 16 | |
| 17 | +from __future__ import annotations
|
|
| 18 | + |
|
| 17 | 19 | from collections import defaultdict
|
| 18 | 20 | |
| 19 | 21 | from gajim.common import app
|
| 20 | 22 | |
| 21 | -from gajim.common.modules.util import UNACKNOWLEDGED_COUNT
|
|
| 23 | +from gajim.common.modules.omemobackend.util import UNACKNOWLEDGED_COUNT
|
|
| 22 | 24 | |
| 23 | 25 | |
| 24 | 26 | class DeviceManager:
|
| 25 | - def __init__(self):
|
|
| 26 | - self.__device_store = defaultdict(set)
|
|
| 27 | - self.__muc_member_store = defaultdict(set)
|
|
| 27 | + def __init__(self) -> None:
|
|
| 28 | + self.__device_store: dict[str, set[int]] = defaultdict(set)
|
|
| 29 | + self.__muc_member_store: dict[str, set[str]] = defaultdict(set)
|
|
| 28 | 30 | |
| 29 | 31 | reg_id = self._storage.getLocalRegistrationId()
|
| 30 | 32 | if reg_id is None:
|
| ... | ... | @@ -38,7 +40,7 @@ def __init__(self): |
| 38 | 40 | self._log.info('Load device from storage: %s - %s', jid, device)
|
| 39 | 41 | self.add_device(jid, device)
|
| 40 | 42 | |
| 41 | - def update_devicelist(self, jid, devicelist):
|
|
| 43 | + def update_devicelist(self, jid: str, devicelist: list[int]) -> None:
|
|
| 42 | 44 | for device in list(devicelist):
|
| 43 | 45 | if device == self.own_device:
|
| 44 | 46 | continue
|
| ... | ... | @@ -52,35 +54,39 @@ def update_devicelist(self, jid, devicelist): |
| 52 | 54 | self._log.info('Saved devices for %s', jid)
|
| 53 | 55 | self._storage.setActiveState(jid, devicelist)
|
| 54 | 56 | |
| 55 | - def add_muc_member(self, room_jid, jid):
|
|
| 57 | + def add_muc_member(self, room_jid: str, jid: str) -> None:
|
|
| 56 | 58 | self._log.info('Saved MUC member %s %s', room_jid, jid)
|
| 57 | 59 | self.__muc_member_store[room_jid].add(jid)
|
| 58 | 60 | |
| 59 | - def remove_muc_member(self, room_jid, jid):
|
|
| 61 | + def remove_muc_member(self, room_jid: str, jid: str) -> None:
|
|
| 60 | 62 | self._log.info('Removed MUC member %s %s', room_jid, jid)
|
| 61 | 63 | self.__muc_member_store[room_jid].discard(jid)
|
| 62 | 64 | |
| 63 | - def get_muc_members(self, room_jid, without_self=True):
|
|
| 65 | + def get_muc_members(self,
|
|
| 66 | + room_jid: str,
|
|
| 67 | + without_self: bool = True
|
|
| 68 | + ) -> set[str]:
|
|
| 69 | + |
|
| 64 | 70 | members = set(self.__muc_member_store[room_jid])
|
| 65 | 71 | if without_self:
|
| 66 | 72 | members.discard(self._own_jid)
|
| 67 | 73 | return members
|
| 68 | 74 | |
| 69 | - def add_device(self, jid, device):
|
|
| 75 | + def add_device(self, jid: str, device: int) -> None:
|
|
| 70 | 76 | self.__device_store[jid].add(device)
|
| 71 | 77 | |
| 72 | - def remove_device(self, jid, device):
|
|
| 78 | + def remove_device(self, jid: str, device: int) -> None:
|
|
| 73 | 79 | self.__device_store[jid].discard(device)
|
| 74 | 80 | self._storage.setInactive(jid, device)
|
| 75 | 81 | |
| 76 | - def get_devices(self, jid, without_self=False):
|
|
| 82 | + def get_devices(self, jid: str, without_self: bool = False) -> set[int]:
|
|
| 77 | 83 | devices = set(self.__device_store[jid])
|
| 78 | 84 | if without_self:
|
| 79 | 85 | devices.discard(self.own_device)
|
| 80 | 86 | return devices
|
| 81 | 87 | |
| 82 | - def get_devices_for_encryption(self, jid):
|
|
| 83 | - devices_for_encryption = []
|
|
| 88 | + def get_devices_for_encryption(self, jid: str) -> set[tuple[str, int]]:
|
|
| 89 | + devices_for_encryption: list[tuple[str, int]] = []
|
|
| 84 | 90 | |
| 85 | 91 | client = app.get_client(self._account)
|
| 86 | 92 | contact = client.get_module('Contacts').get_contact(jid)
|
| ... | ... | @@ -95,22 +101,25 @@ def get_devices_for_encryption(self, jid): |
| 95 | 101 | devices_for_encryption += self._get_own_devices_for_encryption()
|
| 96 | 102 | return set(devices_for_encryption)
|
| 97 | 103 | |
| 98 | - def _get_devices_for_muc_encryption(self, jid):
|
|
| 99 | - devices_for_encryption = []
|
|
| 104 | + def _get_devices_for_muc_encryption(self,
|
|
| 105 | + jid: str
|
|
| 106 | + ) -> list[tuple[str, int]]:
|
|
| 107 | + |
|
| 108 | + devices_for_encryption: list[tuple[str, int]] = []
|
|
| 100 | 109 | for jid_ in self.__muc_member_store[jid]:
|
| 101 | 110 | devices_for_encryption += self._get_devices_for_encryption(jid_)
|
| 102 | 111 | return devices_for_encryption
|
| 103 | 112 | |
| 104 | - def _get_own_devices_for_encryption(self):
|
|
| 105 | - devices_for_encryption = []
|
|
| 113 | + def _get_own_devices_for_encryption(self) -> list[tuple[str, int]]:
|
|
| 114 | + devices_for_encryption: list[tuple[str, int]] = []
|
|
| 106 | 115 | own_devices = self.get_devices(self._own_jid, without_self=True)
|
| 107 | 116 | for device in own_devices:
|
| 108 | 117 | if self._storage.isTrusted(self._own_jid, device):
|
| 109 | 118 | devices_for_encryption.append((self._own_jid, device))
|
| 110 | 119 | return devices_for_encryption
|
| 111 | 120 | |
| 112 | - def _get_devices_for_encryption(self, jid):
|
|
| 113 | - devices_for_encryption = []
|
|
| 121 | + def _get_devices_for_encryption(self, jid: str) -> list[tuple[str, int]]:
|
|
| 122 | + devices_for_encryption: list[tuple[str, int]] = []
|
|
| 114 | 123 | devices = self.get_devices(jid)
|
| 115 | 124 | |
| 116 | 125 | for device in devices:
|
| ... | ... | @@ -119,18 +128,18 @@ def _get_devices_for_encryption(self, jid): |
| 119 | 128 | return devices_for_encryption
|
| 120 | 129 | |
| 121 | 130 | @property
|
| 122 | - def own_device(self):
|
|
| 131 | + def own_device(self) -> int:
|
|
| 123 | 132 | return self.__own_device
|
| 124 | 133 | |
| 125 | 134 | @property
|
| 126 | - def devices_for_publish(self):
|
|
| 135 | + def devices_for_publish(self) -> set[int]:
|
|
| 127 | 136 | devices = self.get_devices(self._own_jid)
|
| 128 | 137 | if self.own_device not in devices:
|
| 129 | 138 | devices.add(self.own_device)
|
| 130 | 139 | return devices
|
| 131 | 140 | |
| 132 | 141 | @property
|
| 133 | - def is_own_device_published(self):
|
|
| 142 | + def is_own_device_published(self) -> bool:
|
|
| 134 | 143 | return self.own_device in self.get_devices(self._own_jid)
|
| 135 | 144 | |
| 136 | 145 |
| ... | ... | @@ -15,9 +15,16 @@ |
| 15 | 15 | # You should have received a copy of the GNU General Public License
|
| 16 | 16 | # along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
| 17 | 17 | |
| 18 | +from __future__ import annotations
|
|
| 19 | + |
|
| 20 | +from typing import Any
|
|
| 21 | +from typing import NamedTuple
|
|
| 22 | +from typing import Optional
|
|
| 23 | + |
|
| 18 | 24 | import time
|
| 19 | 25 | import sqlite3
|
| 20 | 26 | from collections import namedtuple
|
| 27 | +from pathlib import Path
|
|
| 21 | 28 | |
| 22 | 29 | from axolotl.state.axolotlstore import AxolotlStore
|
| 23 | 30 | from axolotl.state.signedprekeyrecord import SignedPreKeyRecord
|
| ... | ... | @@ -26,28 +33,29 @@ |
| 26 | 33 | from axolotl.invalidkeyidexception import InvalidKeyIdException
|
| 27 | 34 | from axolotl.ecc.djbec import DjbECPrivateKey
|
| 28 | 35 | from axolotl.ecc.djbec import DjbECPublicKey
|
| 36 | +from axolotl.identitykey import IdentityKey
|
|
| 29 | 37 | from axolotl.identitykeypair import IdentityKeyPair
|
| 30 | 38 | from axolotl.util.medium import Medium
|
| 31 | 39 | from axolotl.util.keyhelper import KeyHelper
|
| 32 | 40 | |
| 33 | 41 | from gajim.common import app
|
| 34 | - |
|
| 35 | -from gajim.common.modules.util import Trust
|
|
| 36 | -from gajim.common.modules.util import IdentityKeyExtended
|
|
| 37 | -from gajim.common.modules.util import DEFAULT_PREKEY_AMOUNT
|
|
| 42 | +from gajim.common.modules.omemobackend.util import Trust
|
|
| 43 | +from gajim.common.modules.omemobackend.util import IdentityKeyExtended
|
|
| 44 | +from gajim.common.modules.omemobackend.util import DEFAULT_PREKEY_AMOUNT
|
|
| 45 | +from gajim.common.modules.util import LogAdapter
|
|
| 38 | 46 | |
| 39 | 47 | |
| 40 | 48 | def _convert_to_string(text):
|
| 41 | 49 | return text.decode()
|
| 42 | 50 | |
| 43 | 51 | |
| 44 | -def _convert_identity_key(key):
|
|
| 52 | +def _convert_identity_key(key: bytes) -> Optional[IdentityKeyExtended]:
|
|
| 45 | 53 | if not key:
|
| 46 | 54 | return
|
| 47 | 55 | return IdentityKeyExtended(DjbECPublicKey(key[1:]))
|
| 48 | 56 | |
| 49 | 57 | |
| 50 | -def _convert_record(record):
|
|
| 58 | +def _convert_record(record: bytes) -> SessionRecord:
|
|
| 51 | 59 | return SessionRecord(serialized=record)
|
| 52 | 60 | |
| 53 | 61 | |
| ... | ... | @@ -56,7 +64,7 @@ def _convert_record(record): |
| 56 | 64 | |
| 57 | 65 | |
| 58 | 66 | class LiteAxolotlStore(AxolotlStore):
|
| 59 | - def __init__(self, account: str, db_path, log):
|
|
| 67 | + def __init__(self, account: str, db_path: Path, log: LogAdapter) -> None:
|
|
| 60 | 68 | self._log = log
|
| 61 | 69 | self._account = account
|
| 62 | 70 | self._con = sqlite3.connect(db_path,
|
| ... | ... | @@ -78,13 +86,16 @@ def __init__(self, account: str, db_path, log): |
| 78 | 86 | self._log.info("Generating OMEMO keys")
|
| 79 | 87 | self._generate_axolotl_keys()
|
| 80 | 88 | |
| 81 | - def _is_blind_trust_enabled(self):
|
|
| 89 | + def _is_blind_trust_enabled(self) -> bool:
|
|
| 82 | 90 | return app.settings.get_account_setting(self._account,
|
| 83 | 91 | 'omemo_blind_trust')
|
| 84 | 92 | |
| 85 | 93 | @staticmethod
|
| 86 | - def _namedtuple_factory(cursor, row):
|
|
| 87 | - fields = []
|
|
| 94 | + def _namedtuple_factory(cursor: sqlite3.Cursor,
|
|
| 95 | + row: tuple[Any, ...]
|
|
| 96 | + ) -> NamedTuple:
|
|
| 97 | + |
|
| 98 | + fields: list[str] = []
|
|
| 88 | 99 | for col in cursor.description:
|
| 89 | 100 | if col[0] == '_id':
|
| 90 | 101 | fields.append('id')
|
| ... | ... | @@ -98,7 +109,7 @@ def _namedtuple_factory(cursor, row): |
| 98 | 109 | fields.append(col[0])
|
| 99 | 110 | return namedtuple("Row", fields)(*row)
|
| 100 | 111 | |
| 101 | - def _generate_axolotl_keys(self):
|
|
| 112 | + def _generate_axolotl_keys(self) -> None:
|
|
| 102 | 113 | identity_key_pair = KeyHelper.generateIdentityKeyPair()
|
| 103 | 114 | registration_id = KeyHelper.getRandomSequence(2147483647)
|
| 104 | 115 | pre_keys = KeyHelper.generatePreKeys(
|
| ... | ... | @@ -114,10 +125,10 @@ def _generate_axolotl_keys(self): |
| 114 | 125 | for pre_key in pre_keys:
|
| 115 | 126 | self.storePreKey(pre_key.getId(), pre_key)
|
| 116 | 127 | |
| 117 | - def user_version(self):
|
|
| 128 | + def user_version(self) -> int:
|
|
| 118 | 129 | return self._con.execute('PRAGMA user_version').fetchone()[0]
|
| 119 | 130 | |
| 120 | - def createDb(self):
|
|
| 131 | + def createDb(self) -> None:
|
|
| 121 | 132 | if self.user_version() == 0:
|
| 122 | 133 | |
| 123 | 134 | create_tables = '''
|
| ... | ... | @@ -159,7 +170,7 @@ def createDb(self): |
| 159 | 170 | """ % (create_tables)
|
| 160 | 171 | self._con.executescript(create_db_sql)
|
| 161 | 172 | |
| 162 | - def migrateDb(self):
|
|
| 173 | + def migrateDb(self) -> None:
|
|
| 163 | 174 | """ Migrates the DB
|
| 164 | 175 | """
|
| 165 | 176 | |
| ... | ... | @@ -377,7 +388,7 @@ def migrateDb(self): |
| 377 | 388 | self._con.execute('PRAGMA user_version=12')
|
| 378 | 389 | self._con.commit()
|
| 379 | 390 | |
| 380 | - def loadSignedPreKey(self, signedPreKeyId):
|
|
| 391 | + def loadSignedPreKey(self, signedPreKeyId: int) -> SignedPreKeyRecord:
|
|
| 381 | 392 | query = 'SELECT record FROM signed_prekeys WHERE prekey_id = ?'
|
| 382 | 393 | result = self._con.execute(query, (signedPreKeyId, )).fetchone()
|
| 383 | 394 | if result is None:
|
| ... | ... | @@ -385,39 +396,43 @@ def loadSignedPreKey(self, signedPreKeyId): |
| 385 | 396 | signedPreKeyId)
|
| 386 | 397 | return SignedPreKeyRecord(serialized=result.record)
|
| 387 | 398 | |
| 388 | - def loadSignedPreKeys(self):
|
|
| 399 | + def loadSignedPreKeys(self) -> list[SignedPreKeyRecord]:
|
|
| 389 | 400 | query = 'SELECT record FROM signed_prekeys'
|
| 390 | 401 | results = self._con.execute(query).fetchall()
|
| 391 | 402 | return [SignedPreKeyRecord(serialized=row.record) for row in results]
|
| 392 | 403 | |
| 393 | - def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord):
|
|
| 404 | + def storeSignedPreKey(self,
|
|
| 405 | + signedPreKeyId: int,
|
|
| 406 | + signedPreKeyRecord: SignedPreKeyRecord
|
|
| 407 | + ) -> None:
|
|
| 408 | + |
|
| 394 | 409 | query = 'INSERT INTO signed_prekeys (prekey_id, record) VALUES(?,?)'
|
| 395 | 410 | self._con.execute(query, (signedPreKeyId,
|
| 396 | 411 | signedPreKeyRecord.serialize()))
|
| 397 | 412 | self._con.commit()
|
| 398 | 413 | |
| 399 | - def containsSignedPreKey(self, signedPreKeyId):
|
|
| 414 | + def containsSignedPreKey(self, signedPreKeyId: int) -> bool:
|
|
| 400 | 415 | query = 'SELECT record FROM signed_prekeys WHERE prekey_id = ?'
|
| 401 | 416 | result = self._con.execute(query, (signedPreKeyId,)).fetchone()
|
| 402 | 417 | return result is not None
|
| 403 | 418 | |
| 404 | - def removeSignedPreKey(self, signedPreKeyId):
|
|
| 419 | + def removeSignedPreKey(self, signedPreKeyId: int) -> None:
|
|
| 405 | 420 | query = 'DELETE FROM signed_prekeys WHERE prekey_id = ?'
|
| 406 | 421 | self._con.execute(query, (signedPreKeyId,))
|
| 407 | 422 | self._con.commit()
|
| 408 | 423 | |
| 409 | - def getNextSignedPreKeyId(self):
|
|
| 424 | + def getNextSignedPreKeyId(self) -> int:
|
|
| 410 | 425 | result = self.getCurrentSignedPreKeyId()
|
| 411 | 426 | if result is None:
|
| 412 | 427 | return 1 # StartId if no SignedPreKeys exist
|
| 413 | 428 | return (result % (Medium.MAX_VALUE - 1)) + 1
|
| 414 | 429 | |
| 415 | - def getCurrentSignedPreKeyId(self):
|
|
| 430 | + def getCurrentSignedPreKeyId(self) -> Optional[int]:
|
|
| 416 | 431 | query = 'SELECT MAX(prekey_id) FROM signed_prekeys'
|
| 417 | 432 | result = self._con.execute(query).fetchone()
|
| 418 | 433 | return result.max_prekey_id if result is not None else None
|
| 419 | 434 | |
| 420 | - def getSignedPreKeyTimestamp(self, signedPreKeyId):
|
|
| 435 | + def getSignedPreKeyTimestamp(self, signedPreKeyId: int) -> int:
|
|
| 421 | 436 | query = '''SELECT strftime('%s', timestamp) FROM
|
| 422 | 437 | signed_prekeys WHERE prekey_id = ?'''
|
| 423 | 438 | |
| ... | ... | @@ -428,19 +443,19 @@ def getSignedPreKeyTimestamp(self, signedPreKeyId): |
| 428 | 443 | |
| 429 | 444 | return result.formated_time
|
| 430 | 445 | |
| 431 | - def removeOldSignedPreKeys(self, timestamp):
|
|
| 446 | + def removeOldSignedPreKeys(self, timestamp: int) -> None:
|
|
| 432 | 447 | query = '''DELETE FROM signed_prekeys
|
| 433 | 448 | WHERE timestamp < datetime(?, "unixepoch")'''
|
| 434 | 449 | self._con.execute(query, (timestamp,))
|
| 435 | 450 | self._con.commit()
|
| 436 | 451 | |
| 437 | - def loadSession(self, recipientId, deviceId):
|
|
| 452 | + def loadSession(self, recipientId: str, deviceId: int) -> SessionRecord:
|
|
| 438 | 453 | query = '''SELECT record as "record [session_record]"
|
| 439 | 454 | FROM sessions WHERE recipient_id = ? AND device_id = ?'''
|
| 440 | 455 | result = self._con.execute(query, (recipientId, deviceId)).fetchone()
|
| 441 | 456 | return result.record if result is not None else SessionRecord()
|
| 442 | 457 | |
| 443 | - def getJidFromDevice(self, device_id):
|
|
| 458 | + def getJidFromDevice(self, device_id: int) -> Optional[str]:
|
|
| 444 | 459 | query = '''SELECT recipient_id
|
| 445 | 460 | FROM sessions WHERE device_id = ?'''
|
| 446 | 461 | result = self._con.execute(query, (device_id, )).fetchone()
|
| ... | ... | @@ -451,7 +466,12 @@ def getActiveDeviceTuples(self): |
| 451 | 466 | FROM sessions WHERE active = 1'''
|
| 452 | 467 | return self._con.execute(query).fetchall()
|
| 453 | 468 | |
| 454 | - def storeSession(self, recipientId, deviceId, sessionRecord):
|
|
| 469 | + def storeSession(self,
|
|
| 470 | + recipientId: str,
|
|
| 471 | + deviceId: int,
|
|
| 472 | + sessionRecord: SessionRecord
|
|
| 473 | + ) -> None:
|
|
| 474 | + |
|
| 455 | 475 | query = '''INSERT INTO sessions(recipient_id, device_id, record)
|
| 456 | 476 | VALUES(?,?,?)'''
|
| 457 | 477 | try:
|
| ... | ... | @@ -467,24 +487,24 @@ def storeSession(self, recipientId, deviceId, sessionRecord): |
| 467 | 487 | |
| 468 | 488 | self._con.commit()
|
| 469 | 489 | |
| 470 | - def containsSession(self, recipientId, deviceId):
|
|
| 490 | + def containsSession(self, recipientId: str, deviceId: int) -> bool:
|
|
| 471 | 491 | query = '''SELECT record FROM sessions
|
| 472 | 492 | WHERE recipient_id = ? AND device_id = ?'''
|
| 473 | 493 | result = self._con.execute(query, (recipientId, deviceId)).fetchone()
|
| 474 | 494 | return result is not None
|
| 475 | 495 | |
| 476 | - def deleteSession(self, recipientId, deviceId):
|
|
| 496 | + def deleteSession(self, recipientId: str, deviceId: int) -> None:
|
|
| 477 | 497 | self._log.info('Delete session for %s %s', recipientId, deviceId)
|
| 478 | 498 | query = "DELETE FROM sessions WHERE recipient_id = ? AND device_id = ?"
|
| 479 | 499 | self._con.execute(query, (recipientId, deviceId))
|
| 480 | 500 | self._con.commit()
|
| 481 | 501 | |
| 482 | - def deleteAllSessions(self, recipientId):
|
|
| 502 | + def deleteAllSessions(self, recipientId: str) -> None:
|
|
| 483 | 503 | query = 'DELETE FROM sessions WHERE recipient_id = ?'
|
| 484 | 504 | self._con.execute(query, (recipientId,))
|
| 485 | 505 | self._con.commit()
|
| 486 | 506 | |
| 487 | - def getSessionsFromJid(self, recipientId):
|
|
| 507 | + def getSessionsFromJid(self, recipientId: str):
|
|
| 488 | 508 | query = '''SELECT recipient_id,
|
| 489 | 509 | device_id,
|
| 490 | 510 | record as "record [session_record]",
|
| ... | ... | @@ -492,7 +512,7 @@ def getSessionsFromJid(self, recipientId): |
| 492 | 512 | FROM sessions WHERE recipient_id = ?'''
|
| 493 | 513 | return self._con.execute(query, (recipientId,)).fetchall()
|
| 494 | 514 | |
| 495 | - def getSessionsFromJids(self, recipientIds):
|
|
| 515 | + def getSessionsFromJids(self, recipientIds: list[str]):
|
|
| 496 | 516 | query = '''SELECT recipient_id,
|
| 497 | 517 | device_id,
|
| 498 | 518 | record as "record [session_record]",
|
| ... | ... | @@ -502,7 +522,7 @@ def getSessionsFromJids(self, recipientIds): |
| 502 | 522 | ', '.join(['?'] * len(recipientIds)))
|
| 503 | 523 | return self._con.execute(query, recipientIds).fetchall()
|
| 504 | 524 | |
| 505 | - def setActiveState(self, jid, devicelist):
|
|
| 525 | + def setActiveState(self, jid: str, devicelist: list[int]) -> None:
|
|
| 506 | 526 | query = '''UPDATE sessions SET active = 1
|
| 507 | 527 | WHERE recipient_id = ? AND device_id IN ({})'''.format(
|
| 508 | 528 | ', '.join(['?'] * len(devicelist)))
|
| ... | ... | @@ -514,24 +534,27 @@ def setActiveState(self, jid, devicelist): |
| 514 | 534 | self._con.execute(query, (jid,) + tuple(devicelist))
|
| 515 | 535 | self._con.commit()
|
| 516 | 536 | |
| 517 | - def setInactive(self, jid, device_id):
|
|
| 537 | + def setInactive(self, jid: str, device_id: int) -> None:
|
|
| 518 | 538 | query = '''UPDATE sessions SET active = 0
|
| 519 | 539 | WHERE recipient_id = ? AND device_id = ?'''
|
| 520 | 540 | self._con.execute(query, (jid, device_id))
|
| 521 | 541 | self._con.commit()
|
| 522 | 542 | |
| 523 | - def getInactiveSessionsKeys(self, recipientId):
|
|
| 543 | + def getInactiveSessionsKeys(self,
|
|
| 544 | + recipientId: str
|
|
| 545 | + ) -> list[IdentityKeyExtended]:
|
|
| 546 | + |
|
| 524 | 547 | query = '''SELECT record as "record [session_record]" FROM sessions
|
| 525 | 548 | WHERE active = 0 AND recipient_id = ?'''
|
| 526 | 549 | results = self._con.execute(query, (recipientId,)).fetchall()
|
| 527 | 550 | |
| 528 | - keys = []
|
|
| 551 | + keys: list[IdentityKeyExtended] = []
|
|
| 529 | 552 | for result in results:
|
| 530 | 553 | key = result.record.getSessionState().getRemoteIdentityKey()
|
| 531 | 554 | keys.append(IdentityKeyExtended(key.getPublicKey()))
|
| 532 | 555 | return keys
|
| 533 | 556 | |
| 534 | - def loadPreKey(self, preKeyId):
|
|
| 557 | + def loadPreKey(self, preKeyId: int) -> PreKeyRecord:
|
|
| 535 | 558 | query = '''SELECT record FROM prekeys WHERE prekey_id = ?'''
|
| 536 | 559 | |
| 537 | 560 | result = self._con.execute(query, (preKeyId,)).fetchone()
|
| ... | ... | @@ -539,41 +562,41 @@ def loadPreKey(self, preKeyId): |
| 539 | 562 | raise Exception("No such prekeyRecord!")
|
| 540 | 563 | return PreKeyRecord(serialized=result.record)
|
| 541 | 564 | |
| 542 | - def loadPendingPreKeys(self):
|
|
| 565 | + def loadPendingPreKeys(self) -> list[PreKeyRecord]:
|
|
| 543 | 566 | query = '''SELECT record FROM prekeys'''
|
| 544 | 567 | result = self._con.execute(query).fetchall()
|
| 545 | 568 | return [PreKeyRecord(serialized=row.record) for row in result]
|
| 546 | 569 | |
| 547 | - def storePreKey(self, preKeyId, preKeyRecord):
|
|
| 570 | + def storePreKey(self, preKeyId: int, preKeyRecord: PreKeyRecord) -> None:
|
|
| 548 | 571 | query = 'INSERT INTO prekeys (prekey_id, record) VALUES(?,?)'
|
| 549 | 572 | self._con.execute(query, (preKeyId, preKeyRecord.serialize()))
|
| 550 | 573 | self._con.commit()
|
| 551 | 574 | |
| 552 | - def containsPreKey(self, preKeyId):
|
|
| 575 | + def containsPreKey(self, preKeyId: int) -> bool:
|
|
| 553 | 576 | query = 'SELECT record FROM prekeys WHERE prekey_id = ?'
|
| 554 | 577 | result = self._con.execute(query, (preKeyId,)).fetchone()
|
| 555 | 578 | return result is not None
|
| 556 | 579 | |
| 557 | - def removePreKey(self, preKeyId):
|
|
| 580 | + def removePreKey(self, preKeyId: int) -> None:
|
|
| 558 | 581 | query = 'DELETE FROM prekeys WHERE prekey_id = ?'
|
| 559 | 582 | self._con.execute(query, (preKeyId,))
|
| 560 | 583 | self._con.commit()
|
| 561 | 584 | |
| 562 | - def getCurrentPreKeyId(self):
|
|
| 585 | + def getCurrentPreKeyId(self) -> int:
|
|
| 563 | 586 | query = 'SELECT MAX(prekey_id) FROM prekeys'
|
| 564 | 587 | return self._con.execute(query).fetchone().max_prekey_id
|
| 565 | 588 | |
| 566 | - def getPreKeyCount(self):
|
|
| 589 | + def getPreKeyCount(self) -> int:
|
|
| 567 | 590 | query = 'SELECT COUNT(prekey_id) FROM prekeys'
|
| 568 | 591 | return self._con.execute(query).fetchone().count_prekey_id
|
| 569 | 592 | |
| 570 | - def generateNewPreKeys(self, count):
|
|
| 593 | + def generateNewPreKeys(self, count: int) -> None:
|
|
| 571 | 594 | prekey_id = self.getCurrentPreKeyId() or 0
|
| 572 | 595 | pre_keys = KeyHelper.generatePreKeys(prekey_id + 1, count)
|
| 573 | 596 | for pre_key in pre_keys:
|
| 574 | 597 | self.storePreKey(pre_key.getId(), pre_key)
|
| 575 | 598 | |
| 576 | - def getIdentityKeyPair(self):
|
|
| 599 | + def getIdentityKeyPair(self) -> IdentityKeyPair:
|
|
| 577 | 600 | query = '''SELECT public_key as "public_key [pk]", private_key
|
| 578 | 601 | FROM secret LIMIT 1'''
|
| 579 | 602 | result = self._con.execute(query).fetchone()
|
| ... | ... | @@ -581,12 +604,16 @@ def getIdentityKeyPair(self): |
| 581 | 604 | return IdentityKeyPair(result.public_key,
|
| 582 | 605 | DjbECPrivateKey(result.private_key))
|
| 583 | 606 | |
| 584 | - def getLocalRegistrationId(self):
|
|
| 607 | + def getLocalRegistrationId(self) -> Optional[int]:
|
|
| 585 | 608 | query = 'SELECT device_id FROM secret LIMIT 1'
|
| 586 | 609 | result = self._con.execute(query).fetchone()
|
| 587 | 610 | return result.device_id if result is not None else None
|
| 588 | 611 | |
| 589 | - def storeLocalData(self, device_id, identityKeyPair):
|
|
| 612 | + def storeLocalData(self,
|
|
| 613 | + device_id: int,
|
|
| 614 | + identityKeyPair: IdentityKeyPair
|
|
| 615 | + ) -> None:
|
|
| 616 | + |
|
| 590 | 617 | query = 'SELECT * FROM secret'
|
| 591 | 618 | result = self._con.execute(query).fetchone()
|
| 592 | 619 | if result is not None:
|
| ... | ... | @@ -602,7 +629,7 @@ def storeLocalData(self, device_id, identityKeyPair): |
| 602 | 629 | self._con.execute(query, (device_id, public_key, private_key))
|
| 603 | 630 | self._con.commit()
|
| 604 | 631 | |
| 605 | - def saveIdentity(self, recipientId, identityKey):
|
|
| 632 | + def saveIdentity(self, recipientId: int, identityKey: IdentityKey) -> None:
|
|
| 606 | 633 | query = '''INSERT INTO identities (recipient_id, public_key, trust, shown)
|
| 607 | 634 | VALUES(?, ?, ?, ?)'''
|
| 608 | 635 | if not self.containsIdentity(recipientId, identityKey):
|
| ... | ... | @@ -613,7 +640,11 @@ def saveIdentity(self, recipientId, identityKey): |
| 613 | 640 | 1 if trust == Trust.BLIND else 0))
|
| 614 | 641 | self._con.commit()
|
| 615 | 642 | |
| 616 | - def containsIdentity(self, recipientId, identityKey):
|
|
| 643 | + def containsIdentity(self,
|
|
| 644 | + recipientId: str,
|
|
| 645 | + identityKey: IdentityKey
|
|
| 646 | + ) -> bool:
|
|
| 647 | + |
|
| 617 | 648 | query = '''SELECT * FROM identities WHERE recipient_id = ?
|
| 618 | 649 | AND public_key = ?'''
|
| 619 | 650 | |
| ... | ... | @@ -623,24 +654,36 @@ def containsIdentity(self, recipientId, identityKey): |
| 623 | 654 | |
| 624 | 655 | return result is not None
|
| 625 | 656 | |
| 626 | - def deleteIdentity(self, recipientId, identityKey):
|
|
| 657 | + def deleteIdentity(self,
|
|
| 658 | + recipientId: str,
|
|
| 659 | + identityKey: IdentityKey
|
|
| 660 | + ) -> None:
|
|
| 661 | + |
|
| 627 | 662 | query = '''DELETE FROM identities
|
| 628 | 663 | WHERE recipient_id = ? AND public_key = ?'''
|
| 629 | 664 | public_key = identityKey.getPublicKey().serialize()
|
| 630 | 665 | self._con.execute(query, (recipientId, public_key))
|
| 631 | 666 | self._con.commit()
|
| 632 | 667 | |
| 633 | - def isTrustedIdentity(self, recipientId, identityKey):
|
|
| 668 | + def isTrustedIdentity(self,
|
|
| 669 | + recipientId: str,
|
|
| 670 | + identityKey: IdentityKey
|
|
| 671 | + ) -> bool:
|
|
| 672 | + |
|
| 634 | 673 | return True
|
| 635 | 674 | |
| 636 | - def getTrustForIdentity(self, recipientId, identityKey):
|
|
| 675 | + def getTrustForIdentity(self,
|
|
| 676 | + recipientId: str,
|
|
| 677 | + identityKey: IdentityKey
|
|
| 678 | + ) -> Optional[Trust]:
|
|
| 679 | + |
|
| 637 | 680 | query = '''SELECT trust FROM identities WHERE recipient_id = ?
|
| 638 | 681 | AND public_key = ?'''
|
| 639 | 682 | public_key = identityKey.getPublicKey().serialize()
|
| 640 | 683 | result = self._con.execute(query, (recipientId, public_key)).fetchone()
|
| 641 | 684 | return result.trust if result is not None else None
|
| 642 | 685 | |
| 643 | - def getFingerprints(self, jid):
|
|
| 686 | + def getFingerprints(self, jid: str):
|
|
| 644 | 687 | query = '''SELECT recipient_id,
|
| 645 | 688 | public_key as "public_key [pk]",
|
| 646 | 689 | trust,
|
| ... | ... | @@ -649,7 +692,7 @@ def getFingerprints(self, jid): |
| 649 | 692 | WHERE recipient_id = ? ORDER BY trust ASC'''
|
| 650 | 693 | return self._con.execute(query, (jid,)).fetchall()
|
| 651 | 694 | |
| 652 | - def getMucFingerprints(self, jids):
|
|
| 695 | + def getMucFingerprints(self, jids: list[str]):
|
|
| 653 | 696 | query = '''
|
| 654 | 697 | SELECT recipient_id,
|
| 655 | 698 | public_key as "public_key [pk]",
|
| ... | ... | @@ -661,7 +704,7 @@ def getMucFingerprints(self, jids): |
| 661 | 704 | |
| 662 | 705 | return self._con.execute(query, jids).fetchall()
|
| 663 | 706 | |
| 664 | - def hasUndecidedFingerprints(self, jid):
|
|
| 707 | + def hasUndecidedFingerprints(self, jid: str) -> bool:
|
|
| 665 | 708 | query = '''SELECT public_key as "public_key [pk]" FROM identities
|
| 666 | 709 | WHERE recipient_id = ? AND trust = ?'''
|
| 667 | 710 | result = self._con.execute(query, (jid, Trust.UNDECIDED)).fetchall()
|
| ... | ... | @@ -671,7 +714,7 @@ def hasUndecidedFingerprints(self, jid): |
| 671 | 714 | undecided = set(undecided) - set(inactive)
|
| 672 | 715 | return bool(undecided)
|
| 673 | 716 | |
| 674 | - def getDefaultTrust(self, jid):
|
|
| 717 | + def getDefaultTrust(self, jid: str) -> Trust:
|
|
| 675 | 718 | if not self._is_blind_trust_enabled():
|
| 676 | 719 | return Trust.UNDECIDED
|
| 677 | 720 | |
| ... | ... | @@ -682,33 +725,38 @@ def getDefaultTrust(self, jid): |
| 682 | 725 | return Trust.BLIND
|
| 683 | 726 | return Trust.UNDECIDED
|
| 684 | 727 | |
| 685 | - def getTrustedFingerprints(self, jid):
|
|
| 728 | + def getTrustedFingerprints(self, jid: str) -> list[IdentityKeyExtended]:
|
|
| 686 | 729 | query = '''SELECT public_key as "public_key [pk]" FROM identities
|
| 687 | 730 | WHERE recipient_id = ? AND trust IN(1, 3)'''
|
| 688 | 731 | result = self._con.execute(query, (jid,)).fetchall()
|
| 689 | 732 | return [row.public_key for row in result]
|
| 690 | 733 | |
| 691 | - def getNewFingerprints(self, jid):
|
|
| 734 | + def getNewFingerprints(self, jid: str) -> list[int]:
|
|
| 692 | 735 | query = '''SELECT _id FROM identities WHERE shown = 0
|
| 693 | 736 | AND recipient_id = ?'''
|
| 694 | 737 | |
| 695 | 738 | result = self._con.execute(query, (jid,)).fetchall()
|
| 696 | 739 | return [row.id for row in result]
|
| 697 | 740 | |
| 698 | - def setShownFingerprints(self, fingerprints):
|
|
| 741 | + def setShownFingerprints(self, fingerprints: list[int]) -> None:
|
|
| 699 | 742 | query = 'UPDATE identities SET shown = 1 WHERE _id IN ({})'.format(
|
| 700 | 743 | ', '.join(['?'] * len(fingerprints)))
|
| 701 | 744 | self._con.execute(query, fingerprints)
|
| 702 | 745 | self._con.commit()
|
| 703 | 746 | |
| 704 | - def setTrust(self, recipient_id, identityKey, trust):
|
|
| 747 | + def setTrust(self,
|
|
| 748 | + recipient_id: str,
|
|
| 749 | + identityKey: IdentityKey,
|
|
| 750 | + trust: Trust
|
|
| 751 | + ) -> None:
|
|
| 752 | + |
|
| 705 | 753 | query = '''UPDATE identities SET trust = ? WHERE public_key = ?
|
| 706 | 754 | AND recipient_id = ?'''
|
| 707 | 755 | public_key = identityKey.getPublicKey().serialize()
|
| 708 | 756 | self._con.execute(query, (trust, public_key, recipient_id))
|
| 709 | 757 | self._con.commit()
|
| 710 | 758 | |
| 711 | - def isTrusted(self, recipient_id, device_id):
|
|
| 759 | + def isTrusted(self, recipient_id: str, device_id: int) -> bool:
|
|
| 712 | 760 | record = self.loadSession(recipient_id, device_id)
|
| 713 | 761 | if record.isFresh():
|
| 714 | 762 | return False
|
| ... | ... | @@ -716,7 +764,11 @@ def isTrusted(self, recipient_id, device_id): |
| 716 | 764 | return self.getTrustForIdentity(
|
| 717 | 765 | recipient_id, identity_key) in (Trust.VERIFIED, Trust.BLIND)
|
| 718 | 766 | |
| 719 | - def getIdentityLastSeen(self, recipient_id, identity_key):
|
|
| 767 | + def getIdentityLastSeen(self,
|
|
| 768 | + recipient_id: str,
|
|
| 769 | + identity_key: IdentityKey
|
|
| 770 | + ) -> Optional[int]:
|
|
| 771 | + |
|
| 720 | 772 | identity_key = identity_key.getPublicKey().serialize()
|
| 721 | 773 | query = '''SELECT timestamp FROM identities
|
| 722 | 774 | WHERE recipient_id = ? AND public_key = ?'''
|
| ... | ... | @@ -724,7 +776,11 @@ def getIdentityLastSeen(self, recipient_id, identity_key): |
| 724 | 776 | identity_key)).fetchone()
|
| 725 | 777 | return result.timestamp if result is not None else None
|
| 726 | 778 | |
| 727 | - def setIdentityLastSeen(self, recipient_id, identity_key):
|
|
| 779 | + def setIdentityLastSeen(self,
|
|
| 780 | + recipient_id: str,
|
|
| 781 | + identity_key: IdentityKey
|
|
| 782 | + ) -> None:
|
|
| 783 | + |
|
| 728 | 784 | timestamp = int(time.time())
|
| 729 | 785 | identity_key = identity_key.getPublicKey().serialize()
|
| 730 | 786 | self._log.info('Set last seen for %s %s', recipient_id, timestamp)
|
| ... | ... | @@ -733,7 +789,7 @@ def setIdentityLastSeen(self, recipient_id, identity_key): |
| 733 | 789 | self._con.execute(query, (timestamp, recipient_id, identity_key))
|
| 734 | 790 | self._con.commit()
|
| 735 | 791 | |
| 736 | - def getUnacknowledgedCount(self, recipient_id, device_id):
|
|
| 792 | + def getUnacknowledgedCount(self, recipient_id: str, device_id: int) -> int:
|
|
| 737 | 793 | record = self.loadSession(recipient_id, device_id)
|
| 738 | 794 | if record.isFresh():
|
| 739 | 795 | return 0
|
| ... | ... | @@ -15,8 +15,14 @@ |
| 15 | 15 | # You should have received a copy of the GNU General Public License
|
| 16 | 16 | # along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
| 17 | 17 | |
| 18 | +from __future__ import annotations
|
|
| 19 | + |
|
| 20 | +from typing import Any
|
|
| 21 | +from typing import Optional
|
|
| 22 | + |
|
| 18 | 23 | import time
|
| 19 | 24 | from collections import defaultdict
|
| 25 | +from pathlib import Path
|
|
| 20 | 26 | |
| 21 | 27 | from nbxmpp.structs import OMEMOBundle
|
| 22 | 28 | from nbxmpp.structs import OMEMOMessage
|
| ... | ... | @@ -24,6 +30,7 @@ |
| 24 | 30 | from axolotl.ecc.djbec import DjbECPublicKey
|
| 25 | 31 | from axolotl.identitykey import IdentityKey
|
| 26 | 32 | |
| 33 | +from axolotl.identitykeypair import IdentityKeyPair
|
|
| 27 | 34 | from axolotl.protocol.prekeywhispermessage import PreKeyWhisperMessage
|
| 28 | 35 | from axolotl.protocol.whispermessage import WhisperMessage
|
| 29 | 36 | from axolotl.sessionbuilder import SessionBuilder
|
| ... | ... | @@ -32,6 +39,7 @@ |
| 32 | 39 | from axolotl.util.keyhelper import KeyHelper
|
| 33 | 40 | from axolotl.duplicatemessagexception import DuplicateMessageException
|
| 34 | 41 | |
| 42 | +from gajim.common import types
|
|
| 35 | 43 | from gajim.common.modules.omemobackend.aes import aes_decrypt
|
| 36 | 44 | from gajim.common.modules.omemobackend.aes import aes_encrypt
|
| 37 | 45 | from gajim.common.modules.omemobackend.aes import get_new_key
|
| ... | ... | @@ -39,21 +47,28 @@ |
| 39 | 47 | from gajim.common.modules.omemobackend.devices import DeviceManager
|
| 40 | 48 | from gajim.common.modules.omemobackend.devices import NoDevicesFound
|
| 41 | 49 | from gajim.common.modules.omemobackend.liteaxolotlstore import LiteAxolotlStore
|
| 42 | -from gajim.common.modules.util import get_fingerprint
|
|
| 43 | -from gajim.common.modules.util import Trust
|
|
| 44 | -from gajim.common.modules.util import DEFAULT_PREKEY_AMOUNT
|
|
| 45 | -from gajim.common.modules.util import MIN_PREKEY_AMOUNT
|
|
| 46 | -from gajim.common.modules.util import SPK_CYCLE_TIME
|
|
| 47 | -from gajim.common.modules.util import SPK_ARCHIVE_TIME
|
|
| 48 | -from gajim.common.modules.util import UNACKNOWLEDGED_COUNT
|
|
| 50 | +from gajim.common.modules.omemobackend.util import get_fingerprint
|
|
| 51 | +from gajim.common.modules.omemobackend.util import Trust
|
|
| 52 | +from gajim.common.modules.omemobackend.util import DEFAULT_PREKEY_AMOUNT
|
|
| 53 | +from gajim.common.modules.omemobackend.util import MIN_PREKEY_AMOUNT
|
|
| 54 | +from gajim.common.modules.omemobackend.util import SPK_CYCLE_TIME
|
|
| 55 | +from gajim.common.modules.omemobackend.util import SPK_ARCHIVE_TIME
|
|
| 56 | +from gajim.common.modules.omemobackend.util import UNACKNOWLEDGED_COUNT
|
|
| 49 | 57 | |
| 50 | 58 | |
| 51 | 59 | class OmemoState(DeviceManager):
|
| 52 | - def __init__(self, own_jid, db_path, account, xmpp_con):
|
|
| 60 | + def __init__(self,
|
|
| 61 | + own_jid: str,
|
|
| 62 | + db_path: Path,
|
|
| 63 | + account: str,
|
|
| 64 | + xmpp_con: types.xmppClient
|
|
| 65 | + ) -> None:
|
|
| 66 | + |
|
| 53 | 67 | self._account = account
|
| 54 | 68 | self._own_jid = own_jid
|
| 55 | 69 | self._log = xmpp_con._log
|
| 56 | - self._session_ciphers = defaultdict(dict)
|
|
| 70 | + self._session_ciphers: dict[
|
|
| 71 | + str, dict[int, SessionCipher]] = defaultdict(dict)
|
|
| 57 | 72 | self._storage = LiteAxolotlStore(account, db_path, self._log)
|
| 58 | 73 | |
| 59 | 74 | DeviceManager.__init__(self)
|
| ... | ... | @@ -63,7 +78,12 @@ def __init__(self, own_jid, db_path, account, xmpp_con): |
| 63 | 78 | self._log.info('%s PreKeys available',
|
| 64 | 79 | self._storage.getPreKeyCount())
|
| 65 | 80 | |
| 66 | - def build_session(self, jid, device_id, bundle):
|
|
| 81 | + def build_session(self,
|
|
| 82 | + jid: str,
|
|
| 83 | + device_id: int,
|
|
| 84 | + bundle: OMEMOBundle
|
|
| 85 | + ) -> None:
|
|
| 86 | + |
|
| 67 | 87 | session = SessionBuilder(self._storage, self._storage, self._storage,
|
| 68 | 88 | self._storage, jid, device_id)
|
| 69 | 89 | |
| ... | ... | @@ -88,18 +108,18 @@ def build_session(self, jid, device_id, bundle): |
| 88 | 108 | self._get_session_cipher(jid, device_id)
|
| 89 | 109 | |
| 90 | 110 | @property
|
| 91 | - def storage(self):
|
|
| 111 | + def storage(self) -> LiteAxolotlStore:
|
|
| 92 | 112 | return self._storage
|
| 93 | 113 | |
| 94 | 114 | @property
|
| 95 | - def own_fingerprint(self):
|
|
| 115 | + def own_fingerprint(self) -> str:
|
|
| 96 | 116 | return get_fingerprint(self._storage.getIdentityKeyPair())
|
| 97 | 117 | |
| 98 | 118 | @property
|
| 99 | - def bundle(self):
|
|
| 119 | + def bundle(self) -> OMEMOBundle:
|
|
| 100 | 120 | self._check_pre_key_count()
|
| 101 | 121 | |
| 102 | - bundle = {'otpks': []}
|
|
| 122 | + bundle: dict[str, Any] = {'otpks': []}
|
|
| 103 | 123 | for k in self._storage.loadPendingPreKeys():
|
| 104 | 124 | key = k.getKeyPair().getPublicKey().serialize()
|
| 105 | 125 | bundle['otpks'].append({'key': key, 'id': k.getId()})
|
| ... | ... | @@ -117,7 +137,11 @@ def bundle(self): |
| 117 | 137 | |
| 118 | 138 | return OMEMOBundle(**bundle)
|
| 119 | 139 | |
| 120 | - def decrypt_message(self, omemo_message, jid):
|
|
| 140 | + def decrypt_message(self,
|
|
| 141 | + omemo_message: OMEMOMessage,
|
|
| 142 | + jid: str
|
|
| 143 | + ) -> tuple[str, str, Trust]:
|
|
| 144 | + |
|
| 121 | 145 | if omemo_message.sid == self.own_device:
|
| 122 | 146 | self._log.info('Received previously sent message by us')
|
| 123 | 147 | raise SelfMessage
|
| ... | ... | @@ -157,13 +181,18 @@ def decrypt_message(self, omemo_message, jid): |
| 157 | 181 | self._log.debug("Decrypted Message => %s", result)
|
| 158 | 182 | return result, fingerprint, trust
|
| 159 | 183 | |
| 160 | - def _get_whisper_message(self, jid, device, key):
|
|
| 184 | + def _get_whisper_message(self,
|
|
| 185 | + jid: str,
|
|
| 186 | + device: int,
|
|
| 187 | + key: bytes
|
|
| 188 | + ) -> tuple[bytes, bool]:
|
|
| 189 | + |
|
| 161 | 190 | cipher = self._get_session_cipher(jid, device)
|
| 162 | 191 | cipher_key = cipher.encrypt(key)
|
| 163 | 192 | prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
| 164 | 193 | return cipher_key.serialize(), prekey
|
| 165 | 194 | |
| 166 | - def encrypt(self, jid, plaintext):
|
|
| 195 | + def encrypt(self, jid: str, plaintext: str) -> Optional[OMEMOMessage]:
|
|
| 167 | 196 | try:
|
| 168 | 197 | devices_for_encryption = self.get_devices_for_encryption(jid)
|
| 169 | 198 | except NoDevicesFound:
|
| ... | ... | @@ -171,7 +200,8 @@ def encrypt(self, jid, plaintext): |
| 171 | 200 | return
|
| 172 | 201 | |
| 173 | 202 | result = aes_encrypt(plaintext)
|
| 174 | - whisper_messages = defaultdict(dict)
|
|
| 203 | + whisper_messages: dict[
|
|
| 204 | + str, dict[int, tuple[bytes, bool]]] = defaultdict(dict)
|
|
| 175 | 205 | |
| 176 | 206 | for jid_, device in devices_for_encryption:
|
| 177 | 207 | count = self._storage.getUnacknowledgedCount(jid_, device)
|
| ... | ... | @@ -194,7 +224,7 @@ def encrypt(self, jid, plaintext): |
| 194 | 224 | self._log.error('Encrypted keys empty')
|
| 195 | 225 | return
|
| 196 | 226 | |
| 197 | - encrypted_keys = {}
|
|
| 227 | + encrypted_keys: dict[int, tuple[bytes, bool]] = {}
|
|
| 198 | 228 | for jid_ in whisper_messages:
|
| 199 | 229 | encrypted_keys.update(whisper_messages[jid_])
|
| 200 | 230 | |
| ... | ... | @@ -204,8 +234,13 @@ def encrypt(self, jid, plaintext): |
| 204 | 234 | iv=result.iv,
|
| 205 | 235 | payload=result.payload)
|
| 206 | 236 | |
| 207 | - def encrypt_key_transport(self, jid, devices):
|
|
| 208 | - whisper_messages = defaultdict(dict)
|
|
| 237 | + def encrypt_key_transport(self,
|
|
| 238 | + jid: str,
|
|
| 239 | + devices: list[int]
|
|
| 240 | + ) -> Optional[OMEMOMessage]:
|
|
| 241 | + |
|
| 242 | + whisper_messages: dict[
|
|
| 243 | + str, dict[int, tuple[bytes, bool]]] = defaultdict(dict)
|
|
| 209 | 244 | for device in devices:
|
| 210 | 245 | try:
|
| 211 | 246 | whisper_messages[jid][device] = self._get_whisper_message(
|
| ... | ... | @@ -224,12 +259,12 @@ def encrypt_key_transport(self, jid, devices): |
| 224 | 259 | iv=get_new_iv(),
|
| 225 | 260 | payload=None)
|
| 226 | 261 | |
| 227 | - def has_trusted_keys(self, jid):
|
|
| 262 | + def has_trusted_keys(self, jid: str) -> bool:
|
|
| 228 | 263 | inactive = self._storage.getInactiveSessionsKeys(jid)
|
| 229 | 264 | trusted = self._storage.getTrustedFingerprints(jid)
|
| 230 | 265 | return bool(set(trusted) - set(inactive))
|
| 231 | 266 | |
| 232 | - def devices_without_sessions(self, jid):
|
|
| 267 | + def devices_without_sessions(self, jid: str) -> list[int]:
|
|
| 233 | 268 | known_devices = self.get_devices(jid, without_self=True)
|
| 234 | 269 | missing_devices = [dev
|
| 235 | 270 | for dev in known_devices
|
| ... | ... | @@ -239,7 +274,7 @@ def devices_without_sessions(self, jid): |
| 239 | 274 | jid, missing_devices)
|
| 240 | 275 | return missing_devices
|
| 241 | 276 | |
| 242 | - def _get_session_cipher(self, jid, device_id):
|
|
| 277 | + def _get_session_cipher(self, jid: str, device_id: int) -> SessionCipher:
|
|
| 243 | 278 | try:
|
| 244 | 279 | return self._session_ciphers[jid][device_id]
|
| 245 | 280 | except KeyError:
|
| ... | ... | @@ -248,7 +283,12 @@ def _get_session_cipher(self, jid, device_id): |
| 248 | 283 | self._session_ciphers[jid][device_id] = cipher
|
| 249 | 284 | return cipher
|
| 250 | 285 | |
| 251 | - def _process_pre_key_message(self, jid, device, key):
|
|
| 286 | + def _process_pre_key_message(self,
|
|
| 287 | + jid: str,
|
|
| 288 | + device: int,
|
|
| 289 | + key: bytes
|
|
| 290 | + ) -> tuple[bytes, str, Trust]:
|
|
| 291 | + |
|
| 252 | 292 | self._log.info('Process pre key message from %s', jid)
|
| 253 | 293 | pre_key_message = PreKeyWhisperMessage(serialized=key)
|
| 254 | 294 | if not pre_key_message.getPreKeyId():
|
| ... | ... | @@ -268,7 +308,12 @@ def _process_pre_key_message(self, jid, device, key): |
| 268 | 308 | self.add_device(jid, device)
|
| 269 | 309 | return key, fingerprint, trust
|
| 270 | 310 | |
| 271 | - def _process_message(self, jid, device, key):
|
|
| 311 | + def _process_message(self,
|
|
| 312 | + jid: str,
|
|
| 313 | + device: int,
|
|
| 314 | + key: bytes
|
|
| 315 | + ) -> tuple[bytes, str, Trust]:
|
|
| 316 | + |
|
| 272 | 317 | self._log.info('Process message from %s', jid)
|
| 273 | 318 | message = WhisperMessage(serialized=key)
|
| 274 | 319 | |
| ... | ... | @@ -290,15 +335,23 @@ def _get_identity_key_from_pk_message(key): |
| 290 | 335 | pre_key_message = PreKeyWhisperMessage(serialized=key)
|
| 291 | 336 | return pre_key_message.getIdentityKey()
|
| 292 | 337 | |
| 293 | - def _get_identity_key_from_device(self, jid, device):
|
|
| 338 | + def _get_identity_key_from_device(self,
|
|
| 339 | + jid: str,
|
|
| 340 | + device: int
|
|
| 341 | + ) -> Optional[IdentityKey]:
|
|
| 342 | + |
|
| 294 | 343 | session_record = self._storage.loadSession(jid, device)
|
| 295 | 344 | return session_record.getSessionState().getRemoteIdentityKey()
|
| 296 | 345 | |
| 297 | - def _get_trust_from_identity_key(self, jid, identity_key):
|
|
| 346 | + def _get_trust_from_identity_key(self,
|
|
| 347 | + jid: str,
|
|
| 348 | + identity_key: IdentityKey
|
|
| 349 | + ) -> Trust:
|
|
| 350 | + |
|
| 298 | 351 | trust = self._storage.getTrustForIdentity(jid, identity_key)
|
| 299 | 352 | return Trust(trust) if trust is not None else Trust.UNDECIDED
|
| 300 | 353 | |
| 301 | - def _check_pre_key_count(self):
|
|
| 354 | + def _check_pre_key_count(self) -> None:
|
|
| 302 | 355 | # Check if enough PreKeys are available
|
| 303 | 356 | pre_key_count = self._storage.getPreKeyCount()
|
| 304 | 357 | if pre_key_count < MIN_PREKEY_AMOUNT:
|
| ... | ... | @@ -306,7 +359,7 @@ def _check_pre_key_count(self): |
| 306 | 359 | self._storage.generateNewPreKeys(missing_count)
|
| 307 | 360 | self._log.info('%s PreKeys created', missing_count)
|
| 308 | 361 | |
| 309 | - def _cycle_signed_pre_key(self, ik_pair):
|
|
| 362 | + def _cycle_signed_pre_key(self, ik_pair: IdentityKeyPair) -> None:
|
|
| 310 | 363 | # Publish every SPK_CYCLE_TIME a new SignedPreKey
|
| 311 | 364 | # Delete all exsiting SignedPreKeys that are older
|
| 312 | 365 | # then SPK_ARCHIVE_TIME
|
| ... | ... | @@ -14,11 +14,15 @@ |
| 14 | 14 | # You should have received a copy of the GNU General Public License
|
| 15 | 15 | # along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
| 16 | 16 | |
| 17 | +from __future__ import annotations
|
|
| 18 | + |
|
| 17 | 19 | import binascii
|
| 18 | 20 | import textwrap
|
| 19 | 21 | from enum import IntEnum
|
| 20 | 22 | |
| 21 | 23 | from axolotl.identitykey import IdentityKey
|
| 24 | +from axolotl.identitykeypair import IdentityKeyPair
|
|
| 25 | + |
|
| 22 | 26 | |
| 23 | 27 | DEFAULT_PREKEY_AMOUNT = 100
|
| 24 | 28 | MIN_PREKEY_AMOUNT = 80
|
| ... | ... | @@ -34,7 +38,10 @@ class Trust(IntEnum): |
| 34 | 38 | BLIND = 3
|
| 35 | 39 | |
| 36 | 40 | |
| 37 | -def get_fingerprint(identity_key, formatted=False):
|
|
| 41 | +def get_fingerprint(identity_key: IdentityKeyPair,
|
|
| 42 | + formatted: bool = False
|
|
| 43 | + ) -> str:
|
|
| 44 | + |
|
| 38 | 45 | public_key = identity_key.getPublicKey().serialize()
|
| 39 | 46 | fingerprint = binascii.hexlify(public_key).decode()[2:]
|
| 40 | 47 | if not formatted:
|
| ... | ... | @@ -49,8 +56,8 @@ def get_fingerprint(identity_key, formatted=False): |
| 49 | 56 | |
| 50 | 57 | |
| 51 | 58 | class IdentityKeyExtended(IdentityKey):
|
| 52 | - def __hash__(self):
|
|
| 59 | + def __hash__(self) -> int:
|
|
| 53 | 60 | return hash(self.publicKey.serialize())
|
| 54 | 61 | |
| 55 | - def get_fingerprint(self, formatted=False):
|
|
| 62 | + def get_fingerprint(self, formatted: bool = False) -> str:
|
|
| 56 | 63 | return get_fingerprint(self, formatted=formatted) |
| ... | ... | @@ -17,6 +17,7 @@ |
| 17 | 17 | from __future__ import annotations
|
| 18 | 18 | |
| 19 | 19 | from typing import Any
|
| 20 | +from typing import Optional
|
|
| 20 | 21 | from typing import Union
|
| 21 | 22 | |
| 22 | 23 | import logging
|
| ... | ... | @@ -187,13 +188,17 @@ def check_if_message_correction(properties: MessageProperties, |
| 187 | 188 | return True
|
| 188 | 189 | |
| 189 | 190 | |
| 190 | -def prepare_stanza(stanza, plaintext):
|
|
| 191 | +def prepare_stanza(stanza: Message, plaintext: str) -> None:
|
|
| 191 | 192 | delete_nodes(stanza, 'encrypted', Namespace.OMEMO_TEMP)
|
| 192 | 193 | delete_nodes(stanza, 'body')
|
| 193 | 194 | stanza.setBody(plaintext)
|
| 194 | 195 | |
| 195 | 196 | |
| 196 | -def delete_nodes(stanza, name, namespace=None):
|
|
| 197 | +def delete_nodes(stanza: Message,
|
|
| 198 | + name: str,
|
|
| 199 | + namespace: Optional[str] = None
|
|
| 200 | + ) -> None:
|
|
| 201 | + |
|
| 197 | 202 | nodes = stanza.getTags(name, namespace=namespace)
|
| 198 | 203 | for node in nodes:
|
| 199 | 204 | stanza.delChild(node) |