Philipp Hörist pushed to branch master at gajim / gajim
Commits:
-
939b53af
by Philipp Hörist at 2023-05-03T22:01:20+02:00
-
f8ba3f06
by Philipp Hörist at 2023-05-03T22:01:20+02:00
-
9b8b1839
by Philipp Hörist at 2023-05-03T22:19:54+02:00
9 changed files:
- debian/control
- gajim/common/modules/omemo.py
- − gajim/common/omemo/__init__.py
- − gajim/common/omemo/aes.py
- − gajim/common/omemo/state.py
- − gajim/common/omemo/util.py
- gajim/common/storage/omemo.py
- gajim/gtk/omemo_trust_manager.py
- pyproject.toml
Changes:
| ... | ... | @@ -13,7 +13,7 @@ Build-Depends: |
| 13 | 13 | python3-gi,
|
| 14 | 14 | python3-gi-cairo,
|
| 15 | 15 | python3-nbxmpp-nightly (>=20230405),
|
| 16 | - python3-omemo-dr (>=20230407),
|
|
| 16 | + python3-omemo-dr (>=20230503),
|
|
| 17 | 17 | python3-setuptools,
|
| 18 | 18 | python3-packaging,
|
| 19 | 19 | python3-cryptography (>=3.4.8),
|
| ... | ... | @@ -36,7 +36,7 @@ Depends: |
| 36 | 36 | python3-gi (>= 3.42.0),
|
| 37 | 37 | python3-gi-cairo (>= 1.14.0~),
|
| 38 | 38 | python3-nbxmpp-nightly (>=20230405),
|
| 39 | - python3-omemo-dr (>=20230407),
|
|
| 39 | + python3-omemo-dr (>=20230503),
|
|
| 40 | 40 | gir1.2-pango-1.0 (>= 1.50.0),
|
| 41 | 41 | gir1.2-gtk-3.0 (>= 3.24.30),
|
| 42 | 42 | gir1.2-gtksource-4,
|
| ... | ... | @@ -24,6 +24,7 @@ |
| 24 | 24 | |
| 25 | 25 | import binascii
|
| 26 | 26 | import threading
|
| 27 | +from pathlib import Path
|
|
| 27 | 28 | |
| 28 | 29 | from gi.repository import GLib
|
| 29 | 30 | from nbxmpp.const import Affiliation
|
| ... | ... | @@ -42,8 +43,19 @@ |
| 42 | 43 | from nbxmpp.structs import PresenceProperties
|
| 43 | 44 | from nbxmpp.structs import StanzaHandler
|
| 44 | 45 | from nbxmpp.task import Task
|
| 46 | +from omemo_dr.aes import aes_encrypt_file
|
|
| 47 | +from omemo_dr.const import OMEMOTrust
|
|
| 48 | +from omemo_dr.exceptions import DecryptionFailed
|
|
| 49 | +from omemo_dr.exceptions import DuplicateMessage
|
|
| 50 | +from omemo_dr.exceptions import KeyExchangeMessage
|
|
| 51 | +from omemo_dr.exceptions import MessageNotForDevice
|
|
| 52 | +from omemo_dr.exceptions import SelfMessage
|
|
| 53 | +from omemo_dr.session import OMEMOSession
|
|
| 54 | +from omemo_dr.structs import OMEMOBundle
|
|
| 55 | +from omemo_dr.structs import OMEMOConfig
|
|
| 45 | 56 | |
| 46 | 57 | from gajim.common import app
|
| 58 | +from gajim.common import configpaths
|
|
| 47 | 59 | from gajim.common import ged
|
| 48 | 60 | from gajim.common import types
|
| 49 | 61 | from gajim.common.const import EncryptionData
|
| ... | ... | @@ -60,14 +72,7 @@ |
| 60 | 72 | from gajim.common.modules.util import as_task
|
| 61 | 73 | from gajim.common.modules.util import event_node
|
| 62 | 74 | from gajim.common.modules.util import prepare_stanza
|
| 63 | -from gajim.common.omemo.aes import aes_encrypt_file
|
|
| 64 | -from gajim.common.omemo.state import DecryptionFailed
|
|
| 65 | -from gajim.common.omemo.state import DuplicateMessage
|
|
| 66 | -from gajim.common.omemo.state import KeyExchangeMessage
|
|
| 67 | -from gajim.common.omemo.state import MessageNotForDevice
|
|
| 68 | -from gajim.common.omemo.state import OmemoState
|
|
| 69 | -from gajim.common.omemo.state import SelfMessage
|
|
| 70 | -from gajim.common.omemo.util import Trust
|
|
| 75 | +from gajim.common.storage.omemo import OMEMOStorage
|
|
| 71 | 76 | from gajim.common.structs import OutgoingMessage
|
| 72 | 77 | |
| 73 | 78 | ALLOWED_TAGS = [
|
| ... | ... | @@ -122,7 +127,20 @@ def __init__(self, client: types.Client) -> None: |
| 122 | 127 | self.allow_groupchat = True
|
| 123 | 128 | |
| 124 | 129 | self._own_jid = self._client.get_own_jid().bare
|
| 125 | - self._backend = OmemoState(self._account, self._own_jid, self)
|
|
| 130 | + |
|
| 131 | + data_dir = Path(configpaths.get('MY_DATA'))
|
|
| 132 | + db_path = data_dir / f'omemo_{self._own_jid}.db'
|
|
| 133 | + storage = OMEMOStorage(self._account, db_path, self._log)
|
|
| 134 | + |
|
| 135 | + omemo_config = OMEMOConfig(default_prekey_amount=100,
|
|
| 136 | + min_prekey_amount=80,
|
|
| 137 | + spk_archive_seconds=86400 * 15,
|
|
| 138 | + spk_cycle_seconds=86400,
|
|
| 139 | + unacknowledged_count=2000)
|
|
| 140 | + |
|
| 141 | + self._backend = OMEMOSession(self._own_jid, storage, omemo_config)
|
|
| 142 | + self._backend.register_signal('republish-bundle',
|
|
| 143 | + self._on_republish_bundle)
|
|
| 126 | 144 | |
| 127 | 145 | self._omemo_groupchats: set[str] = set()
|
| 128 | 146 | self._muc_temp_store: dict[bytes, str] = {}
|
| ... | ... | @@ -160,8 +178,16 @@ def _on_room_joined(self, |
| 160 | 178 | if self.is_omemo_groupchat(jid):
|
| 161 | 179 | self.get_affiliation_list(jid)
|
| 162 | 180 | |
| 181 | + def _on_republish_bundle(self,
|
|
| 182 | + _session: OMEMOSession,
|
|
| 183 | + _signal_name: str,
|
|
| 184 | + bundle: OMEMOBundle
|
|
| 185 | + ) -> None:
|
|
| 186 | + |
|
| 187 | + self.set_bundle(bundle=bundle)
|
|
| 188 | + |
|
| 163 | 189 | @property
|
| 164 | - def backend(self) -> OmemoState:
|
|
| 190 | + def backend(self) -> OMEMOSession:
|
|
| 165 | 191 | return self._backend
|
| 166 | 192 | |
| 167 | 193 | def check_send_preconditions(self, contact: types.ChatContactT) -> bool:
|
| ... | ... | @@ -175,7 +201,7 @@ def check_send_preconditions(self, contact: types.ChatContactT) -> bool: |
| 175 | 201 | return False
|
| 176 | 202 | |
| 177 | 203 | missing = True
|
| 178 | - for member_jid in self.backend.get_muc_members(jid):
|
|
| 204 | + for member_jid in self.backend.get_group_members(jid):
|
|
| 179 | 205 | if not self.are_keys_missing(member_jid):
|
| 180 | 206 | missing = False
|
| 181 | 207 | if missing:
|
| ... | ... | @@ -197,7 +223,7 @@ def check_send_preconditions(self, contact: types.ChatContactT) -> bool: |
| 197 | 223 | return False
|
| 198 | 224 | |
| 199 | 225 | # check if bundles are missing for some devices
|
| 200 | - if self.backend.storage.hasUndecidedFingerprints(jid):
|
|
| 226 | + if self.backend.storage.has_undecided_fingerprints(jid):
|
|
| 201 | 227 | self._log.info('%s => Undecided Fingerprints for %s',
|
| 202 | 228 | contact.account, jid)
|
| 203 | 229 | app.ged.raise_event(EncryptionInfo(
|
| ... | ... | @@ -217,15 +243,15 @@ def check_send_preconditions(self, contact: types.ChatContactT) -> bool: |
| 217 | 243 | def _new_fingerprints_available(self, contact: types.ChatContactT) -> bool:
|
| 218 | 244 | fingerprints: list[int] = []
|
| 219 | 245 | if contact.is_groupchat:
|
| 220 | - for member_jid in self.backend.get_muc_members(str(contact.jid),
|
|
| 246 | + for member_jid in self.backend.get_group_members(str(contact.jid),
|
|
| 221 | 247 | without_self=False):
|
| 222 | - fingerprints = self.backend.storage.getNewFingerprints(
|
|
| 248 | + fingerprints = self.backend.storage.get_new_fingerprints(
|
|
| 223 | 249 | member_jid)
|
| 224 | 250 | if fingerprints:
|
| 225 | 251 | break
|
| 226 | 252 | |
| 227 | 253 | else:
|
| 228 | - fingerprints = self.backend.storage.getNewFingerprints(
|
|
| 254 | + fingerprints = self.backend.storage.get_new_fingerprints(
|
|
| 229 | 255 | str(contact.jid))
|
| 230 | 256 | |
| 231 | 257 | if not fingerprints:
|
| ... | ... | @@ -245,7 +271,12 @@ def encrypt_message(self, event: OutgoingMessage) -> bool: |
| 245 | 271 | if not event.message:
|
| 246 | 272 | return False
|
| 247 | 273 | |
| 248 | - omemo_message = self.backend.encrypt(str(event.jid), event.message)
|
|
| 274 | + client = app.get_client(self._account)
|
|
| 275 | + contact = client.get_module('Contacts').get_contact(event.jid)
|
|
| 276 | + |
|
| 277 | + omemo_message = self.backend.encrypt(str(event.jid),
|
|
| 278 | + event.message,
|
|
| 279 | + groupchat=contact.is_groupchat)
|
|
| 249 | 280 | if omemo_message is None:
|
| 250 | 281 | raise Exception('Encryption error')
|
| 251 | 282 | |
| ... | ... | @@ -258,7 +289,7 @@ def encrypt_message(self, event: OutgoingMessage) -> bool: |
| 258 | 289 | event.xhtml = None
|
| 259 | 290 | event.additional_data['encrypted'] = {
|
| 260 | 291 | 'name': 'OMEMO',
|
| 261 | - 'trust': GajimTrust[Trust.VERIFIED.name]}
|
|
| 292 | + 'trust': GajimTrust[OMEMOTrust.VERIFIED.name]}
|
|
| 262 | 293 | |
| 263 | 294 | self._debug_print_stanza(event.stanza)
|
| 264 | 295 | return True
|
| ... | ... | @@ -346,8 +377,8 @@ def _message_received(self, |
| 346 | 377 | return
|
| 347 | 378 | |
| 348 | 379 | plaintext = self._muc_temp_store[properties.omemo.payload]
|
| 349 | - fingerprint = self.backend.own_fingerprint
|
|
| 350 | - trust = Trust.VERIFIED
|
|
| 380 | + fingerprint = self.backend.get_own_fingerprint()
|
|
| 381 | + trust = OMEMOTrust.VERIFIED
|
|
| 351 | 382 | del self._muc_temp_store[properties.omemo.payload]
|
| 352 | 383 | |
| 353 | 384 | except DecryptionFailed:
|
| ... | ... | @@ -362,7 +393,7 @@ def _message_received(self, |
| 362 | 393 | 'but not for your device.')
|
| 363 | 394 | # Neither trust nor fingerprint can be verified if we didn't
|
| 364 | 395 | # successfully decrypt the message
|
| 365 | - trust = Trust.UNTRUSTED
|
|
| 396 | + trust = OMEMOTrust.UNTRUSTED
|
|
| 366 | 397 | fingerprint = None
|
| 367 | 398 | |
| 368 | 399 | prepare_stanza(stanza, plaintext)
|
| ... | ... | @@ -388,7 +419,8 @@ def _process_muc_message(self, |
| 388 | 419 | |
| 389 | 420 | assert isinstance(properties.omemo, OMEMOMessage)
|
| 390 | 421 | self._log.info('Groupchat: Last resort trying to find SID in DB')
|
| 391 | - from_jid = self.backend.storage.getJidFromDevice(properties.omemo.sid)
|
|
| 422 | + from_jid = self.backend.storage.get_jid_from_device(
|
|
| 423 | + properties.omemo.sid)
|
|
| 392 | 424 | if not from_jid:
|
| 393 | 425 | self._log.error(
|
| 394 | 426 | "Can't decrypt GroupChat Message from %s", resource)
|
| ... | ... | @@ -430,9 +462,9 @@ def _on_muc_user_presence(self, |
| 430 | 462 | jid = properties.muc_user.jid.bare
|
| 431 | 463 | if properties.muc_user.affiliation in (Affiliation.OUTCAST,
|
| 432 | 464 | Affiliation.NONE):
|
| 433 | - self.backend.remove_muc_member(room, jid)
|
|
| 465 | + self.backend.remove_group_member(room, jid)
|
|
| 434 | 466 | else:
|
| 435 | - self.backend.add_muc_member(room, jid)
|
|
| 467 | + self.backend.add_group_member(room, jid)
|
|
| 436 | 468 | |
| 437 | 469 | if self.is_omemo_groupchat(room):
|
| 438 | 470 | if not self.is_contact_in_roster(jid):
|
| ... | ... | @@ -458,7 +490,7 @@ def _on_affiliations_received(self, task: Task) -> None: |
| 458 | 490 | |
| 459 | 491 | for user_jid in result.users:
|
| 460 | 492 | jid = str(user_jid)
|
| 461 | - self.backend.add_muc_member(room_jid, jid)
|
|
| 493 | + self.backend.add_group_member(room_jid, jid)
|
|
| 462 | 494 | |
| 463 | 495 | if not self.is_contact_in_roster(jid):
|
| 464 | 496 | # Query Devicelists from JIDs not in our Roster
|
| ... | ... | @@ -486,7 +518,7 @@ def _check_if_omemo_capable(self, jid: str) -> None: |
| 486 | 518 | self._omemo_groupchats.discard(jid)
|
| 487 | 519 | |
| 488 | 520 | def _check_for_missing_sessions(self, jid: str) -> None:
|
| 489 | - devices_without_session = self.backend.devices_without_sessions(jid)
|
|
| 521 | + devices_without_session = self.backend.get_devices_without_sessions(jid)
|
|
| 490 | 522 | for device_id in devices_without_session:
|
| 491 | 523 | if device_id in self._device_bundle_querys:
|
| 492 | 524 | continue
|
| ... | ... | @@ -511,7 +543,7 @@ def are_keys_missing(self, contact_jid: str) -> bool: |
| 511 | 543 | # Fetch Bundles of own other Devices
|
| 512 | 544 | if self._own_jid not in self._query_for_bundles:
|
| 513 | 545 | |
| 514 | - devices_without_session = self.backend.devices_without_sessions(
|
|
| 546 | + devices_without_session = self.backend.get_devices_without_sessions(
|
|
| 515 | 547 | self._own_jid)
|
| 516 | 548 | |
| 517 | 549 | self._query_for_bundles.append(self._own_jid)
|
| ... | ... | @@ -523,7 +555,7 @@ def are_keys_missing(self, contact_jid: str) -> bool: |
| 523 | 555 | # Fetch Bundles of contacts devices
|
| 524 | 556 | if contact_jid not in self._query_for_bundles:
|
| 525 | 557 | |
| 526 | - devices_without_session = self.backend.devices_without_sessions(
|
|
| 558 | + devices_without_session = self.backend.get_devices_without_sessions(
|
|
| 527 | 559 | contact_jid)
|
| 528 | 560 | |
| 529 | 561 | self._query_for_bundles.append(contact_jid)
|
| ... | ... | @@ -536,9 +568,11 @@ def are_keys_missing(self, contact_jid: str) -> bool: |
| 536 | 568 | return False
|
| 537 | 569 | return True
|
| 538 | 570 | |
| 539 | - def set_bundle(self) -> None:
|
|
| 540 | - self._nbxmpp('OMEMO').set_bundle(self.backend.bundle,
|
|
| 541 | - self.backend.own_device)
|
|
| 571 | + def set_bundle(self, bundle: OMEMOBundle | None = None) -> None:
|
|
| 572 | + if bundle is None:
|
|
| 573 | + bundle = self.backend.get_bundle()
|
|
| 574 | + self._nbxmpp('OMEMO').set_bundle(bundle,
|
|
| 575 | + self.backend.get_own_device())
|
|
| 542 | 576 | |
| 543 | 577 | @as_task
|
| 544 | 578 | def request_bundle(self, jid: str, device_id: int):
|
| ... | ... | @@ -566,7 +600,7 @@ def request_bundle(self, jid: str, device_id: int): |
| 566 | 600 | message=EncryptionInfoMsg.UNDECIDED_FINGERPRINTS))
|
| 567 | 601 | |
| 568 | 602 | def set_devicelist(self, devicelist: Optional[list[int]] = None) -> None:
|
| 569 | - devicelist_: set[int] = {self.backend.own_device}
|
|
| 603 | + devicelist_: set[int] = {self.backend.get_own_device()}
|
|
| 570 | 604 | if devicelist is not None:
|
| 571 | 605 | devicelist_.update(devicelist)
|
| 572 | 606 | self._log.info('Publishing own devicelist: %s', devicelist_)
|
| ... | ... | @@ -574,7 +608,7 @@ def set_devicelist(self, devicelist: Optional[list[int]] = None) -> None: |
| 574 | 608 | |
| 575 | 609 | def clear_devicelist(self) -> None:
|
| 576 | 610 | self.backend.update_devicelist(
|
| 577 | - self._own_jid, [self.backend.own_device])
|
|
| 611 | + self._own_jid, [self.backend.get_own_device()])
|
|
| 578 | 612 | self.set_devicelist()
|
| 579 | 613 | |
| 580 | 614 | @as_task
|
| ... | ... | @@ -627,7 +661,7 @@ def _process_devicelist_update(self, |
| 627 | 661 | self._query_for_bundles.remove(jid)
|
| 628 | 662 | |
| 629 | 663 | if own_devices:
|
| 630 | - if not self.backend.is_own_device_published:
|
|
| 664 | + if not self.backend.is_own_device_published():
|
|
| 631 | 665 | # Our own device_id is not in the list, it could be
|
| 632 | 666 | # overwritten by some other client
|
| 633 | 667 | self.set_devicelist(devicelist)
|
| ... | ... | @@ -638,3 +672,7 @@ def _debug_print_stanza(self, stanza: Any) -> None: |
| 638 | 672 | stanzastr = '\n' + stanza.__str__(fancy=True)
|
| 639 | 673 | stanzastr = stanzastr[0:-1]
|
| 640 | 674 | self._log.debug(stanzastr)
|
| 675 | + |
|
| 676 | + def cleanup(self) -> None:
|
|
| 677 | + self._backend.destroy()
|
|
| 678 | + del self._backend |
| 1 | -# This file is part of Gajim.
|
|
| 2 | -#
|
|
| 3 | -# Gajim is free software; you can redistribute it and/or modify
|
|
| 4 | -# it under the terms of the GNU General Public License as published
|
|
| 5 | -# by the Free Software Foundation; version 3 only.
|
|
| 6 | -#
|
|
| 7 | -# Gajim is distributed in the hope that it will be useful,
|
|
| 8 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 9 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 10 | -# GNU General Public License for more details.
|
|
| 11 | -#
|
|
| 12 | -# You should have received a copy of the GNU General Public License
|
|
| 13 | -# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
| 14 | - |
|
| 15 | -from __future__ import annotations
|
|
| 16 | - |
|
| 17 | -from typing import NamedTuple
|
|
| 18 | -from typing import Union
|
|
| 19 | - |
|
| 20 | -import logging
|
|
| 21 | -import os
|
|
| 22 | - |
|
| 23 | -from cryptography.hazmat.backends import default_backend
|
|
| 24 | -from cryptography.hazmat.primitives.ciphers import algorithms
|
|
| 25 | -from cryptography.hazmat.primitives.ciphers import Cipher
|
|
| 26 | -from cryptography.hazmat.primitives.ciphers.modes import GCM
|
|
| 27 | - |
|
| 28 | -log = logging.getLogger('gajim.c.omemo.aes')
|
|
| 29 | - |
|
| 30 | -IV_SIZE = 12
|
|
| 31 | - |
|
| 32 | - |
|
| 33 | -class EncryptionResult(NamedTuple):
|
|
| 34 | - payload: bytes
|
|
| 35 | - key: bytes
|
|
| 36 | - iv: bytes
|
|
| 37 | - |
|
| 38 | - |
|
| 39 | -def _decrypt(key: bytes, iv: bytes, tag: bytes, data: bytes) -> bytes:
|
|
| 40 | - decryptor = Cipher(
|
|
| 41 | - algorithms.AES(key),
|
|
| 42 | - GCM(iv, tag=tag),
|
|
| 43 | - backend=default_backend()).decryptor()
|
|
| 44 | - return decryptor.update(data) + decryptor.finalize()
|
|
| 45 | - |
|
| 46 | - |
|
| 47 | -def aes_decrypt(_key: bytes, iv: bytes, payload: bytes) -> str:
|
|
| 48 | - if len(_key) >= 32:
|
|
| 49 | - # XEP-0384
|
|
| 50 | - log.debug('XEP Compliant Key/Tag')
|
|
| 51 | - data = payload
|
|
| 52 | - key = _key[:16]
|
|
| 53 | - tag = _key[16:]
|
|
| 54 | - else:
|
|
| 55 | - # Legacy
|
|
| 56 | - log.debug('Legacy Key/Tag')
|
|
| 57 | - data = payload[:-16]
|
|
| 58 | - key = _key
|
|
| 59 | - tag = payload[-16:]
|
|
| 60 | - |
|
| 61 | - return _decrypt(key, iv, tag, data).decode()
|
|
| 62 | - |
|
| 63 | - |
|
| 64 | -def aes_decrypt_file(key: bytes, iv: bytes, payload: bytes) -> bytes:
|
|
| 65 | - data = payload[:-16]
|
|
| 66 | - tag = payload[-16:]
|
|
| 67 | - return _decrypt(key, iv, tag, data)
|
|
| 68 | - |
|
| 69 | - |
|
| 70 | -def _encrypt(data: Union[str, bytes],
|
|
| 71 | - key_size: int,
|
|
| 72 | - iv_size: int = IV_SIZE
|
|
| 73 | - ) -> tuple[bytes, bytes, bytes, bytes]:
|
|
| 74 | - |
|
| 75 | - if isinstance(data, str):
|
|
| 76 | - data = data.encode()
|
|
| 77 | - key = os.urandom(key_size)
|
|
| 78 | - iv = os.urandom(iv_size)
|
|
| 79 | - encryptor = Cipher(
|
|
| 80 | - algorithms.AES(key),
|
|
| 81 | - GCM(iv),
|
|
| 82 | - backend=default_backend()).encryptor()
|
|
| 83 | - |
|
| 84 | - payload = encryptor.update(data) + encryptor.finalize()
|
|
| 85 | - return key, iv, encryptor.tag, payload
|
|
| 86 | - |
|
| 87 | - |
|
| 88 | -def aes_encrypt(plaintext: str) -> EncryptionResult:
|
|
| 89 | - key, iv, tag, payload = _encrypt(plaintext, 16)
|
|
| 90 | - key += tag
|
|
| 91 | - return EncryptionResult(payload=payload, key=key, iv=iv)
|
|
| 92 | - |
|
| 93 | - |
|
| 94 | -def aes_encrypt_file(data: bytes) -> EncryptionResult:
|
|
| 95 | - key, iv, tag, payload, = _encrypt(data, 32)
|
|
| 96 | - payload += tag
|
|
| 97 | - return EncryptionResult(payload=payload, key=key, iv=iv)
|
|
| 98 | - |
|
| 99 | - |
|
| 100 | -def get_new_key() -> bytes:
|
|
| 101 | - return os.urandom(16)
|
|
| 102 | - |
|
| 103 | - |
|
| 104 | -def get_new_iv() -> bytes:
|
|
| 105 | - return os.urandom(IV_SIZE) |
| 1 | -# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
|
|
| 2 | -# Copyright (C) 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
|
| 3 | -#
|
|
| 4 | -# This file is part of OMEMO Gajim Plugin.
|
|
| 5 | -#
|
|
| 6 | -# OMEMO Gajim Plugin is free software; you can redistribute it and/or modify
|
|
| 7 | -# it under the terms of the GNU General Public License as published
|
|
| 8 | -# by the Free Software Foundation; version 3 only.
|
|
| 9 | -#
|
|
| 10 | -# OMEMO Gajim Plugin is distributed in the hope that it will be useful,
|
|
| 11 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 12 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 13 | -# GNU General Public License for more details.
|
|
| 14 | -#
|
|
| 15 | -# You should have received a copy of the GNU General Public License
|
|
| 16 | -# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
|
| 17 | - |
|
| 18 | -from __future__ import annotations
|
|
| 19 | - |
|
| 20 | -from typing import Any
|
|
| 21 | -from typing import Optional
|
|
| 22 | - |
|
| 23 | -import time
|
|
| 24 | -from collections import defaultdict
|
|
| 25 | -from pathlib import Path
|
|
| 26 | - |
|
| 27 | -from nbxmpp.structs import OMEMOBundle
|
|
| 28 | -from nbxmpp.structs import OMEMOMessage
|
|
| 29 | -from omemo_dr.ecc.djbec import CurvePublicKey
|
|
| 30 | -from omemo_dr.exceptions import DuplicateMessageException
|
|
| 31 | -from omemo_dr.identitykey import IdentityKey
|
|
| 32 | -from omemo_dr.identitykeypair import IdentityKeyPair
|
|
| 33 | -from omemo_dr.protocol.prekeywhispermessage import PreKeyWhisperMessage
|
|
| 34 | -from omemo_dr.protocol.whispermessage import WhisperMessage
|
|
| 35 | -from omemo_dr.sessionbuilder import SessionBuilder
|
|
| 36 | -from omemo_dr.sessioncipher import SessionCipher
|
|
| 37 | -from omemo_dr.state.prekeybundle import PreKeyBundle
|
|
| 38 | -from omemo_dr.util.keyhelper import KeyHelper
|
|
| 39 | - |
|
| 40 | -from gajim.common import app
|
|
| 41 | -from gajim.common import configpaths
|
|
| 42 | -from gajim.common import types
|
|
| 43 | -from gajim.common.omemo.aes import aes_decrypt
|
|
| 44 | -from gajim.common.omemo.aes import aes_encrypt
|
|
| 45 | -from gajim.common.omemo.aes import get_new_iv
|
|
| 46 | -from gajim.common.omemo.aes import get_new_key
|
|
| 47 | -from gajim.common.omemo.util import DEFAULT_PREKEY_AMOUNT
|
|
| 48 | -from gajim.common.omemo.util import get_fingerprint
|
|
| 49 | -from gajim.common.omemo.util import MIN_PREKEY_AMOUNT
|
|
| 50 | -from gajim.common.omemo.util import SPK_ARCHIVE_TIME
|
|
| 51 | -from gajim.common.omemo.util import SPK_CYCLE_TIME
|
|
| 52 | -from gajim.common.omemo.util import Trust
|
|
| 53 | -from gajim.common.omemo.util import UNACKNOWLEDGED_COUNT
|
|
| 54 | -from gajim.common.storage.omemo import OMEMOStorage
|
|
| 55 | - |
|
| 56 | - |
|
| 57 | -class OmemoState:
|
|
| 58 | - def __init__(self,
|
|
| 59 | - account: str,
|
|
| 60 | - own_jid: str,
|
|
| 61 | - xmpp_con: types.xmppClient
|
|
| 62 | - ) -> None:
|
|
| 63 | - |
|
| 64 | - self._account = account
|
|
| 65 | - self._own_jid = own_jid
|
|
| 66 | - self._log = xmpp_con._log
|
|
| 67 | - self._session_ciphers: dict[
|
|
| 68 | - str, dict[int, SessionCipher]] = defaultdict(dict)
|
|
| 69 | - |
|
| 70 | - data_dir = Path(configpaths.get('MY_DATA'))
|
|
| 71 | - db_path = data_dir / f'omemo_{self._own_jid}.db'
|
|
| 72 | - self._storage = OMEMOStorage(account, db_path, self._log)
|
|
| 73 | - |
|
| 74 | - self.xmpp_con = xmpp_con
|
|
| 75 | - |
|
| 76 | - self._log.info('%s PreKeys available',
|
|
| 77 | - self._storage.getPreKeyCount())
|
|
| 78 | - |
|
| 79 | - self._device_store: dict[str, set[int]] = defaultdict(set)
|
|
| 80 | - self._muc_member_store: dict[str, set[str]] = defaultdict(set)
|
|
| 81 | - |
|
| 82 | - reg_id = self._storage.getLocalRegistrationId()
|
|
| 83 | - if reg_id is None:
|
|
| 84 | - raise ValueError('No own device found')
|
|
| 85 | - |
|
| 86 | - self._own_device = reg_id
|
|
| 87 | - self.add_device(self._own_jid, self._own_device)
|
|
| 88 | - self._log.info('Our device id: %s', self._own_device)
|
|
| 89 | - |
|
| 90 | - for jid, device in self._storage.getActiveDeviceTuples():
|
|
| 91 | - self._log.info('Load device from storage: %s - %s', jid, device)
|
|
| 92 | - self.add_device(jid, device)
|
|
| 93 | - |
|
| 94 | - def build_session(self,
|
|
| 95 | - jid: str,
|
|
| 96 | - device_id: int,
|
|
| 97 | - bundle: OMEMOBundle
|
|
| 98 | - ) -> None:
|
|
| 99 | - |
|
| 100 | - session = SessionBuilder(self._storage, self._storage, self._storage,
|
|
| 101 | - self._storage, jid, device_id)
|
|
| 102 | - |
|
| 103 | - registration_id = self._storage.getLocalRegistrationId()
|
|
| 104 | - |
|
| 105 | - prekey = bundle.pick_prekey()
|
|
| 106 | - otpk = CurvePublicKey(prekey['key'][1:])
|
|
| 107 | - |
|
| 108 | - spk = CurvePublicKey(bundle.spk['key'][1:])
|
|
| 109 | - ik = IdentityKey(CurvePublicKey(bundle.ik[1:]))
|
|
| 110 | - |
|
| 111 | - prekey_bundle = PreKeyBundle(registration_id,
|
|
| 112 | - device_id,
|
|
| 113 | - prekey['id'],
|
|
| 114 | - otpk,
|
|
| 115 | - bundle.spk['id'],
|
|
| 116 | - spk,
|
|
| 117 | - bundle.spk_signature,
|
|
| 118 | - ik)
|
|
| 119 | - |
|
| 120 | - session.processPreKeyBundle(prekey_bundle)
|
|
| 121 | - self._get_session_cipher(jid, device_id)
|
|
| 122 | - |
|
| 123 | - @property
|
|
| 124 | - def storage(self) -> OMEMOStorage:
|
|
| 125 | - return self._storage
|
|
| 126 | - |
|
| 127 | - @property
|
|
| 128 | - def own_fingerprint(self) -> str:
|
|
| 129 | - return get_fingerprint(self._storage.getIdentityKeyPair())
|
|
| 130 | - |
|
| 131 | - @property
|
|
| 132 | - def bundle(self) -> OMEMOBundle:
|
|
| 133 | - self._check_pre_key_count()
|
|
| 134 | - |
|
| 135 | - bundle: dict[str, Any] = {'otpks': []}
|
|
| 136 | - for k in self._storage.loadPendingPreKeys():
|
|
| 137 | - key = k.getKeyPair().getPublicKey().serialize()
|
|
| 138 | - bundle['otpks'].append({'key': key, 'id': k.getId()})
|
|
| 139 | - |
|
| 140 | - ik_pair = self._storage.getIdentityKeyPair()
|
|
| 141 | - bundle['ik'] = ik_pair.getPublicKey().serialize()
|
|
| 142 | - |
|
| 143 | - self._cycle_signed_pre_key(ik_pair)
|
|
| 144 | - |
|
| 145 | - spk = self._storage.loadSignedPreKey(
|
|
| 146 | - self._storage.getCurrentSignedPreKeyId())
|
|
| 147 | - bundle['spk_signature'] = spk.getSignature()
|
|
| 148 | - bundle['spk'] = {'key': spk.getKeyPair().getPublicKey().serialize(),
|
|
| 149 | - 'id': spk.getId()}
|
|
| 150 | - |
|
| 151 | - return OMEMOBundle(**bundle)
|
|
| 152 | - |
|
| 153 | - def decrypt_message(self,
|
|
| 154 | - omemo_message: OMEMOMessage,
|
|
| 155 | - jid: str
|
|
| 156 | - ) -> tuple[str, str, Trust]:
|
|
| 157 | - |
|
| 158 | - if omemo_message.sid == self.own_device:
|
|
| 159 | - self._log.info('Received previously sent message by us')
|
|
| 160 | - raise SelfMessage
|
|
| 161 | - |
|
| 162 | - try:
|
|
| 163 | - encrypted_key, prekey = omemo_message.keys[self.own_device]
|
|
| 164 | - except KeyError:
|
|
| 165 | - self._log.info('Received message not for our device')
|
|
| 166 | - raise MessageNotForDevice
|
|
| 167 | - |
|
| 168 | - try:
|
|
| 169 | - if prekey:
|
|
| 170 | - key, fingerprint, trust = self._process_pre_key_message(
|
|
| 171 | - jid, omemo_message.sid, encrypted_key)
|
|
| 172 | - else:
|
|
| 173 | - key, fingerprint, trust = self._process_message(
|
|
| 174 | - jid, omemo_message.sid, encrypted_key)
|
|
| 175 | - |
|
| 176 | - except DuplicateMessageException:
|
|
| 177 | - self._log.info('Received duplicated message')
|
|
| 178 | - raise DuplicateMessage
|
|
| 179 | - |
|
| 180 | - except Exception as error:
|
|
| 181 | - self._log.warning(error)
|
|
| 182 | - raise DecryptionFailed
|
|
| 183 | - |
|
| 184 | - if omemo_message.payload is None:
|
|
| 185 | - self._log.debug('Decrypted Key Exchange Message')
|
|
| 186 | - raise KeyExchangeMessage
|
|
| 187 | - |
|
| 188 | - try:
|
|
| 189 | - result = aes_decrypt(key, omemo_message.iv, omemo_message.payload)
|
|
| 190 | - except Exception as error:
|
|
| 191 | - self._log.warning(error)
|
|
| 192 | - raise DecryptionFailed
|
|
| 193 | - |
|
| 194 | - self._log.debug('Decrypted Message => %s', result)
|
|
| 195 | - return result, fingerprint, trust
|
|
| 196 | - |
|
| 197 | - def _get_whisper_message(self,
|
|
| 198 | - jid: str,
|
|
| 199 | - device: int,
|
|
| 200 | - key: bytes
|
|
| 201 | - ) -> tuple[bytes, bool]:
|
|
| 202 | - |
|
| 203 | - cipher = self._get_session_cipher(jid, device)
|
|
| 204 | - cipher_key = cipher.encrypt(key)
|
|
| 205 | - prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
|
| 206 | - return cipher_key.serialize(), prekey
|
|
| 207 | - |
|
| 208 | - def encrypt(self, jid: str, plaintext: str) -> Optional[OMEMOMessage]:
|
|
| 209 | - try:
|
|
| 210 | - devices_for_encryption = self.get_devices_for_encryption(jid)
|
|
| 211 | - except NoDevicesFound:
|
|
| 212 | - self._log.warning('No devices for encryption found for: %s', jid)
|
|
| 213 | - return
|
|
| 214 | - |
|
| 215 | - result = aes_encrypt(plaintext)
|
|
| 216 | - whisper_messages: dict[
|
|
| 217 | - str, dict[int, tuple[bytes, bool]]] = defaultdict(dict)
|
|
| 218 | - |
|
| 219 | - for jid_, device in devices_for_encryption:
|
|
| 220 | - count = self._storage.getUnacknowledgedCount(jid_, device)
|
|
| 221 | - if count >= UNACKNOWLEDGED_COUNT:
|
|
| 222 | - self._log.warning('Set device inactive %s because of %s '
|
|
| 223 | - 'unacknowledged messages', device, count)
|
|
| 224 | - self.remove_device(jid_, device)
|
|
| 225 | - |
|
| 226 | - try:
|
|
| 227 | - whisper_messages[jid_][device] = self._get_whisper_message(
|
|
| 228 | - jid_, device, result.key)
|
|
| 229 | - except Exception:
|
|
| 230 | - self._log.exception('Failed to encrypt')
|
|
| 231 | - continue
|
|
| 232 | - |
|
| 233 | - recipients = set(whisper_messages.keys())
|
|
| 234 | - if jid != self._own_jid:
|
|
| 235 | - recipients -= {self._own_jid}
|
|
| 236 | - if not recipients:
|
|
| 237 | - self._log.error('Encrypted keys empty')
|
|
| 238 | - return
|
|
| 239 | - |
|
| 240 | - encrypted_keys: dict[int, tuple[bytes, bool]] = {}
|
|
| 241 | - for jid_ in whisper_messages:
|
|
| 242 | - encrypted_keys.update(whisper_messages[jid_])
|
|
| 243 | - |
|
| 244 | - self._log.debug('Finished encrypting message')
|
|
| 245 | - return OMEMOMessage(sid=self.own_device,
|
|
| 246 | - keys=encrypted_keys,
|
|
| 247 | - iv=result.iv,
|
|
| 248 | - payload=result.payload)
|
|
| 249 | - |
|
| 250 | - def encrypt_key_transport(self,
|
|
| 251 | - jid: str,
|
|
| 252 | - devices: list[int]
|
|
| 253 | - ) -> Optional[OMEMOMessage]:
|
|
| 254 | - |
|
| 255 | - whisper_messages: dict[
|
|
| 256 | - str, dict[int, tuple[bytes, bool]]] = defaultdict(dict)
|
|
| 257 | - for device in devices:
|
|
| 258 | - try:
|
|
| 259 | - whisper_messages[jid][device] = self._get_whisper_message(
|
|
| 260 | - jid, device, get_new_key())
|
|
| 261 | - except Exception:
|
|
| 262 | - self._log.exception('Failed to encrypt')
|
|
| 263 | - continue
|
|
| 264 | - |
|
| 265 | - if not whisper_messages[jid]:
|
|
| 266 | - self._log.error('Encrypted keys empty')
|
|
| 267 | - return
|
|
| 268 | - |
|
| 269 | - self._log.debug('Finished Key Transport message')
|
|
| 270 | - return OMEMOMessage(sid=self.own_device,
|
|
| 271 | - keys=whisper_messages[jid],
|
|
| 272 | - iv=get_new_iv(),
|
|
| 273 | - payload=None)
|
|
| 274 | - |
|
| 275 | - def has_trusted_keys(self, jid: str) -> bool:
|
|
| 276 | - inactive = self._storage.getInactiveSessionsKeys(jid)
|
|
| 277 | - trusted = self._storage.getTrustedFingerprints(jid)
|
|
| 278 | - return bool(set(trusted) - set(inactive))
|
|
| 279 | - |
|
| 280 | - def devices_without_sessions(self, jid: str) -> list[int]:
|
|
| 281 | - known_devices = self.get_devices(jid, without_self=True)
|
|
| 282 | - missing_devices = [dev
|
|
| 283 | - for dev in known_devices
|
|
| 284 | - if not self._storage.containsSession(jid, dev)]
|
|
| 285 | - if missing_devices:
|
|
| 286 | - self._log.info('Missing device sessions for %s: %s',
|
|
| 287 | - jid, missing_devices)
|
|
| 288 | - return missing_devices
|
|
| 289 | - |
|
| 290 | - def _get_session_cipher(self, jid: str, device_id: int) -> SessionCipher:
|
|
| 291 | - try:
|
|
| 292 | - return self._session_ciphers[jid][device_id]
|
|
| 293 | - except KeyError:
|
|
| 294 | - cipher = SessionCipher(self._storage, self._storage, self._storage,
|
|
| 295 | - self._storage, jid, device_id)
|
|
| 296 | - self._session_ciphers[jid][device_id] = cipher
|
|
| 297 | - return cipher
|
|
| 298 | - |
|
| 299 | - def _process_pre_key_message(self,
|
|
| 300 | - jid: str,
|
|
| 301 | - device: int,
|
|
| 302 | - key: bytes
|
|
| 303 | - ) -> tuple[bytes, str, Trust]:
|
|
| 304 | - |
|
| 305 | - self._log.info('Process pre key message from %s', jid)
|
|
| 306 | - pre_key_message = PreKeyWhisperMessage.from_bytes(key)
|
|
| 307 | - if not pre_key_message.getPreKeyId():
|
|
| 308 | - raise Exception('Received Pre Key Message '
|
|
| 309 | - 'without PreKey => %s' % jid)
|
|
| 310 | - |
|
| 311 | - session_cipher = self._get_session_cipher(jid, device)
|
|
| 312 | - key = session_cipher.decryptPkmsg(pre_key_message)
|
|
| 313 | - |
|
| 314 | - identity_key = pre_key_message.getIdentityKey()
|
|
| 315 | - trust = self._get_trust_from_identity_key(jid, identity_key)
|
|
| 316 | - fingerprint = get_fingerprint(identity_key)
|
|
| 317 | - |
|
| 318 | - self._storage.setIdentityLastSeen(jid, identity_key)
|
|
| 319 | - |
|
| 320 | - self.xmpp_con.set_bundle()
|
|
| 321 | - self.add_device(jid, device)
|
|
| 322 | - return key, fingerprint, trust
|
|
| 323 | - |
|
| 324 | - def _process_message(self,
|
|
| 325 | - jid: str,
|
|
| 326 | - device: int,
|
|
| 327 | - key: bytes
|
|
| 328 | - ) -> tuple[bytes, str, Trust]:
|
|
| 329 | - |
|
| 330 | - self._log.info('Process message from %s', jid)
|
|
| 331 | - message = WhisperMessage.from_bytes(key)
|
|
| 332 | - |
|
| 333 | - session_cipher = self._get_session_cipher(jid, device)
|
|
| 334 | - key = session_cipher.decryptMsg(message)
|
|
| 335 | - |
|
| 336 | - identity_key = self._get_identity_key_from_device(jid, device)
|
|
| 337 | - trust = self._get_trust_from_identity_key(jid, identity_key)
|
|
| 338 | - fingerprint = get_fingerprint(identity_key)
|
|
| 339 | - |
|
| 340 | - self._storage.setIdentityLastSeen(jid, identity_key)
|
|
| 341 | - |
|
| 342 | - self.add_device(jid, device)
|
|
| 343 | - |
|
| 344 | - return key, fingerprint, trust
|
|
| 345 | - |
|
| 346 | - @staticmethod
|
|
| 347 | - def _get_identity_key_from_pk_message(key):
|
|
| 348 | - pre_key_message = PreKeyWhisperMessage.from_bytes(key)
|
|
| 349 | - return pre_key_message.getIdentityKey()
|
|
| 350 | - |
|
| 351 | - def _get_identity_key_from_device(self,
|
|
| 352 | - jid: str,
|
|
| 353 | - device: int
|
|
| 354 | - ) -> Optional[IdentityKey]:
|
|
| 355 | - |
|
| 356 | - session_record = self._storage.loadSession(jid, device)
|
|
| 357 | - return session_record.getSessionState().getRemoteIdentityKey()
|
|
| 358 | - |
|
| 359 | - def _get_trust_from_identity_key(self,
|
|
| 360 | - jid: str,
|
|
| 361 | - identity_key: IdentityKey
|
|
| 362 | - ) -> Trust:
|
|
| 363 | - |
|
| 364 | - trust = self._storage.getTrustForIdentity(jid, identity_key)
|
|
| 365 | - return Trust(trust) if trust is not None else Trust.UNDECIDED
|
|
| 366 | - |
|
| 367 | - def _check_pre_key_count(self) -> None:
|
|
| 368 | - # Check if enough PreKeys are available
|
|
| 369 | - pre_key_count = self._storage.getPreKeyCount()
|
|
| 370 | - if pre_key_count < MIN_PREKEY_AMOUNT:
|
|
| 371 | - missing_count = DEFAULT_PREKEY_AMOUNT - pre_key_count
|
|
| 372 | - self._storage.generateNewPreKeys(missing_count)
|
|
| 373 | - self._log.info('%s PreKeys created', missing_count)
|
|
| 374 | - |
|
| 375 | - def _cycle_signed_pre_key(self, ik_pair: IdentityKeyPair) -> None:
|
|
| 376 | - # Publish every SPK_CYCLE_TIME a new SignedPreKey
|
|
| 377 | - # Delete all existing SignedPreKeys that are older
|
|
| 378 | - # then SPK_ARCHIVE_TIME
|
|
| 379 | - |
|
| 380 | - # Check if SignedPreKey exist and create if not
|
|
| 381 | - if not self._storage.getCurrentSignedPreKeyId():
|
|
| 382 | - spk = KeyHelper.generateSignedPreKey(
|
|
| 383 | - ik_pair, self._storage.getNextSignedPreKeyId())
|
|
| 384 | - self._storage.storeSignedPreKey(spk.getId(), spk)
|
|
| 385 | - self._log.debug('New SignedPreKey created, because none existed')
|
|
| 386 | - |
|
| 387 | - # if SPK_CYCLE_TIME is reached, generate a new SignedPreKey
|
|
| 388 | - now = int(time.time())
|
|
| 389 | - timestamp = self._storage.getSignedPreKeyTimestamp(
|
|
| 390 | - self._storage.getCurrentSignedPreKeyId())
|
|
| 391 | - |
|
| 392 | - if int(timestamp) < now - SPK_CYCLE_TIME:
|
|
| 393 | - spk = KeyHelper.generateSignedPreKey(
|
|
| 394 | - ik_pair, self._storage.getNextSignedPreKeyId())
|
|
| 395 | - self._storage.storeSignedPreKey(spk.getId(), spk)
|
|
| 396 | - self._log.debug('Cycled SignedPreKey')
|
|
| 397 | - |
|
| 398 | - # Delete all SignedPreKeys that are older than SPK_ARCHIVE_TIME
|
|
| 399 | - timestamp = now - SPK_ARCHIVE_TIME
|
|
| 400 | - self._storage.removeOldSignedPreKeys(timestamp)
|
|
| 401 | - |
|
| 402 | - def update_devicelist(self, jid: str, devicelist: list[int]) -> None:
|
|
| 403 | - for device in list(devicelist):
|
|
| 404 | - if device == self.own_device:
|
|
| 405 | - continue
|
|
| 406 | - count = self._storage.getUnacknowledgedCount(jid, device)
|
|
| 407 | - if count > UNACKNOWLEDGED_COUNT:
|
|
| 408 | - self._log.warning('Ignore device because of %s unacknowledged'
|
|
| 409 | - ' messages: %s %s', count, jid, device)
|
|
| 410 | - devicelist.remove(device)
|
|
| 411 | - |
|
| 412 | - self._device_store[jid] = set(devicelist)
|
|
| 413 | - self._log.info('Saved devices for %s', jid)
|
|
| 414 | - self._storage.setActiveState(jid, devicelist)
|
|
| 415 | - |
|
| 416 | - def add_muc_member(self, room_jid: str, jid: str) -> None:
|
|
| 417 | - self._log.info('Saved MUC member %s %s', room_jid, jid)
|
|
| 418 | - self._muc_member_store[room_jid].add(jid)
|
|
| 419 | - |
|
| 420 | - def remove_muc_member(self, room_jid: str, jid: str) -> None:
|
|
| 421 | - self._log.info('Removed MUC member %s %s', room_jid, jid)
|
|
| 422 | - self._muc_member_store[room_jid].discard(jid)
|
|
| 423 | - |
|
| 424 | - def get_muc_members(self,
|
|
| 425 | - room_jid: str,
|
|
| 426 | - without_self: bool = True
|
|
| 427 | - ) -> set[str]:
|
|
| 428 | - |
|
| 429 | - members = set(self._muc_member_store[room_jid])
|
|
| 430 | - if without_self:
|
|
| 431 | - members.discard(self._own_jid)
|
|
| 432 | - return members
|
|
| 433 | - |
|
| 434 | - def add_device(self, jid: str, device: int) -> None:
|
|
| 435 | - self._device_store[jid].add(device)
|
|
| 436 | - |
|
| 437 | - def remove_device(self, jid: str, device: int) -> None:
|
|
| 438 | - self._device_store[jid].discard(device)
|
|
| 439 | - self._storage.setInactive(jid, device)
|
|
| 440 | - |
|
| 441 | - def get_devices(self, jid: str, without_self: bool = False) -> set[int]:
|
|
| 442 | - devices = set(self._device_store[jid])
|
|
| 443 | - if without_self:
|
|
| 444 | - devices.discard(self.own_device)
|
|
| 445 | - return devices
|
|
| 446 | - |
|
| 447 | - def get_devices_for_encryption(self, jid: str) -> set[tuple[str, int]]:
|
|
| 448 | - devices_for_encryption: list[tuple[str, int]] = []
|
|
| 449 | - |
|
| 450 | - client = app.get_client(self._account)
|
|
| 451 | - contact = client.get_module('Contacts').get_contact(jid)
|
|
| 452 | - if contact.is_groupchat:
|
|
| 453 | - devices_for_encryption = self._get_devices_for_muc_encryption(jid)
|
|
| 454 | - else:
|
|
| 455 | - devices_for_encryption = self._get_devices_for_encryption(jid)
|
|
| 456 | - |
|
| 457 | - if not devices_for_encryption:
|
|
| 458 | - raise NoDevicesFound
|
|
| 459 | - |
|
| 460 | - devices_for_encryption += self._get_own_devices_for_encryption()
|
|
| 461 | - return set(devices_for_encryption)
|
|
| 462 | - |
|
| 463 | - def _get_devices_for_muc_encryption(self,
|
|
| 464 | - jid: str
|
|
| 465 | - ) -> list[tuple[str, int]]:
|
|
| 466 | - |
|
| 467 | - devices_for_encryption: list[tuple[str, int]] = []
|
|
| 468 | - for jid_ in self._muc_member_store[jid]:
|
|
| 469 | - devices_for_encryption += self._get_devices_for_encryption(jid_)
|
|
| 470 | - return devices_for_encryption
|
|
| 471 | - |
|
| 472 | - def _get_own_devices_for_encryption(self) -> list[tuple[str, int]]:
|
|
| 473 | - devices_for_encryption: list[tuple[str, int]] = []
|
|
| 474 | - own_devices = self.get_devices(self._own_jid, without_self=True)
|
|
| 475 | - for device in own_devices:
|
|
| 476 | - if self._storage.isTrusted(self._own_jid, device):
|
|
| 477 | - devices_for_encryption.append((self._own_jid, device))
|
|
| 478 | - return devices_for_encryption
|
|
| 479 | - |
|
| 480 | - def _get_devices_for_encryption(self, jid: str) -> list[tuple[str, int]]:
|
|
| 481 | - devices_for_encryption: list[tuple[str, int]] = []
|
|
| 482 | - devices = self.get_devices(jid)
|
|
| 483 | - |
|
| 484 | - for device in devices:
|
|
| 485 | - if self._storage.isTrusted(jid, device):
|
|
| 486 | - devices_for_encryption.append((jid, device))
|
|
| 487 | - return devices_for_encryption
|
|
| 488 | - |
|
| 489 | - @property
|
|
| 490 | - def own_device(self) -> int:
|
|
| 491 | - return self._own_device
|
|
| 492 | - |
|
| 493 | - @property
|
|
| 494 | - def devices_for_publish(self) -> set[int]:
|
|
| 495 | - devices = self.get_devices(self._own_jid)
|
|
| 496 | - if self.own_device not in devices:
|
|
| 497 | - devices.add(self.own_device)
|
|
| 498 | - return devices
|
|
| 499 | - |
|
| 500 | - @property
|
|
| 501 | - def is_own_device_published(self) -> bool:
|
|
| 502 | - return self.own_device in self.get_devices(self._own_jid)
|
|
| 503 | - |
|
| 504 | - |
|
| 505 | -class NoDevicesFound(Exception):
|
|
| 506 | - pass
|
|
| 507 | - |
|
| 508 | - |
|
| 509 | -class NoValidSessions(Exception):
|
|
| 510 | - pass
|
|
| 511 | - |
|
| 512 | - |
|
| 513 | -class SelfMessage(Exception):
|
|
| 514 | - pass
|
|
| 515 | - |
|
| 516 | - |
|
| 517 | -class MessageNotForDevice(Exception):
|
|
| 518 | - pass
|
|
| 519 | - |
|
| 520 | - |
|
| 521 | -class DecryptionFailed(Exception):
|
|
| 522 | - pass
|
|
| 523 | - |
|
| 524 | - |
|
| 525 | -class KeyExchangeMessage(Exception):
|
|
| 526 | - pass
|
|
| 527 | - |
|
| 528 | - |
|
| 529 | -class InvalidMessage(Exception):
|
|
| 530 | - pass
|
|
| 531 | - |
|
| 532 | - |
|
| 533 | -class DuplicateMessage(Exception):
|
|
| 534 | - pass |
| 1 | -# Copyright (C) 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
|
| 2 | -#
|
|
| 3 | -# This file is part of OMEMO Gajim Plugin.
|
|
| 4 | -#
|
|
| 5 | -# OMEMO Gajim Plugin is free software; you can redistribute it and/or modify
|
|
| 6 | -# it under the terms of the GNU General Public License as published
|
|
| 7 | -# by the Free Software Foundation; version 3 only.
|
|
| 8 | -#
|
|
| 9 | -# OMEMO Gajim Plugin is distributed in the hope that it will be useful,
|
|
| 10 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 11 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 12 | -# GNU General Public License for more details.
|
|
| 13 | -#
|
|
| 14 | -# You should have received a copy of the GNU General Public License
|
|
| 15 | -# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
|
| 16 | - |
|
| 17 | -from __future__ import annotations
|
|
| 18 | - |
|
| 19 | -import binascii
|
|
| 20 | -import textwrap
|
|
| 21 | -from enum import IntEnum
|
|
| 22 | - |
|
| 23 | -from omemo_dr.identitykey import IdentityKey
|
|
| 24 | -from omemo_dr.identitykeypair import IdentityKeyPair
|
|
| 25 | - |
|
| 26 | -DEFAULT_PREKEY_AMOUNT = 100
|
|
| 27 | -MIN_PREKEY_AMOUNT = 80
|
|
| 28 | -SPK_ARCHIVE_TIME = 86400 * 15 # 15 Days
|
|
| 29 | -SPK_CYCLE_TIME = 86400 # 24 Hours
|
|
| 30 | -UNACKNOWLEDGED_COUNT = 2000
|
|
| 31 | - |
|
| 32 | - |
|
| 33 | -class Trust(IntEnum):
|
|
| 34 | - UNTRUSTED = 0
|
|
| 35 | - VERIFIED = 1
|
|
| 36 | - UNDECIDED = 2
|
|
| 37 | - BLIND = 3
|
|
| 38 | - |
|
| 39 | - |
|
| 40 | -def get_fingerprint(identity_key: IdentityKeyPair,
|
|
| 41 | - formatted: bool = False
|
|
| 42 | - ) -> str:
|
|
| 43 | - |
|
| 44 | - public_key = identity_key.getPublicKey().serialize()
|
|
| 45 | - fingerprint = binascii.hexlify(public_key).decode()[2:]
|
|
| 46 | - if not formatted:
|
|
| 47 | - return fingerprint
|
|
| 48 | - fplen = len(fingerprint)
|
|
| 49 | - wordsize = fplen // 8
|
|
| 50 | - buf = ''
|
|
| 51 | - for word in range(0, fplen, wordsize):
|
|
| 52 | - buf += '{0} '.format(fingerprint[word:word + wordsize])
|
|
| 53 | - buf = textwrap.fill(buf, width=36)
|
|
| 54 | - return buf.rstrip().upper()
|
|
| 55 | - |
|
| 56 | - |
|
| 57 | -class IdentityKeyExtended(IdentityKey):
|
|
| 58 | - def __hash__(self) -> int:
|
|
| 59 | - return hash(self.publicKey.serialize())
|
|
| 60 | - |
|
| 61 | - def get_fingerprint(self, formatted: bool = False) -> str:
|
|
| 62 | - return get_fingerprint(self, formatted=formatted) |
| ... | ... | @@ -26,29 +26,26 @@ |
| 26 | 26 | from collections import namedtuple
|
| 27 | 27 | from pathlib import Path
|
| 28 | 28 | |
| 29 | +from omemo_dr.const import OMEMOTrust
|
|
| 29 | 30 | from omemo_dr.ecc.djbec import CurvePublicKey
|
| 30 | 31 | from omemo_dr.ecc.djbec import DjbECPrivateKey
|
| 31 | 32 | from omemo_dr.exceptions import InvalidKeyIdException
|
| 32 | 33 | from omemo_dr.identitykey import IdentityKey
|
| 33 | 34 | from omemo_dr.identitykeypair import IdentityKeyPair
|
| 34 | -from omemo_dr.state.axolotlstore import AxolotlStore
|
|
| 35 | 35 | from omemo_dr.state.prekeyrecord import PreKeyRecord
|
| 36 | 36 | from omemo_dr.state.sessionrecord import SessionRecord
|
| 37 | 37 | from omemo_dr.state.signedprekeyrecord import SignedPreKeyRecord
|
| 38 | -from omemo_dr.util.keyhelper import KeyHelper
|
|
| 38 | +from omemo_dr.state.store import Store
|
|
| 39 | 39 | from omemo_dr.util.medium import Medium
|
| 40 | 40 | |
| 41 | 41 | from gajim.common import app
|
| 42 | 42 | from gajim.common.modules.util import LogAdapter
|
| 43 | -from gajim.common.omemo.util import DEFAULT_PREKEY_AMOUNT
|
|
| 44 | -from gajim.common.omemo.util import IdentityKeyExtended
|
|
| 45 | -from gajim.common.omemo.util import Trust
|
|
| 46 | 43 | |
| 47 | 44 | |
| 48 | -def _convert_identity_key(key: bytes) -> Optional[IdentityKeyExtended]:
|
|
| 45 | +def _convert_identity_key(key: bytes) -> Optional[IdentityKey]:
|
|
| 49 | 46 | if not key:
|
| 50 | 47 | return
|
| 51 | - return IdentityKeyExtended(CurvePublicKey(key[1:]))
|
|
| 48 | + return IdentityKey(CurvePublicKey(key[1:]))
|
|
| 52 | 49 | |
| 53 | 50 | |
| 54 | 51 | def _convert_record(record: bytes) -> SessionRecord:
|
| ... | ... | @@ -59,15 +56,15 @@ def _convert_record(record: bytes) -> SessionRecord: |
| 59 | 56 | sqlite3.register_converter('session_record', _convert_record)
|
| 60 | 57 | |
| 61 | 58 | |
| 62 | -class OMEMOStorage(AxolotlStore):
|
|
| 59 | +class OMEMOStorage(Store):
|
|
| 63 | 60 | def __init__(self, account: str, db_path: Path, log: LogAdapter) -> None:
|
| 64 | 61 | self._log = log
|
| 65 | 62 | self._account = account
|
| 66 | 63 | self._con = sqlite3.connect(db_path,
|
| 67 | 64 | detect_types=sqlite3.PARSE_COLNAMES)
|
| 68 | 65 | self._con.row_factory = self._namedtuple_factory
|
| 69 | - self.createDb()
|
|
| 70 | - self.migrateDb()
|
|
| 66 | + self.create_db()
|
|
| 67 | + self.migrate_db()
|
|
| 71 | 68 | |
| 72 | 69 | self._con.execute('PRAGMA secure_delete=1')
|
| 73 | 70 | self._con.execute('PRAGMA synchronous=NORMAL;')
|
| ... | ... | @@ -78,10 +75,6 @@ def __init__(self, account: str, db_path: Path, log: LogAdapter) -> None: |
| 78 | 75 | self._con.execute('PRAGMA journal_mode=MEMORY;')
|
| 79 | 76 | self._con.commit()
|
| 80 | 77 | |
| 81 | - if not self.getLocalRegistrationId():
|
|
| 82 | - self._log.info('Generating OMEMO keys')
|
|
| 83 | - self._generate_axolotl_keys()
|
|
| 84 | - |
|
| 85 | 78 | def _is_blind_trust_enabled(self) -> bool:
|
| 86 | 79 | return app.settings.get_account_setting(self._account,
|
| 87 | 80 | 'omemo_blind_trust')
|
| ... | ... | @@ -105,26 +98,10 @@ def _namedtuple_factory(cursor: sqlite3.Cursor, |
| 105 | 98 | fields.append(col[0])
|
| 106 | 99 | return namedtuple('Row', fields)(*row) # pyright: ignore
|
| 107 | 100 | |
| 108 | - def _generate_axolotl_keys(self) -> None:
|
|
| 109 | - identity_key_pair = KeyHelper.generateIdentityKeyPair()
|
|
| 110 | - registration_id = KeyHelper.getRandomSequence(2147483647)
|
|
| 111 | - pre_keys = KeyHelper.generatePreKeys(
|
|
| 112 | - KeyHelper.getRandomSequence(4294967296),
|
|
| 113 | - DEFAULT_PREKEY_AMOUNT)
|
|
| 114 | - self.storeLocalData(registration_id, identity_key_pair)
|
|
| 115 | - |
|
| 116 | - signed_pre_key = KeyHelper.generateSignedPreKey(
|
|
| 117 | - identity_key_pair, KeyHelper.getRandomSequence(65536))
|
|
| 118 | - |
|
| 119 | - self.storeSignedPreKey(signed_pre_key.getId(), signed_pre_key)
|
|
| 120 | - |
|
| 121 | - for pre_key in pre_keys:
|
|
| 122 | - self.storePreKey(pre_key.getId(), pre_key)
|
|
| 123 | - |
|
| 124 | 101 | def user_version(self) -> int:
|
| 125 | 102 | return self._con.execute('PRAGMA user_version').fetchone()[0]
|
| 126 | 103 | |
| 127 | - def createDb(self) -> None:
|
|
| 104 | + def create_db(self) -> None:
|
|
| 128 | 105 | if self.user_version() == 0:
|
| 129 | 106 | |
| 130 | 107 | create_tables = '''
|
| ... | ... | @@ -166,7 +143,7 @@ def createDb(self) -> None: |
| 166 | 143 | ''' % (create_tables)
|
| 167 | 144 | self._con.executescript(create_db_sql)
|
| 168 | 145 | |
| 169 | - def migrateDb(self) -> None:
|
|
| 146 | + def migrate_db(self) -> None:
|
|
| 170 | 147 | ''' Migrates the DB
|
| 171 | 148 | '''
|
| 172 | 149 | |
| ... | ... | @@ -384,131 +361,130 @@ def migrateDb(self) -> None: |
| 384 | 361 | self._con.execute('PRAGMA user_version=12')
|
| 385 | 362 | self._con.commit()
|
| 386 | 363 | |
| 387 | - def loadSignedPreKey(self, signedPreKeyId: int) -> SignedPreKeyRecord:
|
|
| 364 | + def load_signed_pre_key(self, signed_pre_key_id: int) -> SignedPreKeyRecord:
|
|
| 388 | 365 | query = 'SELECT record FROM signed_prekeys WHERE prekey_id = ?'
|
| 389 | - result = self._con.execute(query, (signedPreKeyId, )).fetchone()
|
|
| 366 | + result = self._con.execute(query, (signed_pre_key_id, )).fetchone()
|
|
| 390 | 367 | if result is None:
|
| 391 | 368 | raise InvalidKeyIdException('No such signedprekeyrecord! %s ' %
|
| 392 | - signedPreKeyId)
|
|
| 369 | + signed_pre_key_id)
|
|
| 393 | 370 | return SignedPreKeyRecord.from_bytes(result.record)
|
| 394 | 371 | |
| 395 | - def loadSignedPreKeys(self) -> list[SignedPreKeyRecord]:
|
|
| 372 | + def load_signed_pre_keys(self) -> list[SignedPreKeyRecord]:
|
|
| 396 | 373 | query = 'SELECT record FROM signed_prekeys'
|
| 397 | 374 | results = self._con.execute(query).fetchall()
|
| 398 | 375 | return [SignedPreKeyRecord.from_bytes(row.record) for row in results]
|
| 399 | 376 | |
| 400 | - def storeSignedPreKey(self,
|
|
| 401 | - signedPreKeyId: int,
|
|
| 402 | - signedPreKeyRecord: SignedPreKeyRecord
|
|
| 403 | - ) -> None:
|
|
| 377 | + def store_signed_pre_key(self,
|
|
| 378 | + signed_pre_key_id: int,
|
|
| 379 | + signed_pre_key_record: SignedPreKeyRecord
|
|
| 380 | + ) -> None:
|
|
| 404 | 381 | |
| 405 | 382 | query = 'INSERT INTO signed_prekeys (prekey_id, record) VALUES(?,?)'
|
| 406 | - self._con.execute(query, (signedPreKeyId,
|
|
| 407 | - signedPreKeyRecord.serialize()))
|
|
| 383 | + self._con.execute(query, (signed_pre_key_id,
|
|
| 384 | + signed_pre_key_record.serialize()))
|
|
| 408 | 385 | self._con.commit()
|
| 409 | 386 | |
| 410 | - def containsSignedPreKey(self, signedPreKeyId: int) -> bool:
|
|
| 387 | + def contains_signed_pre_key(self, signed_pre_key_id: int) -> bool:
|
|
| 411 | 388 | query = 'SELECT record FROM signed_prekeys WHERE prekey_id = ?'
|
| 412 | - result = self._con.execute(query, (signedPreKeyId,)).fetchone()
|
|
| 389 | + result = self._con.execute(query, (signed_pre_key_id,)).fetchone()
|
|
| 413 | 390 | return result is not None
|
| 414 | 391 | |
| 415 | - def removeSignedPreKey(self, signedPreKeyId: int) -> None:
|
|
| 392 | + def remove_signed_pre_key(self, signed_pre_key_id: int) -> None:
|
|
| 416 | 393 | query = 'DELETE FROM signed_prekeys WHERE prekey_id = ?'
|
| 417 | - self._con.execute(query, (signedPreKeyId,))
|
|
| 394 | + self._con.execute(query, (signed_pre_key_id,))
|
|
| 418 | 395 | self._con.commit()
|
| 419 | 396 | |
| 420 | - def getNextSignedPreKeyId(self) -> int:
|
|
| 421 | - result = self.getCurrentSignedPreKeyId()
|
|
| 422 | - if result is None:
|
|
| 423 | - return 1 # StartId if no SignedPreKeys exist
|
|
| 397 | + def get_next_signed_pre_key_id(self) -> int:
|
|
| 398 | + result = self.get_current_signed_pre_key_id()
|
|
| 424 | 399 | return (result % (Medium.MAX_VALUE - 1)) + 1
|
| 425 | 400 | |
| 426 | - def getCurrentSignedPreKeyId(self) -> Optional[int]:
|
|
| 401 | + def get_current_signed_pre_key_id(self) -> int:
|
|
| 427 | 402 | query = 'SELECT MAX(prekey_id) FROM signed_prekeys'
|
| 428 | 403 | result = self._con.execute(query).fetchone()
|
| 429 | - return result.max_prekey_id if result is not None else None
|
|
| 404 | + assert result is not None
|
|
| 405 | + return result.max_prekey_id
|
|
| 430 | 406 | |
| 431 | - def getSignedPreKeyTimestamp(self, signedPreKeyId: int) -> int:
|
|
| 407 | + def get_signed_pre_key_timestamp(self, signed_pre_key_id: int) -> int:
|
|
| 432 | 408 | query = '''SELECT strftime('%s', timestamp) FROM
|
| 433 | 409 | signed_prekeys WHERE prekey_id = ?'''
|
| 434 | 410 | |
| 435 | - result = self._con.execute(query, (signedPreKeyId,)).fetchone()
|
|
| 411 | + result = self._con.execute(query, (signed_pre_key_id,)).fetchone()
|
|
| 436 | 412 | if result is None:
|
| 437 | 413 | raise InvalidKeyIdException('No such signedprekeyrecord! %s' %
|
| 438 | - signedPreKeyId)
|
|
| 414 | + signed_pre_key_id)
|
|
| 439 | 415 | |
| 440 | 416 | return result.formated_time
|
| 441 | 417 | |
| 442 | - def removeOldSignedPreKeys(self, timestamp: int) -> None:
|
|
| 418 | + def remove_old_signed_pre_keys(self, timestamp: int) -> None:
|
|
| 443 | 419 | query = '''DELETE FROM signed_prekeys
|
| 444 | 420 | WHERE timestamp < datetime(?, "unixepoch")'''
|
| 445 | 421 | self._con.execute(query, (timestamp,))
|
| 446 | 422 | self._con.commit()
|
| 447 | 423 | |
| 448 | - def loadSession(self, recipientId: str, deviceId: int) -> SessionRecord:
|
|
| 424 | + def load_session(self, recipient_id: str, device_id: int) -> SessionRecord:
|
|
| 449 | 425 | query = '''SELECT record as "record [session_record]"
|
| 450 | 426 | FROM sessions WHERE recipient_id = ? AND device_id = ?'''
|
| 451 | - result = self._con.execute(query, (recipientId, deviceId)).fetchone()
|
|
| 427 | + result = self._con.execute(query, (recipient_id, device_id)).fetchone()
|
|
| 452 | 428 | return result.record if result is not None else SessionRecord()
|
| 453 | 429 | |
| 454 | - def getJidFromDevice(self, device_id: int) -> Optional[str]:
|
|
| 430 | + def get_jid_from_device(self, device_id: int) -> Optional[str]:
|
|
| 455 | 431 | query = '''SELECT recipient_id
|
| 456 | 432 | FROM sessions WHERE device_id = ?'''
|
| 457 | 433 | result = self._con.execute(query, (device_id, )).fetchone()
|
| 458 | 434 | return result.recipient_id if result is not None else None
|
| 459 | 435 | |
| 460 | - def getActiveDeviceTuples(self):
|
|
| 436 | + def get_active_device_tuples(self):
|
|
| 461 | 437 | query = '''SELECT recipient_id, device_id
|
| 462 | 438 | FROM sessions WHERE active = 1'''
|
| 463 | 439 | return self._con.execute(query).fetchall()
|
| 464 | 440 | |
| 465 | - def storeSession(self,
|
|
| 466 | - recipientId: str,
|
|
| 467 | - deviceId: int,
|
|
| 468 | - sessionRecord: SessionRecord
|
|
| 469 | - ) -> None:
|
|
| 441 | + def store_session(self,
|
|
| 442 | + recipient_id: str,
|
|
| 443 | + device_id: int,
|
|
| 444 | + session_record: SessionRecord
|
|
| 445 | + ) -> None:
|
|
| 470 | 446 | |
| 471 | 447 | query = '''INSERT INTO sessions(recipient_id, device_id, record)
|
| 472 | 448 | VALUES(?,?,?)'''
|
| 473 | 449 | try:
|
| 474 | - self._con.execute(query, (recipientId,
|
|
| 475 | - deviceId,
|
|
| 476 | - sessionRecord.serialize()))
|
|
| 450 | + self._con.execute(query, (recipient_id,
|
|
| 451 | + device_id,
|
|
| 452 | + session_record.serialize()))
|
|
| 477 | 453 | except sqlite3.IntegrityError:
|
| 478 | 454 | query = '''UPDATE sessions SET record = ?
|
| 479 | 455 | WHERE recipient_id = ? AND device_id = ?'''
|
| 480 | - self._con.execute(query, (sessionRecord.serialize(),
|
|
| 481 | - recipientId,
|
|
| 482 | - deviceId))
|
|
| 456 | + self._con.execute(query, (session_record.serialize(),
|
|
| 457 | + recipient_id,
|
|
| 458 | + device_id))
|
|
| 483 | 459 | |
| 484 | 460 | self._con.commit()
|
| 485 | 461 | |
| 486 | - def containsSession(self, recipientId: str, deviceId: int) -> bool:
|
|
| 462 | + def contains_session(self, recipient_id: str, device_id: int) -> bool:
|
|
| 487 | 463 | query = '''SELECT record FROM sessions
|
| 488 | 464 | WHERE recipient_id = ? AND device_id = ?'''
|
| 489 | - result = self._con.execute(query, (recipientId, deviceId)).fetchone()
|
|
| 465 | + result = self._con.execute(query, (recipient_id, device_id)).fetchone()
|
|
| 490 | 466 | return result is not None
|
| 491 | 467 | |
| 492 | - def deleteSession(self, recipientId: str, deviceId: int) -> None:
|
|
| 493 | - self._log.info('Delete session for %s %s', recipientId, deviceId)
|
|
| 468 | + def delete_session(self, recipient_id: str, device_id: int) -> None:
|
|
| 469 | + self._log.info('Delete session for %s %s', recipient_id, device_id)
|
|
| 494 | 470 | query = 'DELETE FROM sessions WHERE recipient_id = ? AND device_id = ?'
|
| 495 | - self._con.execute(query, (recipientId, deviceId))
|
|
| 471 | + self._con.execute(query, (recipient_id, device_id))
|
|
| 496 | 472 | self._con.commit()
|
| 497 | 473 | |
| 498 | - def deleteAllSessions(self, recipientId: str) -> None:
|
|
| 474 | + def delete_all_sessions(self, recipient_id: str) -> None:
|
|
| 499 | 475 | query = 'DELETE FROM sessions WHERE recipient_id = ?'
|
| 500 | - self._con.execute(query, (recipientId,))
|
|
| 476 | + self._con.execute(query, (recipient_id,))
|
|
| 501 | 477 | self._con.commit()
|
| 502 | 478 | |
| 503 | - def getSessionsFromJid(self, recipientId: str):
|
|
| 479 | + def get_sessions_from_jid(self, recipient_id: str):
|
|
| 504 | 480 | query = '''SELECT recipient_id,
|
| 505 | 481 | device_id,
|
| 506 | 482 | record as "record [session_record]",
|
| 507 | 483 | active
|
| 508 | 484 | FROM sessions WHERE recipient_id = ?'''
|
| 509 | - return self._con.execute(query, (recipientId,)).fetchall()
|
|
| 485 | + return self._con.execute(query, (recipient_id,)).fetchall()
|
|
| 510 | 486 | |
| 511 | - def getSessionsFromJids(self, recipientIds: list[str]):
|
|
| 487 | + def get_sessions_from_jids(self, recipient_ids: list[str]):
|
|
| 512 | 488 | query = '''
|
| 513 | 489 | SELECT recipient_id,
|
| 514 | 490 | device_id,
|
| ... | ... | @@ -516,86 +492,84 @@ def getSessionsFromJids(self, recipientIds: list[str]): |
| 516 | 492 | active
|
| 517 | 493 | FROM sessions
|
| 518 | 494 | WHERE recipient_id IN ({})'''.format(
|
| 519 | - ', '.join(['?'] * len(recipientIds)))
|
|
| 520 | - return self._con.execute(query, recipientIds).fetchall()
|
|
| 495 | + ', '.join(['?'] * len(recipient_ids)))
|
|
| 496 | + return self._con.execute(query, recipient_ids).fetchall()
|
|
| 521 | 497 | |
| 522 | - def setActiveState(self, jid: str, devicelist: list[int]) -> None:
|
|
| 498 | + def set_active_state(self, address: str, devicelist: list[int]) -> None:
|
|
| 523 | 499 | query = '''
|
| 524 | 500 | UPDATE sessions SET active = 1
|
| 525 | 501 | WHERE recipient_id = ? AND device_id IN ({})'''.format(
|
| 526 | 502 | ', '.join(['?'] * len(devicelist)))
|
| 527 | - self._con.execute(query, (jid,) + tuple(devicelist))
|
|
| 503 | + self._con.execute(query, (address,) + tuple(devicelist))
|
|
| 528 | 504 | |
| 529 | 505 | query = '''
|
| 530 | 506 | UPDATE sessions SET active = 0
|
| 531 | 507 | WHERE recipient_id = ? AND device_id NOT IN ({})'''.format(
|
| 532 | 508 | ', '.join(['?'] * len(devicelist)))
|
| 533 | - self._con.execute(query, (jid,) + tuple(devicelist))
|
|
| 509 | + self._con.execute(query, (address,) + tuple(devicelist))
|
|
| 534 | 510 | self._con.commit()
|
| 535 | 511 | |
| 536 | - def setInactive(self, jid: str, device_id: int) -> None:
|
|
| 512 | + def set_inactive(self, address: str, device_id: int) -> None:
|
|
| 537 | 513 | query = '''UPDATE sessions SET active = 0
|
| 538 | 514 | WHERE recipient_id = ? AND device_id = ?'''
|
| 539 | - self._con.execute(query, (jid, device_id))
|
|
| 515 | + self._con.execute(query, (address, device_id))
|
|
| 540 | 516 | self._con.commit()
|
| 541 | 517 | |
| 542 | - def getInactiveSessionsKeys(self,
|
|
| 543 | - recipientId: str
|
|
| 544 | - ) -> list[IdentityKeyExtended]:
|
|
| 518 | + def get_inactive_sessions_keys(self,
|
|
| 519 | + recipient_id: str
|
|
| 520 | + ) -> list[IdentityKey]:
|
|
| 545 | 521 | |
| 546 | 522 | query = '''SELECT record as "record [session_record]" FROM sessions
|
| 547 | 523 | WHERE active = 0 AND recipient_id = ?'''
|
| 548 | - results = self._con.execute(query, (recipientId,)).fetchall()
|
|
| 524 | + results = self._con.execute(query, (recipient_id,)).fetchall()
|
|
| 549 | 525 | |
| 550 | - keys: list[IdentityKeyExtended] = []
|
|
| 526 | + keys: list[IdentityKey] = []
|
|
| 551 | 527 | for result in results:
|
| 552 | - key = result.record.getSessionState().getRemoteIdentityKey()
|
|
| 553 | - keys.append(IdentityKeyExtended(key.getPublicKey()))
|
|
| 528 | + key = result.record.get_session_state().get_remote_identity_key()
|
|
| 529 | + keys.append(IdentityKey(key.get_public_key()))
|
|
| 554 | 530 | return keys
|
| 555 | 531 | |
| 556 | - def loadPreKey(self, preKeyId: int) -> PreKeyRecord:
|
|
| 532 | + def load_pre_key(self, pre_key_id: int) -> PreKeyRecord:
|
|
| 557 | 533 | query = '''SELECT record FROM prekeys WHERE prekey_id = ?'''
|
| 558 | 534 | |
| 559 | - result = self._con.execute(query, (preKeyId,)).fetchone()
|
|
| 535 | + result = self._con.execute(query, (pre_key_id,)).fetchone()
|
|
| 560 | 536 | if result is None:
|
| 561 | 537 | raise Exception('No such prekeyRecord!')
|
| 562 | 538 | return PreKeyRecord.from_bytes(result.record)
|
| 563 | 539 | |
| 564 | - def loadPendingPreKeys(self) -> list[PreKeyRecord]:
|
|
| 540 | + def load_pending_pre_keys(self) -> list[PreKeyRecord]:
|
|
| 565 | 541 | query = '''SELECT record FROM prekeys'''
|
| 566 | 542 | result = self._con.execute(query).fetchall()
|
| 567 | 543 | return [PreKeyRecord.from_bytes(row.record) for row in result]
|
| 568 | 544 | |
| 569 | - def storePreKey(self, preKeyId: int, preKeyRecord: PreKeyRecord) -> None:
|
|
| 545 | + def store_pre_key(self,
|
|
| 546 | + pre_key_id: int,
|
|
| 547 | + pre_key_record: PreKeyRecord
|
|
| 548 | + ) -> None:
|
|
| 570 | 549 | query = 'INSERT INTO prekeys (prekey_id, record) VALUES(?,?)'
|
| 571 | - self._con.execute(query, (preKeyId, preKeyRecord.serialize()))
|
|
| 550 | + self._con.execute(query, (pre_key_id, pre_key_record.serialize()))
|
|
| 572 | 551 | self._con.commit()
|
| 573 | 552 | |
| 574 | - def containsPreKey(self, preKeyId: int) -> bool:
|
|
| 553 | + def contains_pre_key(self, pre_key_id: int) -> bool:
|
|
| 575 | 554 | query = 'SELECT record FROM prekeys WHERE prekey_id = ?'
|
| 576 | - result = self._con.execute(query, (preKeyId,)).fetchone()
|
|
| 555 | + result = self._con.execute(query, (pre_key_id,)).fetchone()
|
|
| 577 | 556 | return result is not None
|
| 578 | 557 | |
| 579 | - def removePreKey(self, preKeyId: int) -> None:
|
|
| 558 | + def remove_pre_key(self, pre_key_id: int) -> None:
|
|
| 580 | 559 | query = 'DELETE FROM prekeys WHERE prekey_id = ?'
|
| 581 | - self._con.execute(query, (preKeyId,))
|
|
| 560 | + self._con.execute(query, (pre_key_id,))
|
|
| 582 | 561 | self._con.commit()
|
| 583 | 562 | |
| 584 | - def getCurrentPreKeyId(self) -> int:
|
|
| 563 | + def get_current_pre_key_id(self) -> Optional[int]:
|
|
| 585 | 564 | query = 'SELECT MAX(prekey_id) FROM prekeys'
|
| 586 | - return self._con.execute(query).fetchone().max_prekey_id
|
|
| 565 | + result = self._con.execute(query).fetchone()
|
|
| 566 | + return result.max_prekey_id if result is not None else None
|
|
| 587 | 567 | |
| 588 | - def getPreKeyCount(self) -> int:
|
|
| 568 | + def get_pre_key_count(self) -> int:
|
|
| 589 | 569 | query = 'SELECT COUNT(prekey_id) FROM prekeys'
|
| 590 | 570 | return self._con.execute(query).fetchone().count_prekey_id
|
| 591 | 571 | |
| 592 | - def generateNewPreKeys(self, count: int) -> None:
|
|
| 593 | - prekey_id = self.getCurrentPreKeyId() or 0
|
|
| 594 | - pre_keys = KeyHelper.generatePreKeys(prekey_id + 1, count)
|
|
| 595 | - for pre_key in pre_keys:
|
|
| 596 | - self.storePreKey(pre_key.getId(), pre_key)
|
|
| 597 | - |
|
| 598 | - def getIdentityKeyPair(self) -> IdentityKeyPair:
|
|
| 572 | + def get_identity_key_pair(self) -> IdentityKeyPair:
|
|
| 599 | 573 | query = '''SELECT public_key as "public_key [pk]", private_key
|
| 600 | 574 | FROM secret LIMIT 1'''
|
| 601 | 575 | result = self._con.execute(query).fetchone()
|
| ... | ... | @@ -603,15 +577,16 @@ def getIdentityKeyPair(self) -> IdentityKeyPair: |
| 603 | 577 | return IdentityKeyPair.new(result.public_key,
|
| 604 | 578 | DjbECPrivateKey(result.private_key))
|
| 605 | 579 | |
| 606 | - def getLocalRegistrationId(self) -> Optional[int]: # pyright: ignore
|
|
| 580 | + def get_local_registration_id(self) -> int:
|
|
| 607 | 581 | query = 'SELECT device_id FROM secret LIMIT 1'
|
| 608 | 582 | result = self._con.execute(query).fetchone()
|
| 609 | - return result.device_id if result is not None else None
|
|
| 583 | + assert result is not None
|
|
| 584 | + return result.device_id
|
|
| 610 | 585 | |
| 611 | - def storeLocalData(self,
|
|
| 612 | - device_id: int,
|
|
| 613 | - identityKeyPair: IdentityKeyPair
|
|
| 614 | - ) -> None:
|
|
| 586 | + def store_own_identity(self,
|
|
| 587 | + device_id: int,
|
|
| 588 | + identity_key_pair: IdentityKeyPair
|
|
| 589 | + ) -> None:
|
|
| 615 | 590 | |
| 616 | 591 | query = 'SELECT * FROM secret'
|
| 617 | 592 | result = self._con.execute(query).fetchone()
|
| ... | ... | @@ -623,67 +598,71 @@ def storeLocalData(self, |
| 623 | 598 | query = '''INSERT INTO secret(device_id, public_key, private_key)
|
| 624 | 599 | VALUES(?, ?, ?)'''
|
| 625 | 600 | |
| 626 | - public_key = identityKeyPair.getPublicKey().getPublicKey().serialize()
|
|
| 627 | - private_key = identityKeyPair.getPrivateKey().serialize()
|
|
| 601 | + public_key = identity_key_pair.get_public_key().get_public_key().\
|
|
| 602 | + serialize()
|
|
| 603 | + private_key = identity_key_pair.get_private_key().serialize()
|
|
| 628 | 604 | self._con.execute(query, (device_id, public_key, private_key))
|
| 629 | 605 | self._con.commit()
|
| 630 | 606 | |
| 631 | - def saveIdentity(self, recipientId: str, identityKey: IdentityKey) -> None:
|
|
| 607 | + def save_identity(self,
|
|
| 608 | + recipient_id: str,
|
|
| 609 | + identity_key: IdentityKey
|
|
| 610 | + ) -> None:
|
|
| 632 | 611 | query = '''INSERT INTO identities
|
| 633 | 612 | (recipient_id, public_key, trust, shown)
|
| 634 | 613 | VALUES(?, ?, ?, ?)'''
|
| 635 | - if not self.containsIdentity(recipientId, identityKey):
|
|
| 636 | - trust = self.getDefaultTrust(recipientId)
|
|
| 637 | - self._con.execute(query, (recipientId,
|
|
| 638 | - identityKey.getPublicKey().serialize(),
|
|
| 614 | + if not self.contains_identity(recipient_id, identity_key):
|
|
| 615 | + trust = self.get_default_trust(recipient_id)
|
|
| 616 | + self._con.execute(query, (recipient_id,
|
|
| 617 | + identity_key.get_public_key().serialize(),
|
|
| 639 | 618 | trust,
|
| 640 | - 1 if trust == Trust.BLIND else 0))
|
|
| 619 | + 1 if trust == OMEMOTrust.BLIND else 0))
|
|
| 641 | 620 | self._con.commit()
|
| 642 | 621 | |
| 643 | - def containsIdentity(self,
|
|
| 644 | - recipientId: str,
|
|
| 645 | - identityKey: IdentityKey
|
|
| 646 | - ) -> bool:
|
|
| 622 | + def contains_identity(self,
|
|
| 623 | + recipient_id: str,
|
|
| 624 | + identity_key: IdentityKey
|
|
| 625 | + ) -> bool:
|
|
| 647 | 626 | |
| 648 | 627 | query = '''SELECT * FROM identities WHERE recipient_id = ?
|
| 649 | 628 | AND public_key = ?'''
|
| 650 | 629 | |
| 651 | - public_key = identityKey.getPublicKey().serialize()
|
|
| 652 | - result = self._con.execute(query, (recipientId,
|
|
| 630 | + public_key = identity_key.get_public_key().serialize()
|
|
| 631 | + result = self._con.execute(query, (recipient_id,
|
|
| 653 | 632 | public_key)).fetchone()
|
| 654 | 633 | |
| 655 | 634 | return result is not None
|
| 656 | 635 | |
| 657 | - def deleteIdentity(self,
|
|
| 658 | - recipientId: str,
|
|
| 659 | - identityKey: IdentityKey
|
|
| 660 | - ) -> None:
|
|
| 636 | + def delete_identity(self,
|
|
| 637 | + recipient_id: str,
|
|
| 638 | + identity_key: IdentityKey
|
|
| 639 | + ) -> None:
|
|
| 661 | 640 | |
| 662 | 641 | query = '''DELETE FROM identities
|
| 663 | 642 | WHERE recipient_id = ? AND public_key = ?'''
|
| 664 | - public_key = identityKey.getPublicKey().serialize()
|
|
| 665 | - self._con.execute(query, (recipientId, public_key))
|
|
| 643 | + public_key = identity_key.get_public_key().serialize()
|
|
| 644 | + self._con.execute(query, (recipient_id, public_key))
|
|
| 666 | 645 | self._con.commit()
|
| 667 | 646 | |
| 668 | - def isTrustedIdentity(self,
|
|
| 669 | - recipientId: str,
|
|
| 670 | - identityKey: IdentityKey
|
|
| 671 | - ) -> bool:
|
|
| 647 | + def is_trusted_identity(self,
|
|
| 648 | + recipient_id: str,
|
|
| 649 | + identity_key: IdentityKey
|
|
| 650 | + ) -> bool:
|
|
| 672 | 651 | |
| 673 | 652 | return True
|
| 674 | 653 | |
| 675 | - def getTrustForIdentity(self,
|
|
| 676 | - recipientId: str,
|
|
| 677 | - identityKey: IdentityKey
|
|
| 678 | - ) -> Optional[Trust]:
|
|
| 654 | + def get_trust_for_identity(self,
|
|
| 655 | + recipient_id: str,
|
|
| 656 | + identity_key: IdentityKey
|
|
| 657 | + ) -> Optional[OMEMOTrust]:
|
|
| 679 | 658 | |
| 680 | 659 | query = '''SELECT trust FROM identities WHERE recipient_id = ?
|
| 681 | 660 | AND public_key = ?'''
|
| 682 | - public_key = identityKey.getPublicKey().serialize()
|
|
| 683 | - result = self._con.execute(query, (recipientId, public_key)).fetchone()
|
|
| 661 | + public_key = identity_key.get_public_key().serialize()
|
|
| 662 | + result = self._con.execute(query, (recipient_id, public_key)).fetchone()
|
|
| 684 | 663 | return result.trust if result is not None else None
|
| 685 | 664 | |
| 686 | - def getFingerprints(self, jid: str):
|
|
| 665 | + def get_fingerprints(self, jid: str):
|
|
| 687 | 666 | query = '''SELECT recipient_id,
|
| 688 | 667 | public_key as "public_key [pk]",
|
| 689 | 668 | trust,
|
| ... | ... | @@ -692,7 +671,7 @@ def getFingerprints(self, jid: str): |
| 692 | 671 | WHERE recipient_id = ? ORDER BY trust ASC'''
|
| 693 | 672 | return self._con.execute(query, (jid,)).fetchall()
|
| 694 | 673 | |
| 695 | - def getMucFingerprints(self, jids: list[str]):
|
|
| 674 | + def get_muc_fingerprints(self, jids: list[str]):
|
|
| 696 | 675 | query = '''
|
| 697 | 676 | SELECT recipient_id,
|
| 698 | 677 | public_key as "public_key [pk]",
|
| ... | ... | @@ -704,98 +683,106 @@ def getMucFingerprints(self, jids: list[str]): |
| 704 | 683 | |
| 705 | 684 | return self._con.execute(query, jids).fetchall()
|
| 706 | 685 | |
| 707 | - def hasUndecidedFingerprints(self, jid: str) -> bool:
|
|
| 686 | + def has_undecided_fingerprints(self, jid: str) -> bool:
|
|
| 708 | 687 | query = '''SELECT public_key as "public_key [pk]" FROM identities
|
| 709 | 688 | WHERE recipient_id = ? AND trust = ?'''
|
| 710 | - result = self._con.execute(query, (jid, Trust.UNDECIDED)).fetchall()
|
|
| 689 | + result = self._con.execute(query,
|
|
| 690 | + (jid, OMEMOTrust.UNDECIDED)).fetchall()
|
|
| 711 | 691 | undecided = [row.public_key for row in result]
|
| 712 | 692 | |
| 713 | - inactive = self.getInactiveSessionsKeys(jid)
|
|
| 693 | + inactive = self.get_inactive_sessions_keys(jid)
|
|
| 714 | 694 | undecided = set(undecided) - set(inactive)
|
| 715 | 695 | return bool(undecided)
|
| 716 | 696 | |
| 717 | - def getDefaultTrust(self, jid: str) -> Trust:
|
|
| 697 | + def get_default_trust(self, jid: str) -> OMEMOTrust:
|
|
| 718 | 698 | if not self._is_blind_trust_enabled():
|
| 719 | - return Trust.UNDECIDED
|
|
| 699 | + return OMEMOTrust.UNDECIDED
|
|
| 720 | 700 | |
| 721 | 701 | query = '''SELECT * FROM identities
|
| 722 | 702 | WHERE recipient_id = ? AND trust IN (0, 1)'''
|
| 723 | 703 | result = self._con.execute(query, (jid,)).fetchone()
|
| 724 | 704 | if result is None:
|
| 725 | - return Trust.BLIND
|
|
| 726 | - return Trust.UNDECIDED
|
|
| 705 | + return OMEMOTrust.BLIND
|
|
| 706 | + return OMEMOTrust.UNDECIDED
|
|
| 727 | 707 | |
| 728 | - def getTrustedFingerprints(self, jid: str) -> list[IdentityKeyExtended]:
|
|
| 708 | + def get_trusted_fingerprints(self, address: str) -> list[IdentityKey]:
|
|
| 729 | 709 | query = '''SELECT public_key as "public_key [pk]" FROM identities
|
| 730 | 710 | WHERE recipient_id = ? AND trust IN(1, 3)'''
|
| 731 | - result = self._con.execute(query, (jid,)).fetchall()
|
|
| 711 | + result = self._con.execute(query, (address,)).fetchall()
|
|
| 732 | 712 | return [row.public_key for row in result]
|
| 733 | 713 | |
| 734 | - def getNewFingerprints(self, jid: str) -> list[int]:
|
|
| 714 | + def get_new_fingerprints(self, jid: str) -> list[int]:
|
|
| 735 | 715 | query = '''SELECT _id FROM identities WHERE shown = 0
|
| 736 | 716 | AND recipient_id = ?'''
|
| 737 | 717 | |
| 738 | 718 | result = self._con.execute(query, (jid,)).fetchall()
|
| 739 | 719 | return [row.id for row in result]
|
| 740 | 720 | |
| 741 | - def setShownFingerprints(self, fingerprints: list[int]) -> None:
|
|
| 721 | + def set_shown_fingerprints(self, fingerprints: list[int]) -> None:
|
|
| 742 | 722 | query = 'UPDATE identities SET shown = 1 WHERE _id IN ({})'.format(
|
| 743 | 723 | ', '.join(['?'] * len(fingerprints)))
|
| 744 | 724 | self._con.execute(query, fingerprints)
|
| 745 | 725 | self._con.commit()
|
| 746 | 726 | |
| 747 | - def setTrust(self,
|
|
| 748 | - recipient_id: str,
|
|
| 749 | - identityKey: IdentityKey,
|
|
| 750 | - trust: Trust
|
|
| 751 | - ) -> None:
|
|
| 727 | + def set_trust(self,
|
|
| 728 | + recipient_id: str,
|
|
| 729 | + identity_key: IdentityKey,
|
|
| 730 | + trust: OMEMOTrust
|
|
| 731 | + ) -> None:
|
|
| 752 | 732 | |
| 753 | 733 | query = '''UPDATE identities SET trust = ? WHERE public_key = ?
|
| 754 | 734 | AND recipient_id = ?'''
|
| 755 | - public_key = identityKey.getPublicKey().serialize()
|
|
| 735 | + public_key = identity_key.get_public_key().serialize()
|
|
| 756 | 736 | self._con.execute(query, (trust, public_key, recipient_id))
|
| 757 | 737 | self._con.commit()
|
| 758 | 738 | |
| 759 | - def isTrusted(self, recipient_id: str, device_id: int) -> bool:
|
|
| 760 | - record = self.loadSession(recipient_id, device_id)
|
|
| 761 | - if record.isFresh():
|
|
| 739 | + def is_trusted(self, recipient_id: str, device_id: int) -> bool:
|
|
| 740 | + record = self.load_session(recipient_id, device_id)
|
|
| 741 | + if record.is_fresh():
|
|
| 762 | 742 | return False
|
| 763 | - identity_key = record.getSessionState().getRemoteIdentityKey()
|
|
| 764 | - return self.getTrustForIdentity(
|
|
| 765 | - recipient_id, identity_key) in (Trust.VERIFIED, Trust.BLIND)
|
|
| 743 | + identity_key = record.get_session_state().get_remote_identity_key()
|
|
| 744 | + return self.get_trust_for_identity(
|
|
| 745 | + recipient_id, identity_key) in (OMEMOTrust.VERIFIED,
|
|
| 746 | + OMEMOTrust.BLIND)
|
|
| 766 | 747 | |
| 767 | - def getIdentityLastSeen(self,
|
|
| 768 | - recipient_id: str,
|
|
| 769 | - identity_key: IdentityKey
|
|
| 770 | - ) -> Optional[int]:
|
|
| 748 | + def get_identity_last_seen(self,
|
|
| 749 | + recipient_id: str,
|
|
| 750 | + identity_key: IdentityKey
|
|
| 751 | + ) -> Optional[int]:
|
|
| 771 | 752 | |
| 772 | - serialized = identity_key.getPublicKey().serialize()
|
|
| 753 | + serialized = identity_key.get_public_key().serialize()
|
|
| 773 | 754 | query = '''SELECT timestamp FROM identities
|
| 774 | 755 | WHERE recipient_id = ? AND public_key = ?'''
|
| 775 | 756 | result = self._con.execute(query, (recipient_id,
|
| 776 | 757 | serialized)).fetchone()
|
| 777 | 758 | return result.timestamp if result is not None else None
|
| 778 | 759 | |
| 779 | - def setIdentityLastSeen(self,
|
|
| 780 | - recipient_id: str,
|
|
| 781 | - identity_key: IdentityKey
|
|
| 782 | - ) -> None:
|
|
| 760 | + def set_identity_last_seen(self,
|
|
| 761 | + recipient_id: str,
|
|
| 762 | + identity_key: IdentityKey
|
|
| 763 | + ) -> None:
|
|
| 783 | 764 | |
| 784 | 765 | timestamp = int(time.time())
|
| 785 | - serialized = identity_key.getPublicKey().serialize()
|
|
| 766 | + serialized = identity_key.get_public_key().serialize()
|
|
| 786 | 767 | self._log.info('Set last seen for %s %s', recipient_id, timestamp)
|
| 787 | 768 | query = '''UPDATE identities SET timestamp = ?
|
| 788 | 769 | WHERE recipient_id = ? AND public_key = ?'''
|
| 789 | 770 | self._con.execute(query, (timestamp, recipient_id, serialized))
|
| 790 | 771 | self._con.commit()
|
| 791 | 772 | |
| 792 | - def getUnacknowledgedCount(self, recipient_id: str, device_id: int) -> int:
|
|
| 793 | - record = self.loadSession(recipient_id, device_id)
|
|
| 794 | - if record.isFresh():
|
|
| 773 | + def get_unacknowledged_count(self,
|
|
| 774 | + recipient_id: str,
|
|
| 775 | + device_id: int
|
|
| 776 | + ) -> int:
|
|
| 777 | + record = self.load_session(recipient_id, device_id)
|
|
| 778 | + if record.is_fresh():
|
|
| 795 | 779 | return 0
|
| 796 | - state = record.getSessionState()
|
|
| 797 | - return state.getSenderChainKey().getIndex()
|
|
| 780 | + state = record.get_session_state()
|
|
| 781 | + return state.get_sender_chain_key().get_index()
|
|
| 798 | 782 | |
| 799 | - def getSubDeviceSessions(self, recipientId: str) -> list[int]:
|
|
| 800 | - # Not used
|
|
| 801 | - return [] |
|
| 783 | + def needs_init(self) -> bool:
|
|
| 784 | + try:
|
|
| 785 | + self.get_local_registration_id()
|
|
| 786 | + except AssertionError:
|
|
| 787 | + return True
|
|
| 788 | + return False |
| ... | ... | @@ -28,7 +28,8 @@ |
| 28 | 28 | from gi.repository import GdkPixbuf
|
| 29 | 29 | from gi.repository import Gtk
|
| 30 | 30 | from nbxmpp.protocol import JID
|
| 31 | -from omemo_dr.identitykeypair import IdentityKeyPair
|
|
| 31 | +from omemo_dr.const import OMEMOTrust
|
|
| 32 | +from omemo_dr.identitykey import IdentityKey
|
|
| 32 | 33 | |
| 33 | 34 | from gajim.common import app
|
| 34 | 35 | from gajim.common import ged
|
| ... | ... | @@ -40,9 +41,6 @@ |
| 40 | 41 | from gajim.common.helpers import generate_qr_code
|
| 41 | 42 | from gajim.common.i18n import _
|
| 42 | 43 | from gajim.common.modules.contacts import BareContact
|
| 43 | -from gajim.common.omemo.util import get_fingerprint
|
|
| 44 | -from gajim.common.omemo.util import IdentityKeyExtended
|
|
| 45 | -from gajim.common.omemo.util import Trust
|
|
| 46 | 44 | |
| 47 | 45 | from .builder import get_builder
|
| 48 | 46 | from .dialogs import ConfirmationDialog
|
| ... | ... | @@ -53,16 +51,16 @@ |
| 53 | 51 | |
| 54 | 52 | |
| 55 | 53 | TRUST_DATA = {
|
| 56 | - Trust.UNTRUSTED: ('dialog-error-symbolic',
|
|
| 54 | + OMEMOTrust.UNTRUSTED: ('dialog-error-symbolic',
|
|
| 57 | 55 | _('Untrusted'),
|
| 58 | 56 | 'error-color'),
|
| 59 | - Trust.UNDECIDED: ('security-low-symbolic',
|
|
| 57 | + OMEMOTrust.UNDECIDED: ('security-low-symbolic',
|
|
| 60 | 58 | _('Not Decided'),
|
| 61 | 59 | 'warning-color'),
|
| 62 | - Trust.VERIFIED: ('security-high-symbolic',
|
|
| 60 | + OMEMOTrust.VERIFIED: ('security-high-symbolic',
|
|
| 63 | 61 | _('Verified'),
|
| 64 | 62 | 'encrypted-color'),
|
| 65 | - Trust.BLIND: ('security-medium-symbolic',
|
|
| 63 | + OMEMOTrust.BLIND: ('security-medium-symbolic',
|
|
| 66 | 64 | _('Blind Trust'),
|
| 67 | 65 | 'encrypted-color')
|
| 68 | 66 | }
|
| ... | ... | @@ -125,9 +123,8 @@ def _update(self) -> None: |
| 125 | 123 | |
| 126 | 124 | self._omemo = client.get_module('OMEMO')
|
| 127 | 125 | |
| 128 | - self._identity_key = cast(
|
|
| 129 | - IdentityKeyPair, self._omemo.backend.storage.getIdentityKeyPair())
|
|
| 130 | - our_fpr_formatted = get_fingerprint(self._identity_key, formatted=True)
|
|
| 126 | + our_fpr_formatted = self._omemo.backend.get_own_fingerprint(
|
|
| 127 | + formatted=True)
|
|
| 131 | 128 | self._ui.our_fingerprint_1.set_text(our_fpr_formatted)
|
| 132 | 129 | self._ui.our_fingerprint_2.set_text(our_fpr_formatted)
|
| 133 | 130 | |
| ... | ... | @@ -198,15 +195,16 @@ def _load_fingerprints(self, contact: types.ChatContactT) -> None: |
| 198 | 195 | if contact.is_groupchat:
|
| 199 | 196 | members = list(self._omemo.backend.get_muc_members(
|
| 200 | 197 | str(contact.jid)))
|
| 201 | - sessions = self._omemo.backend.storage.getSessionsFromJids(members)
|
|
| 202 | - results = self._omemo.backend.storage.getMucFingerprints(members)
|
|
| 198 | + sessions = self._omemo.backend.storage.get_sessions_from_jids(
|
|
| 199 | + members)
|
|
| 200 | + results = self._omemo.backend.storage.get_muc_fingerprints(members)
|
|
| 203 | 201 | else:
|
| 204 | - sessions = self._omemo.backend.storage.getSessionsFromJid(
|
|
| 202 | + sessions = self._omemo.backend.storage.get_sessions_from_jid(
|
|
| 205 | 203 | str(contact.jid))
|
| 206 | - results = self._omemo.backend.storage.getFingerprints(
|
|
| 204 | + results = self._omemo.backend.storage.get_fingerprints(
|
|
| 207 | 205 | str(contact.jid))
|
| 208 | 206 | |
| 209 | - rows: dict[IdentityKeyExtended, KeyRow] = {}
|
|
| 207 | + rows: dict[IdentityKey, KeyRow] = {}
|
|
| 210 | 208 | for result in results:
|
| 211 | 209 | rows[result.public_key] = KeyRow(contact,
|
| 212 | 210 | result.recipient_id,
|
| ... | ... | @@ -215,16 +213,17 @@ def _load_fingerprints(self, contact: types.ChatContactT) -> None: |
| 215 | 213 | result.timestamp)
|
| 216 | 214 | |
| 217 | 215 | for item in sessions:
|
| 218 | - if item.record.isFresh():
|
|
| 216 | + if item.record.is_fresh():
|
|
| 219 | 217 | return
|
| 220 | - identity_key = item.record.getSessionState().getRemoteIdentityKey()
|
|
| 221 | - identity_key = IdentityKeyExtended(identity_key.getPublicKey())
|
|
| 218 | + identity_key = item.record.get_session_state().\
|
|
| 219 | + get_remote_identity_key()
|
|
| 220 | + identity_key = IdentityKey(identity_key.get_public_key())
|
|
| 222 | 221 | try:
|
| 223 | 222 | key_row = rows[identity_key]
|
| 224 | 223 | except KeyError:
|
| 225 | 224 | log.warning('Could not find session identitykey %s',
|
| 226 | 225 | item.device_id)
|
| 227 | - self._omemo.backend.storage.deleteSession(item.recipient_id,
|
|
| 226 | + self._omemo.backend.storage.delete_session(item.recipient_id,
|
|
| 228 | 227 | item.device_id)
|
| 229 | 228 | continue
|
| 230 | 229 | |
| ... | ... | @@ -237,10 +236,9 @@ def _load_fingerprints(self, contact: types.ChatContactT) -> None: |
| 237 | 236 | @staticmethod
|
| 238 | 237 | def _get_qrcode(jid: JID,
|
| 239 | 238 | sid: int,
|
| 240 | - identity_key: IdentityKeyPair
|
|
| 239 | + fingerprint: str
|
|
| 241 | 240 | ) -> GdkPixbuf.Pixbuf | None:
|
| 242 | 241 | |
| 243 | - fingerprint = get_fingerprint(identity_key)
|
|
| 244 | 242 | qry = (XmppUriQuery.MESSAGE.value, [(f'omemo-sid-{sid}', fingerprint)])
|
| 245 | 243 | ver_string = jid.new_as_bare().to_iri(qry)
|
| 246 | 244 | log.debug('Verification String: %s', ver_string)
|
| ... | ... | @@ -249,8 +247,8 @@ def _get_qrcode(jid: JID, |
| 249 | 247 | def _load_qrcode(self) -> None:
|
| 250 | 248 | client = app.get_client(self._account)
|
| 251 | 249 | pixbuf = self._get_qrcode(client.get_own_jid(),
|
| 252 | - self._omemo.backend.own_device,
|
|
| 253 | - self._identity_key)
|
|
| 250 | + self._omemo.backend.get_own_device(),
|
|
| 251 | + self._omemo.backend.get_own_fingerprint())
|
|
| 254 | 252 | self._ui.qr_code_image.set_from_pixbuf(pixbuf)
|
| 255 | 253 | |
| 256 | 254 | def _on_show_inactive(self, switch: Gtk.Switch, _param: Any) -> None:
|
| ... | ... | @@ -280,8 +278,8 @@ class KeyRow(Gtk.ListBoxRow): |
| 280 | 278 | def __init__(self,
|
| 281 | 279 | contact: types.ChatContactT,
|
| 282 | 280 | jid: JID,
|
| 283 | - identity_key: IdentityKeyExtended,
|
|
| 284 | - trust: Trust,
|
|
| 281 | + identity_key: IdentityKey,
|
|
| 282 | + trust: OMEMOTrust,
|
|
| 285 | 283 | last_seen: Optional[float]
|
| 286 | 284 | ) -> None:
|
| 287 | 285 | |
| ... | ... | @@ -347,9 +345,9 @@ def delete_fingerprint(self, *args: Any) -> None: |
| 347 | 345 | |
| 348 | 346 | def _remove():
|
| 349 | 347 | self._omemo.backend.remove_device(str(self.jid), self.device_id)
|
| 350 | - self._omemo.backend.storage.deleteSession(
|
|
| 348 | + self._omemo.backend.storage.delete_session(
|
|
| 351 | 349 | str(self.jid), self.device_id)
|
| 352 | - self._omemo.backend.storage.deleteIdentity(
|
|
| 350 | + self._omemo.backend.storage.delete_identity(
|
|
| 353 | 351 | str(self.jid), self._identity_key)
|
| 354 | 352 | |
| 355 | 353 | listbox = cast(Gtk.ListBox, self.get_parent())
|
| ... | ... | @@ -373,7 +371,7 @@ def set_trust(self) -> None: |
| 373 | 371 | image.get_style_context().add_class(css_class)
|
| 374 | 372 | image.set_tooltip_text(tooltip)
|
| 375 | 373 | |
| 376 | - self._omemo.backend.storage.setTrust(
|
|
| 374 | + self._omemo.backend.storage.set_trust(
|
|
| 377 | 375 | str(self.jid), self._identity_key, self.trust)
|
| 378 | 376 | |
| 379 | 377 | @property
|
| ... | ... | @@ -454,11 +452,11 @@ def _activated(self, _listbox: Gtk.ListBox, row: MenuOption) -> None: |
| 454 | 452 | |
| 455 | 453 | def update(self) -> None:
|
| 456 | 454 | self._listbox.foreach(self._listbox.remove)
|
| 457 | - if self._row.trust != Trust.VERIFIED:
|
|
| 455 | + if self._row.trust != OMEMOTrust.VERIFIED:
|
|
| 458 | 456 | self._listbox.add(VerifiedOption())
|
| 459 | - if self._row.trust != Trust.BLIND:
|
|
| 457 | + if self._row.trust != OMEMOTrust.BLIND:
|
|
| 460 | 458 | self._listbox.add(BlindOption())
|
| 461 | - if self._row.trust != Trust.UNTRUSTED:
|
|
| 459 | + if self._row.trust != OMEMOTrust.UNTRUSTED:
|
|
| 462 | 460 | self._listbox.add(NotTrustedOption())
|
| 463 | 461 | self._listbox.add(DeleteOption())
|
| 464 | 462 | |
| ... | ... | @@ -468,7 +466,7 @@ def __init__(self, |
| 468 | 466 | icon: str,
|
| 469 | 467 | label_text: str,
|
| 470 | 468 | color: str,
|
| 471 | - type_: Optional[Trust] = None
|
|
| 469 | + type_: Optional[OMEMOTrust] = None
|
|
| 472 | 470 | ) -> None:
|
| 473 | 471 | |
| 474 | 472 | Gtk.ListBoxRow.__init__(self)
|
| ... | ... | @@ -497,7 +495,7 @@ def __init__(self) -> None: |
| 497 | 495 | 'security-medium-symbolic',
|
| 498 | 496 | _('Blind Trust'),
|
| 499 | 497 | 'encrypted-color',
|
| 500 | - Trust.BLIND)
|
|
| 498 | + OMEMOTrust.BLIND)
|
|
| 501 | 499 | |
| 502 | 500 | |
| 503 | 501 | class VerifiedOption(MenuOption):
|
| ... | ... | @@ -506,7 +504,7 @@ def __init__(self) -> None: |
| 506 | 504 | 'security-high-symbolic',
|
| 507 | 505 | _('Verified'),
|
| 508 | 506 | 'encrypted-color',
|
| 509 | - Trust.VERIFIED)
|
|
| 507 | + OMEMOTrust.VERIFIED)
|
|
| 510 | 508 | |
| 511 | 509 | |
| 512 | 510 | class NotTrustedOption(MenuOption):
|
| ... | ... | @@ -515,7 +513,7 @@ def __init__(self) -> None: |
| 515 | 513 | 'dialog-error-symbolic',
|
| 516 | 514 | _('Untrusted'),
|
| 517 | 515 | 'error-color',
|
| 518 | - Trust.UNTRUSTED)
|
|
| 516 | + OMEMOTrust.UNTRUSTED)
|
|
| 519 | 517 | |
| 520 | 518 | |
| 521 | 519 | class DeleteOption(MenuOption):
|
| ... | ... | @@ -35,7 +35,7 @@ dependencies = [ |
| 35 | 35 | "pycairo>=1.16.0",
|
| 36 | 36 | "PyGObject>=3.42.0",
|
| 37 | 37 | "qrcode>=7.3.1",
|
| 38 | - "omemo-dr>=0.99.0,<2.0.0",
|
|
| 38 | + "omemo-dr>=0.99.1,<2.0.0",
|
|
| 39 | 39 | "winsdk ; platform_system == 'Windows'",
|
| 40 | 40 | ]
|
| 41 | 41 | dynamic = ["version"]
|