diff options
Diffstat (limited to 'omemo/backend')
-rw-r--r-- | omemo/backend/__init__.py | 1 | ||||
-rw-r--r-- | omemo/backend/aes.py | 95 | ||||
-rw-r--r-- | omemo/backend/devices.py | 138 | ||||
-rw-r--r-- | omemo/backend/liteaxolotlstore.py | 741 | ||||
-rw-r--r-- | omemo/backend/state.py | 362 | ||||
-rw-r--r-- | omemo/backend/util.py | 56 |
6 files changed, 0 insertions, 1393 deletions
diff --git a/omemo/backend/__init__.py b/omemo/backend/__init__.py deleted file mode 100644 index 3dc1f76..0000000 --- a/omemo/backend/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" diff --git a/omemo/backend/aes.py b/omemo/backend/aes.py deleted file mode 100644 index 49d288a..0000000 --- a/omemo/backend/aes.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com> -# -# This file is part of OMEMO Gajim Plugin. -# -# OMEMO Gajim Plugin is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# OMEMO Gajim Plugin is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>. - - -import os -import logging -from collections import namedtuple - -from cryptography.hazmat.primitives.ciphers import Cipher -from cryptography.hazmat.primitives.ciphers import algorithms -from cryptography.hazmat.primitives.ciphers.modes import GCM -from cryptography.hazmat.backends import default_backend - -log = logging.getLogger('gajim.p.omemo') - -EncryptionResult = namedtuple('EncryptionResult', 'payload key iv') - -IV_SIZE = 12 - -def _decrypt(key, iv, tag, data): - decryptor = Cipher( - algorithms.AES(key), - GCM(iv, tag=tag), - backend=default_backend()).decryptor() - return decryptor.update(data) + decryptor.finalize() - - -def aes_decrypt(_key, iv, payload): - if len(_key) >= 32: - # XEP-0384 - log.debug('XEP Compliant Key/Tag') - data = payload - key = _key[:16] - tag = _key[16:] - else: - # Legacy - log.debug('Legacy Key/Tag') - data = payload[:-16] - key = _key - tag = payload[-16:] - - return _decrypt(key, iv, tag, data).decode() - - -def aes_decrypt_file(key, iv, payload): - data = payload[:-16] - tag = payload[-16:] - return _decrypt(key, iv, tag, data) - - -def _encrypt(data, key_size, iv_size=IV_SIZE): - if isinstance(data, str): - data = data.encode() - key = os.urandom(key_size) - iv = os.urandom(iv_size) - encryptor = Cipher( - algorithms.AES(key), - GCM(iv), - backend=default_backend()).encryptor() - - payload = encryptor.update(data) + encryptor.finalize() - return key, iv, encryptor.tag, payload - - -def aes_encrypt(plaintext): - key, iv, tag, payload = _encrypt(plaintext, 16) - key += tag - return EncryptionResult(payload=payload, key=key, iv=iv) - - -def aes_encrypt_file(data): - key, iv, tag, payload, = _encrypt(data, 32) - payload += tag - return EncryptionResult(payload=payload, key=key, iv=iv) - - -def get_new_key(): - return os.urandom(16) - - -def get_new_iv(): - return os.urandom(IV_SIZE) diff --git a/omemo/backend/devices.py b/omemo/backend/devices.py deleted file mode 100644 index 3790785..0000000 --- a/omemo/backend/devices.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com> -# -# This file is part of OMEMO Gajim Plugin. -# -# OMEMO Gajim Plugin is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# OMEMO Gajim Plugin is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>. - -from collections import defaultdict - -from gajim.common import app - -from omemo.backend.util import UNACKNOWLEDGED_COUNT - - -class DeviceManager: - def __init__(self): - self.__device_store = defaultdict(set) - self.__muc_member_store = defaultdict(set) - - reg_id = self._storage.getLocalRegistrationId() - if reg_id is None: - raise ValueError('No own device found') - - self.__own_device = reg_id - self.add_device(self._own_jid, self.__own_device) - self._log.info('Our device id: %s', self.__own_device) - - for jid, device in self._storage.getActiveDeviceTuples(): - self._log.info('Load device from storage: %s - %s', jid, device) - self.add_device(jid, device) - - def update_devicelist(self, jid, devicelist): - for device in list(devicelist): - if device == self.own_device: - continue - count = self._storage.getUnacknowledgedCount(jid, device) - if count > UNACKNOWLEDGED_COUNT: - self._log.warning('Ignore device because of %s unacknowledged' - ' messages: %s %s', count, jid, device) - devicelist.remove(device) - - self.__device_store[jid] = set(devicelist) - self._log.info('Saved devices for %s', jid) - self._storage.setActiveState(jid, devicelist) - - def add_muc_member(self, room_jid, jid): - self._log.info('Saved MUC member %s %s', room_jid, jid) - self.__muc_member_store[room_jid].add(jid) - - def remove_muc_member(self, room_jid, jid): - self._log.info('Removed MUC member %s %s', room_jid, jid) - self.__muc_member_store[room_jid].discard(jid) - - def get_muc_members(self, room_jid, without_self=True): - members = set(self.__muc_member_store[room_jid]) - if without_self: - members.discard(self._own_jid) - return members - - def add_device(self, jid, device): - self.__device_store[jid].add(device) - - def remove_device(self, jid, device): - self.__device_store[jid].discard(device) - self._storage.setInactive(jid, device) - - def get_devices(self, jid, without_self=False): - devices = set(self.__device_store[jid]) - if without_self: - devices.discard(self.own_device) - return devices - - def get_devices_for_encryption(self, jid): - devices_for_encryption = [] - - client = app.get_client(self._account) - contact = client.get_module('Contacts').get_contact(jid) - if contact.is_groupchat: - devices_for_encryption = self._get_devices_for_muc_encryption(jid) - else: - devices_for_encryption = self._get_devices_for_encryption(jid) - - if not devices_for_encryption: - raise NoDevicesFound - - devices_for_encryption += self._get_own_devices_for_encryption() - return set(devices_for_encryption) - - def _get_devices_for_muc_encryption(self, jid): - devices_for_encryption = [] - for jid_ in self.__muc_member_store[jid]: - devices_for_encryption += self._get_devices_for_encryption(jid_) - return devices_for_encryption - - def _get_own_devices_for_encryption(self): - devices_for_encryption = [] - own_devices = self.get_devices(self._own_jid, without_self=True) - for device in own_devices: - if self._storage.isTrusted(self._own_jid, device): - devices_for_encryption.append((self._own_jid, device)) - return devices_for_encryption - - def _get_devices_for_encryption(self, jid): - devices_for_encryption = [] - devices = self.get_devices(jid) - - for device in devices: - if self._storage.isTrusted(jid, device): - devices_for_encryption.append((jid, device)) - return devices_for_encryption - - @property - def own_device(self): - return self.__own_device - - @property - def devices_for_publish(self): - devices = self.get_devices(self._own_jid) - if self.own_device not in devices: - devices.add(self.own_device) - return devices - - @property - def is_own_device_published(self): - return self.own_device in self.get_devices(self._own_jid) - - -class NoDevicesFound(Exception): - pass diff --git a/omemo/backend/liteaxolotlstore.py b/omemo/backend/liteaxolotlstore.py deleted file mode 100644 index c1004bc..0000000 --- a/omemo/backend/liteaxolotlstore.py +++ /dev/null @@ -1,741 +0,0 @@ -# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com> -# Copyright (C) 2015 Tarek Galal <tare2.galal@gmail.com> -# -# This file is part of OMEMO Gajim Plugin. -# -# OMEMO Gajim Plugin is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# OMEMO Gajim Plugin is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>. - -import time -import sqlite3 -from collections import namedtuple - -from axolotl.state.axolotlstore import AxolotlStore -from axolotl.state.signedprekeyrecord import SignedPreKeyRecord -from axolotl.state.sessionrecord import SessionRecord -from axolotl.state.prekeyrecord import PreKeyRecord -from axolotl.invalidkeyidexception import InvalidKeyIdException -from axolotl.ecc.djbec import DjbECPrivateKey -from axolotl.ecc.djbec import DjbECPublicKey -from axolotl.identitykeypair import IdentityKeyPair -from axolotl.util.medium import Medium -from axolotl.util.keyhelper import KeyHelper - -from gajim.common import app - -from omemo.backend.util import Trust -from omemo.backend.util import IdentityKeyExtended -from omemo.backend.util import DEFAULT_PREKEY_AMOUNT - - -def _convert_to_string(text): - return text.decode() - - -def _convert_identity_key(key): - if not key: - return - return IdentityKeyExtended(DjbECPublicKey(key[1:])) - - -def _convert_record(record): - return SessionRecord(serialized=record) - - -sqlite3.register_converter('pk', _convert_identity_key) -sqlite3.register_converter('session_record', _convert_record) - - -class LiteAxolotlStore(AxolotlStore): - def __init__(self, db_path, log): - self._log = log - self._con = sqlite3.connect(db_path, - detect_types=sqlite3.PARSE_COLNAMES) - self._con.row_factory = self._namedtuple_factory - self.createDb() - self.migrateDb() - - self._con.execute("PRAGMA secure_delete=1") - self._con.execute("PRAGMA synchronous=NORMAL;") - mode = self._con.execute("PRAGMA journal_mode;").fetchone()[0] - - # WAL is a persistent DB mode, don't override it if user has set it - if mode != 'wal': - self._con.execute("PRAGMA journal_mode=MEMORY;") - self._con.commit() - - if not self.getLocalRegistrationId(): - self._log.info("Generating OMEMO keys") - self._generate_axolotl_keys() - - @staticmethod - def _is_blind_trust_enabled(): - plugin = app.plugin_manager.get_active_plugin('omemo') - return plugin.config['BLIND_TRUST'] - - @staticmethod - def _namedtuple_factory(cursor, row): - fields = [] - for col in cursor.description: - if col[0] == '_id': - fields.append('id') - elif 'strftime' in col[0]: - fields.append('formated_time') - elif 'MAX' in col[0] or 'COUNT' in col[0]: - col_name = col[0].replace('(', '_') - col_name = col_name.replace(')', '') - fields.append(col_name.lower()) - else: - fields.append(col[0]) - return namedtuple("Row", fields)(*row) - - def _generate_axolotl_keys(self): - identity_key_pair = KeyHelper.generateIdentityKeyPair() - registration_id = KeyHelper.getRandomSequence(2147483647) - pre_keys = KeyHelper.generatePreKeys( - KeyHelper.getRandomSequence(4294967296), - DEFAULT_PREKEY_AMOUNT) - self.storeLocalData(registration_id, identity_key_pair) - - signed_pre_key = KeyHelper.generateSignedPreKey( - identity_key_pair, KeyHelper.getRandomSequence(65536)) - - self.storeSignedPreKey(signed_pre_key.getId(), signed_pre_key) - - for pre_key in pre_keys: - self.storePreKey(pre_key.getId(), pre_key) - - def user_version(self): - return self._con.execute('PRAGMA user_version').fetchone()[0] - - def createDb(self): - if self.user_version() == 0: - - create_tables = ''' - CREATE TABLE IF NOT EXISTS secret ( - device_id INTEGER, public_key BLOB, private_key BLOB); - - CREATE TABLE IF NOT EXISTS identities ( - _id INTEGER PRIMARY KEY AUTOINCREMENT, recipient_id TEXT, - registration_id INTEGER, public_key BLOB, - timestamp INTEGER, trust INTEGER, - shown INTEGER DEFAULT 0); - - CREATE UNIQUE INDEX IF NOT EXISTS - public_key_index ON identities (public_key, recipient_id); - - CREATE TABLE IF NOT EXISTS prekeys( - _id INTEGER PRIMARY KEY AUTOINCREMENT, - prekey_id INTEGER UNIQUE, sent_to_server BOOLEAN, - record BLOB); - - CREATE TABLE IF NOT EXISTS signed_prekeys ( - _id INTEGER PRIMARY KEY AUTOINCREMENT, - prekey_id INTEGER UNIQUE, - timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB); - - CREATE TABLE IF NOT EXISTS sessions ( - _id INTEGER PRIMARY KEY AUTOINCREMENT, - recipient_id TEXT, device_id INTEGER, - record BLOB, timestamp INTEGER, active INTEGER DEFAULT 1, - UNIQUE(recipient_id, device_id)); - - ''' - - create_db_sql = """ - BEGIN TRANSACTION; - %s - PRAGMA user_version=12; - END TRANSACTION; - """ % (create_tables) - self._con.executescript(create_db_sql) - - def migrateDb(self): - """ Migrates the DB - """ - - # Find all double entries and delete them - if self.user_version() < 2: - delete_dupes = """ DELETE FROM identities WHERE _id not in ( - SELECT MIN(_id) - FROM identities - GROUP BY - recipient_id, public_key - ); - """ - - self._con.executescript( - """ BEGIN TRANSACTION; - %s - PRAGMA user_version=2; - END TRANSACTION; - """ % (delete_dupes)) - - if self.user_version() < 3: - # Create a UNIQUE INDEX so every public key/recipient_id tuple - # can only be once in the db - add_index = """ CREATE UNIQUE INDEX IF NOT EXISTS - public_key_index - ON identities (public_key, recipient_id); - """ - - self._con.executescript( - """ BEGIN TRANSACTION; - %s - PRAGMA user_version=3; - END TRANSACTION; - """ % (add_index)) - - if self.user_version() < 4: - # Adds column "active" to the sessions table - add_active = """ ALTER TABLE sessions - ADD COLUMN active INTEGER DEFAULT 1; - """ - - self._con.executescript( - """ BEGIN TRANSACTION; - %s - PRAGMA user_version=4; - END TRANSACTION; - """ % (add_active)) - - if self.user_version() < 5: - # Adds DEFAULT Timestamp - add_timestamp = """ - DROP TABLE signed_prekeys; - CREATE TABLE IF NOT EXISTS signed_prekeys ( - _id INTEGER PRIMARY KEY AUTOINCREMENT, - prekey_id INTEGER UNIQUE, - timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB); - ALTER TABLE identities ADD COLUMN shown INTEGER DEFAULT 0; - UPDATE identities SET shown = 1; - """ - - self._con.executescript( - """ BEGIN TRANSACTION; - %s - PRAGMA user_version=5; - END TRANSACTION; - """ % (add_timestamp)) - - if self.user_version() < 6: - # Move secret data into own table - # We add +1 to registration id because we did that in other code in - # earlier versions. On this migration we correct this mistake now. - move = """ - CREATE TABLE IF NOT EXISTS secret ( - device_id INTEGER, public_key BLOB, private_key BLOB); - INSERT INTO secret (device_id, public_key, private_key) - SELECT registration_id + 1, public_key, private_key - FROM identities - WHERE recipient_id = -1; - """ - - self._con.executescript( - """ BEGIN TRANSACTION; - %s - PRAGMA user_version=6; - END TRANSACTION; - """ % move) - - if self.user_version() < 7: - # Convert old device ids to integer - convert = """ - UPDATE secret SET device_id = device_id % 2147483646; - """ - - self._con.executescript( - """ BEGIN TRANSACTION; - %s - PRAGMA user_version=7; - END TRANSACTION; - """ % convert) - - if self.user_version() < 8: - # Sanitize invalid BLOBs from the python2 days - query_keys = '''SELECT recipient_id, - registration_id, - CAST(public_key as BLOB) as public_key, - CAST(private_key as BLOB) as private_key, - timestamp, trust, shown - FROM identities''' - rows = self._con.execute(query_keys).fetchall() - - delete = 'DELETE FROM identities' - self._con.execute(delete) - - insert = '''INSERT INTO identities ( - recipient_id, registration_id, public_key, private_key, - timestamp, trust, shown) - VALUES (?, ?, ?, ?, ?, ?, ?)''' - for row in rows: - try: - self._con.execute(insert, row) - except Exception as error: - self._log.warning(error) - self._con.execute('PRAGMA user_version=8') - self._con.commit() - - if self.user_version() < 9: - # Sanitize invalid BLOBs from the python2 days - query_keys = '''SELECT device_id, - CAST(public_key as BLOB) as public_key, - CAST(private_key as BLOB) as private_key - FROM secret''' - rows = self._con.execute(query_keys).fetchall() - - delete = 'DELETE FROM secret' - self._con.execute(delete) - - insert = '''INSERT INTO secret (device_id, public_key, private_key) - VALUES (?, ?, ?)''' - for row in rows: - try: - self._con.execute(insert, row) - except Exception as error: - self._log.warning(error) - self._con.execute('PRAGMA user_version=9') - self._con.commit() - - if self.user_version() < 10: - # Sanitize invalid BLOBs from the python2 days - query_keys = '''SELECT _id, - recipient_id, - device_id, - CAST(record as BLOB) as record, - timestamp, - active - FROM sessions''' - rows = self._con.execute(query_keys).fetchall() - - delete = 'DELETE FROM sessions' - self._con.execute(delete) - - insert = '''INSERT INTO sessions (_id, recipient_id, device_id, - record, timestamp, active) - VALUES (?, ?, ?, ?, ?, ?)''' - for row in rows: - try: - self._con.execute(insert, row) - except Exception as error: - self._log.warning(error) - self._con.execute('PRAGMA user_version=10') - self._con.commit() - - if self.user_version() < 11: - # Sanitize invalid BLOBs from the python2 days - query_keys = '''SELECT _id, - prekey_id, - sent_to_server, - CAST(record as BLOB) as record - FROM prekeys''' - rows = self._con.execute(query_keys).fetchall() - - delete = 'DELETE FROM prekeys' - self._con.execute(delete) - - insert = '''INSERT INTO prekeys ( - _id, prekey_id, sent_to_server, record) - VALUES (?, ?, ?, ?)''' - for row in rows: - try: - self._con.execute(insert, row) - except Exception as error: - self._log.warning(error) - self._con.execute('PRAGMA user_version=11') - self._con.commit() - - if self.user_version() < 12: - # Sanitize invalid BLOBs from the python2 days - query_keys = '''SELECT _id, - prekey_id, - timestamp, - CAST(record as BLOB) as record - FROM signed_prekeys''' - rows = self._con.execute(query_keys).fetchall() - - delete = 'DELETE FROM signed_prekeys' - self._con.execute(delete) - - insert = '''INSERT INTO signed_prekeys ( - _id, prekey_id, timestamp, record) - VALUES (?, ?, ?, ?)''' - for row in rows: - try: - self._con.execute(insert, row) - except Exception as error: - self._log.warning(error) - self._con.execute('PRAGMA user_version=12') - self._con.commit() - - def loadSignedPreKey(self, signedPreKeyId): - query = 'SELECT record FROM signed_prekeys WHERE prekey_id = ?' - result = self._con.execute(query, (signedPreKeyId, )).fetchone() - if result is None: - raise InvalidKeyIdException("No such signedprekeyrecord! %s " % - signedPreKeyId) - return SignedPreKeyRecord(serialized=result.record) - - def loadSignedPreKeys(self): - query = 'SELECT record FROM signed_prekeys' - results = self._con.execute(query).fetchall() - return [SignedPreKeyRecord(serialized=row.record) for row in results] - - def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord): - query = 'INSERT INTO signed_prekeys (prekey_id, record) VALUES(?,?)' - self._con.execute(query, (signedPreKeyId, - signedPreKeyRecord.serialize())) - self._con.commit() - - def containsSignedPreKey(self, signedPreKeyId): - query = 'SELECT record FROM signed_prekeys WHERE prekey_id = ?' - result = self._con.execute(query, (signedPreKeyId,)).fetchone() - return result is not None - - def removeSignedPreKey(self, signedPreKeyId): - query = 'DELETE FROM signed_prekeys WHERE prekey_id = ?' - self._con.execute(query, (signedPreKeyId,)) - self._con.commit() - - def getNextSignedPreKeyId(self): - result = self.getCurrentSignedPreKeyId() - if result is None: - return 1 # StartId if no SignedPreKeys exist - return (result % (Medium.MAX_VALUE - 1)) + 1 - - def getCurrentSignedPreKeyId(self): - query = 'SELECT MAX(prekey_id) FROM signed_prekeys' - result = self._con.execute(query).fetchone() - return result.max_prekey_id if result is not None else None - - def getSignedPreKeyTimestamp(self, signedPreKeyId): - query = '''SELECT strftime('%s', timestamp) FROM - signed_prekeys WHERE prekey_id = ?''' - - result = self._con.execute(query, (signedPreKeyId,)).fetchone() - if result is None: - raise InvalidKeyIdException('No such signedprekeyrecord! %s' % - signedPreKeyId) - - return result.formated_time - - def removeOldSignedPreKeys(self, timestamp): - query = '''DELETE FROM signed_prekeys - WHERE timestamp < datetime(?, "unixepoch")''' - self._con.execute(query, (timestamp,)) - self._con.commit() - - def loadSession(self, recipientId, deviceId): - query = '''SELECT record as "record [session_record]" - FROM sessions WHERE recipient_id = ? AND device_id = ?''' - result = self._con.execute(query, (recipientId, deviceId)).fetchone() - return result.record if result is not None else SessionRecord() - - def getJidFromDevice(self, device_id): - query = '''SELECT recipient_id - FROM sessions WHERE device_id = ?''' - result = self._con.execute(query, (device_id, )).fetchone() - return result.recipient_id if result is not None else None - - def getActiveDeviceTuples(self): - query = '''SELECT recipient_id, device_id - FROM sessions WHERE active = 1''' - return self._con.execute(query).fetchall() - - def storeSession(self, recipientId, deviceId, sessionRecord): - query = '''INSERT INTO sessions(recipient_id, device_id, record) - VALUES(?,?,?)''' - try: - self._con.execute(query, (recipientId, - deviceId, - sessionRecord.serialize())) - except sqlite3.IntegrityError: - query = '''UPDATE sessions SET record = ? - WHERE recipient_id = ? AND device_id = ?''' - self._con.execute(query, (sessionRecord.serialize(), - recipientId, - deviceId)) - - self._con.commit() - - def containsSession(self, recipientId, deviceId): - query = '''SELECT record FROM sessions - WHERE recipient_id = ? AND device_id = ?''' - result = self._con.execute(query, (recipientId, deviceId)).fetchone() - return result is not None - - def deleteSession(self, recipientId, deviceId): - self._log.info('Delete session for %s %s', recipientId, deviceId) - query = "DELETE FROM sessions WHERE recipient_id = ? AND device_id = ?" - self._con.execute(query, (recipientId, deviceId)) - self._con.commit() - - def deleteAllSessions(self, recipientId): - query = 'DELETE FROM sessions WHERE recipient_id = ?' - self._con.execute(query, (recipientId,)) - self._con.commit() - - def getSessionsFromJid(self, recipientId): - query = '''SELECT recipient_id, - device_id, - record as "record [session_record]", - active - FROM sessions WHERE recipient_id = ?''' - return self._con.execute(query, (recipientId,)).fetchall() - - def getSessionsFromJids(self, recipientIds): - query = '''SELECT recipient_id, - device_id, - record as "record [session_record]", - active - FROM sessions - WHERE recipient_id IN ({})'''.format( - ', '.join(['?'] * len(recipientIds))) - return self._con.execute(query, recipientIds).fetchall() - - def setActiveState(self, jid, devicelist): - query = '''UPDATE sessions SET active = 1 - WHERE recipient_id = ? AND device_id IN ({})'''.format( - ', '.join(['?'] * len(devicelist))) - self._con.execute(query, (jid,) + tuple(devicelist)) - - query = '''UPDATE sessions SET active = 0 - WHERE recipient_id = ? AND device_id NOT IN ({})'''.format( - ', '.join(['?'] * len(devicelist))) - self._con.execute(query, (jid,) + tuple(devicelist)) - self._con.commit() - - def setInactive(self, jid, device_id): - query = '''UPDATE sessions SET active = 0 - WHERE recipient_id = ? AND device_id = ?''' - self._con.execute(query, (jid, device_id)) - self._con.commit() - - def getInactiveSessionsKeys(self, recipientId): - query = '''SELECT record as "record [session_record]" FROM sessions - WHERE active = 0 AND recipient_id = ?''' - results = self._con.execute(query, (recipientId,)).fetchall() - - keys = [] - for result in results: - key = result.record.getSessionState().getRemoteIdentityKey() - keys.append(IdentityKeyExtended(key.getPublicKey())) - return keys - - def loadPreKey(self, preKeyId): - query = '''SELECT record FROM prekeys WHERE prekey_id = ?''' - - result = self._con.execute(query, (preKeyId,)).fetchone() - if result is None: - raise Exception("No such prekeyRecord!") - return PreKeyRecord(serialized=result.record) - - def loadPendingPreKeys(self): - query = '''SELECT record FROM prekeys''' - result = self._con.execute(query).fetchall() - return [PreKeyRecord(serialized=row.record) for row in result] - - def storePreKey(self, preKeyId, preKeyRecord): - query = 'INSERT INTO prekeys (prekey_id, record) VALUES(?,?)' - self._con.execute(query, (preKeyId, preKeyRecord.serialize())) - self._con.commit() - - def containsPreKey(self, preKeyId): - query = 'SELECT record FROM prekeys WHERE prekey_id = ?' - result = self._con.execute(query, (preKeyId,)).fetchone() - return result is not None - - def removePreKey(self, preKeyId): - query = 'DELETE FROM prekeys WHERE prekey_id = ?' - self._con.execute(query, (preKeyId,)) - self._con.commit() - - def getCurrentPreKeyId(self): - query = 'SELECT MAX(prekey_id) FROM prekeys' - return self._con.execute(query).fetchone().max_prekey_id - - def getPreKeyCount(self): - query = 'SELECT COUNT(prekey_id) FROM prekeys' - return self._con.execute(query).fetchone().count_prekey_id - - def generateNewPreKeys(self, count): - prekey_id = self.getCurrentPreKeyId() or 0 - pre_keys = KeyHelper.generatePreKeys(prekey_id + 1, count) - for pre_key in pre_keys: - self.storePreKey(pre_key.getId(), pre_key) - - def getIdentityKeyPair(self): - query = '''SELECT public_key as "public_key [pk]", private_key - FROM secret LIMIT 1''' - result = self._con.execute(query).fetchone() - - return IdentityKeyPair(result.public_key, - DjbECPrivateKey(result.private_key)) - - def getLocalRegistrationId(self): - query = 'SELECT device_id FROM secret LIMIT 1' - result = self._con.execute(query).fetchone() - return result.device_id if result is not None else None - - def storeLocalData(self, device_id, identityKeyPair): - query = 'SELECT * FROM secret' - result = self._con.execute(query).fetchone() - if result is not None: - self._log.error('Trying to save secret key into ' - 'non-empty secret table') - return - - query = '''INSERT INTO secret(device_id, public_key, private_key) - VALUES(?, ?, ?)''' - - public_key = identityKeyPair.getPublicKey().getPublicKey().serialize() - private_key = identityKeyPair.getPrivateKey().serialize() - self._con.execute(query, (device_id, public_key, private_key)) - self._con.commit() - - def saveIdentity(self, recipientId, identityKey): - query = '''INSERT INTO identities (recipient_id, public_key, trust, shown) - VALUES(?, ?, ?, ?)''' - if not self.containsIdentity(recipientId, identityKey): - trust = self.getDefaultTrust(recipientId) - self._con.execute(query, (recipientId, - identityKey.getPublicKey().serialize(), - trust, - 1 if trust == Trust.BLIND else 0)) - self._con.commit() - - def containsIdentity(self, recipientId, identityKey): - query = '''SELECT * FROM identities WHERE recipient_id = ? - AND public_key = ?''' - - public_key = identityKey.getPublicKey().serialize() - result = self._con.execute(query, (recipientId, - public_key)).fetchone() - - return result is not None - - def deleteIdentity(self, recipientId, identityKey): - query = '''DELETE FROM identities - WHERE recipient_id = ? AND public_key = ?''' - public_key = identityKey.getPublicKey().serialize() - self._con.execute(query, (recipientId, public_key)) - self._con.commit() - - def isTrustedIdentity(self, recipientId, identityKey): - return True - - def getTrustForIdentity(self, recipientId, identityKey): - query = '''SELECT trust FROM identities WHERE recipient_id = ? - AND public_key = ?''' - public_key = identityKey.getPublicKey().serialize() - result = self._con.execute(query, (recipientId, public_key)).fetchone() - return result.trust if result is not None else None - - def getFingerprints(self, jid): - query = '''SELECT recipient_id, - public_key as "public_key [pk]", - trust, - timestamp - FROM identities - WHERE recipient_id = ? ORDER BY trust ASC''' - return self._con.execute(query, (jid,)).fetchall() - - def getMucFingerprints(self, jids): - query = ''' - SELECT recipient_id, - public_key as "public_key [pk]", - trust, - timestamp - FROM identities - WHERE recipient_id IN ({}) ORDER BY trust ASC - '''.format(', '.join(['?'] * len(jids))) - - return self._con.execute(query, jids).fetchall() - - def hasUndecidedFingerprints(self, jid): - query = '''SELECT public_key as "public_key [pk]" FROM identities - WHERE recipient_id = ? AND trust = ?''' - result = self._con.execute(query, (jid, Trust.UNDECIDED)).fetchall() - undecided = [row.public_key for row in result] - - inactive = self.getInactiveSessionsKeys(jid) - undecided = set(undecided) - set(inactive) - return bool(undecided) - - def getDefaultTrust(self, jid): - if not self._is_blind_trust_enabled(): - return Trust.UNDECIDED - - query = '''SELECT * FROM identities - WHERE recipient_id = ? AND trust IN (0, 1)''' - result = self._con.execute(query, (jid,)).fetchone() - if result is None: - return Trust.BLIND - return Trust.UNDECIDED - - def getTrustedFingerprints(self, jid): - query = '''SELECT public_key as "public_key [pk]" FROM identities - WHERE recipient_id = ? AND trust IN(1, 3)''' - result = self._con.execute(query, (jid,)).fetchall() - return [row.public_key for row in result] - - def getNewFingerprints(self, jid): - query = '''SELECT _id FROM identities WHERE shown = 0 - AND recipient_id = ?''' - - result = self._con.execute(query, (jid,)).fetchall() - return [row.id for row in result] - - def setShownFingerprints(self, fingerprints): - query = 'UPDATE identities SET shown = 1 WHERE _id IN ({})'.format( - ', '.join(['?'] * len(fingerprints))) - self._con.execute(query, fingerprints) - self._con.commit() - - def setTrust(self, recipient_id, identityKey, trust): - query = '''UPDATE identities SET trust = ? WHERE public_key = ? - AND recipient_id = ?''' - public_key = identityKey.getPublicKey().serialize() - self._con.execute(query, (trust, public_key, recipient_id)) - self._con.commit() - - def isTrusted(self, recipient_id, device_id): - record = self.loadSession(recipient_id, device_id) - if record.isFresh(): - return False - identity_key = record.getSessionState().getRemoteIdentityKey() - return self.getTrustForIdentity( - recipient_id, identity_key) in (Trust.VERIFIED, Trust.BLIND) - - def getIdentityLastSeen(self, recipient_id, identity_key): - identity_key = identity_key.getPublicKey().serialize() - query = '''SELECT timestamp FROM identities - WHERE recipient_id = ? AND public_key = ?''' - result = self._con.execute(query, (recipient_id, - identity_key)).fetchone() - return result.timestamp if result is not None else None - - def setIdentityLastSeen(self, recipient_id, identity_key): - timestamp = int(time.time()) - identity_key = identity_key.getPublicKey().serialize() - self._log.info('Set last seen for %s %s', recipient_id, timestamp) - query = '''UPDATE identities SET timestamp = ? - WHERE recipient_id = ? AND public_key = ?''' - self._con.execute(query, (timestamp, recipient_id, identity_key)) - self._con.commit() - - def getUnacknowledgedCount(self, recipient_id, device_id): - record = self.loadSession(recipient_id, device_id) - if record.isFresh(): - return 0 - state = record.getSessionState() - return state.getSenderChainKey().getIndex() diff --git a/omemo/backend/state.py b/omemo/backend/state.py deleted file mode 100644 index ce3b099..0000000 --- a/omemo/backend/state.py +++ /dev/null @@ -1,362 +0,0 @@ -# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com> -# Copyright (C) 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de> -# -# This file is part of OMEMO Gajim Plugin. -# -# OMEMO Gajim Plugin is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# OMEMO Gajim Plugin is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>. - -import time -from collections import defaultdict - -from nbxmpp.structs import OMEMOBundle -from nbxmpp.structs import OMEMOMessage - -from axolotl.ecc.djbec import DjbECPublicKey -from axolotl.identitykey import IdentityKey - -from axolotl.protocol.prekeywhispermessage import PreKeyWhisperMessage -from axolotl.protocol.whispermessage import WhisperMessage -from axolotl.sessionbuilder import SessionBuilder -from axolotl.sessioncipher import SessionCipher -from axolotl.state.prekeybundle import PreKeyBundle -from axolotl.util.keyhelper import KeyHelper -from axolotl.duplicatemessagexception import DuplicateMessageException - -from omemo.backend.aes import aes_decrypt -from omemo.backend.aes import aes_encrypt -from omemo.backend.aes import get_new_key -from omemo.backend.aes import get_new_iv -from omemo.backend.devices import DeviceManager -from omemo.backend.devices import NoDevicesFound -from omemo.backend.liteaxolotlstore import LiteAxolotlStore -from omemo.backend.util import get_fingerprint -from omemo.backend.util import Trust -from omemo.backend.util import DEFAULT_PREKEY_AMOUNT -from omemo.backend.util import MIN_PREKEY_AMOUNT -from omemo.backend.util import SPK_CYCLE_TIME -from omemo.backend.util import SPK_ARCHIVE_TIME -from omemo.backend.util import UNACKNOWLEDGED_COUNT - - -class OmemoState(DeviceManager): - def __init__(self, own_jid, db_path, account, xmpp_con): - self._account = account - self._own_jid = own_jid - self._log = xmpp_con._log - self._session_ciphers = defaultdict(dict) - self._storage = LiteAxolotlStore(db_path, self._log) - - DeviceManager.__init__(self) - - self.xmpp_con = xmpp_con - - self._log.info('%s PreKeys available', - self._storage.getPreKeyCount()) - - def build_session(self, jid, device_id, bundle): - session = SessionBuilder(self._storage, self._storage, self._storage, - self._storage, jid, device_id) - - registration_id = self._storage.getLocalRegistrationId() - - prekey = bundle.pick_prekey() - otpk = DjbECPublicKey(prekey['key'][1:]) - - spk = DjbECPublicKey(bundle.spk['key'][1:]) - ik = IdentityKey(DjbECPublicKey(bundle.ik[1:])) - - prekey_bundle = PreKeyBundle(registration_id, - device_id, - prekey['id'], - otpk, - bundle.spk['id'], - spk, - bundle.spk_signature, - ik) - - session.processPreKeyBundle(prekey_bundle) - self._get_session_cipher(jid, device_id) - - @property - def storage(self): - return self._storage - - @property - def own_fingerprint(self): - return get_fingerprint(self._storage.getIdentityKeyPair()) - - @property - def bundle(self): - self._check_pre_key_count() - - bundle = {'otpks': []} - for k in self._storage.loadPendingPreKeys(): - key = k.getKeyPair().getPublicKey().serialize() - bundle['otpks'].append({'key': key, 'id': k.getId()}) - - ik_pair = self._storage.getIdentityKeyPair() - bundle['ik'] = ik_pair.getPublicKey().serialize() - - self._cycle_signed_pre_key(ik_pair) - - spk = self._storage.loadSignedPreKey( - self._storage.getCurrentSignedPreKeyId()) - bundle['spk_signature'] = spk.getSignature() - bundle['spk'] = {'key': spk.getKeyPair().getPublicKey().serialize(), - 'id': spk.getId()} - - return OMEMOBundle(**bundle) - - def decrypt_message(self, omemo_message, jid): - if omemo_message.sid == self.own_device: - self._log.info('Received previously sent message by us') - raise SelfMessage - - try: - encrypted_key, prekey = omemo_message.keys[self.own_device] - except KeyError: - self._log.info('Received message not for our device') - raise MessageNotForDevice - - try: - if prekey: - key, fingerprint, trust = self._process_pre_key_message( - jid, omemo_message.sid, encrypted_key) - else: - key, fingerprint, trust = self._process_message( - jid, omemo_message.sid, encrypted_key) - - except DuplicateMessageException: - self._log.info('Received duplicated message') - raise DuplicateMessage - - except Exception as error: - self._log.warning(error) - raise DecryptionFailed - - if omemo_message.payload is None: - self._log.debug("Decrypted Key Exchange Message") - raise KeyExchangeMessage - - try: - result = aes_decrypt(key, omemo_message.iv, omemo_message.payload) - except Exception as error: - self._log.warning(error) - raise DecryptionFailed - - self._log.debug("Decrypted Message => %s", result) - return result, fingerprint, trust - - def _get_whisper_message(self, jid, device, key): - cipher = self._get_session_cipher(jid, device) - cipher_key = cipher.encrypt(key) - prekey = isinstance(cipher_key, PreKeyWhisperMessage) - return cipher_key.serialize(), prekey - - def encrypt(self, jid, plaintext): - try: - devices_for_encryption = self.get_devices_for_encryption(jid) - except NoDevicesFound: - self._log.warning('No devices for encryption found for: %s', jid) - return - - result = aes_encrypt(plaintext) - whisper_messages = defaultdict(dict) - - for jid_, device in devices_for_encryption: - count = self._storage.getUnacknowledgedCount(jid_, device) - if count >= UNACKNOWLEDGED_COUNT: - self._log.warning('Set device inactive %s because of %s ' - 'unacknowledged messages', device, count) - self.remove_device(jid_, device) - - try: - whisper_messages[jid_][device] = self._get_whisper_message( - jid_, device, result.key) - except Exception: - self._log.exception('Failed to encrypt') - continue - - recipients = set(whisper_messages.keys()) - if jid != self._own_jid: - recipients -= set([self._own_jid]) - if not recipients: - self._log.error('Encrypted keys empty') - return - - encrypted_keys = {} - for jid_ in whisper_messages: - encrypted_keys.update(whisper_messages[jid_]) - - self._log.debug('Finished encrypting message') - return OMEMOMessage(sid=self.own_device, - keys=encrypted_keys, - iv=result.iv, - payload=result.payload) - - def encrypt_key_transport(self, jid, devices): - whisper_messages = defaultdict(dict) - for device in devices: - try: - whisper_messages[jid][device] = self._get_whisper_message( - jid, device, get_new_key()) - except Exception: - self._log.exception('Failed to encrypt') - continue - - if not whisper_messages[jid]: - self._log.error('Encrypted keys empty') - return - - self._log.debug('Finished Key Transport message') - return OMEMOMessage(sid=self.own_device, - keys=whisper_messages[jid], - iv=get_new_iv(), - payload=None) - - def has_trusted_keys(self, jid): - inactive = self._storage.getInactiveSessionsKeys(jid) - trusted = self._storage.getTrustedFingerprints(jid) - return bool(set(trusted) - set(inactive)) - - def devices_without_sessions(self, jid): - known_devices = self.get_devices(jid, without_self=True) - missing_devices = [dev - for dev in known_devices - if not self._storage.containsSession(jid, dev)] - if missing_devices: - self._log.info('Missing device sessions for %s: %s', - jid, missing_devices) - return missing_devices - - def _get_session_cipher(self, jid, device_id): - try: - return self._session_ciphers[jid][device_id] - except KeyError: - cipher = SessionCipher(self._storage, self._storage, self._storage, - self._storage, jid, device_id) - self._session_ciphers[jid][device_id] = cipher - return cipher - - def _process_pre_key_message(self, jid, device, key): - self._log.info('Process pre key message from %s', jid) - pre_key_message = PreKeyWhisperMessage(serialized=key) - if not pre_key_message.getPreKeyId(): - raise Exception('Received Pre Key Message ' - 'without PreKey => %s' % jid) - - session_cipher = self._get_session_cipher(jid, device) - key = session_cipher.decryptPkmsg(pre_key_message) - - identity_key = pre_key_message.getIdentityKey() - trust = self._get_trust_from_identity_key(jid, identity_key) - fingerprint = get_fingerprint(identity_key) - - self._storage.setIdentityLastSeen(jid, identity_key) - - self.xmpp_con.set_bundle() - self.add_device(jid, device) - return key, fingerprint, trust - - def _process_message(self, jid, device, key): - self._log.info('Process message from %s', jid) - message = WhisperMessage(serialized=key) - - session_cipher = self._get_session_cipher(jid, device) - key = session_cipher.decryptMsg(message, textMsg=False) - - identity_key = self._get_identity_key_from_device(jid, device) - trust = self._get_trust_from_identity_key(jid, identity_key) - fingerprint = get_fingerprint(identity_key) - - self._storage.setIdentityLastSeen(jid, identity_key) - - self.add_device(jid, device) - - return key, fingerprint, trust - - @staticmethod - def _get_identity_key_from_pk_message(key): - pre_key_message = PreKeyWhisperMessage(serialized=key) - return pre_key_message.getIdentityKey() - - def _get_identity_key_from_device(self, jid, device): - session_record = self._storage.loadSession(jid, device) - return session_record.getSessionState().getRemoteIdentityKey() - - def _get_trust_from_identity_key(self, jid, identity_key): - trust = self._storage.getTrustForIdentity(jid, identity_key) - return Trust(trust) if trust is not None else Trust.UNDECIDED - - def _check_pre_key_count(self): - # Check if enough PreKeys are available - pre_key_count = self._storage.getPreKeyCount() - if pre_key_count < MIN_PREKEY_AMOUNT: - missing_count = DEFAULT_PREKEY_AMOUNT - pre_key_count - self._storage.generateNewPreKeys(missing_count) - self._log.info('%s PreKeys created', missing_count) - - def _cycle_signed_pre_key(self, ik_pair): - # Publish every SPK_CYCLE_TIME a new SignedPreKey - # Delete all exsiting SignedPreKeys that are older - # then SPK_ARCHIVE_TIME - - # Check if SignedPreKey exist and create if not - if not self._storage.getCurrentSignedPreKeyId(): - spk = KeyHelper.generateSignedPreKey( - ik_pair, self._storage.getNextSignedPreKeyId()) - self._storage.storeSignedPreKey(spk.getId(), spk) - self._log.debug('New SignedPreKey created, because none existed') - - # if SPK_CYCLE_TIME is reached, generate a new SignedPreKey - now = int(time.time()) - timestamp = self._storage.getSignedPreKeyTimestamp( - self._storage.getCurrentSignedPreKeyId()) - - if int(timestamp) < now - SPK_CYCLE_TIME: - spk = KeyHelper.generateSignedPreKey( - ik_pair, self._storage.getNextSignedPreKeyId()) - self._storage.storeSignedPreKey(spk.getId(), spk) - self._log.debug('Cycled SignedPreKey') - - # Delete all SignedPreKeys that are older than SPK_ARCHIVE_TIME - timestamp = now - SPK_ARCHIVE_TIME - self._storage.removeOldSignedPreKeys(timestamp) - - -class NoValidSessions(Exception): - pass - - -class SelfMessage(Exception): - pass - - -class MessageNotForDevice(Exception): - pass - - -class DecryptionFailed(Exception): - pass - - -class KeyExchangeMessage(Exception): - pass - - -class InvalidMessage(Exception): - pass - - -class DuplicateMessage(Exception): - pass diff --git a/omemo/backend/util.py b/omemo/backend/util.py deleted file mode 100644 index 9d07d5f..0000000 --- a/omemo/backend/util.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (C) 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de> -# -# This file is part of OMEMO Gajim Plugin. -# -# OMEMO Gajim Plugin is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# OMEMO Gajim Plugin is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>. - -import binascii -import textwrap -from enum import IntEnum - -from axolotl.identitykey import IdentityKey - -DEFAULT_PREKEY_AMOUNT = 100 -MIN_PREKEY_AMOUNT = 80 -SPK_ARCHIVE_TIME = 86400 * 15 # 15 Days -SPK_CYCLE_TIME = 86400 # 24 Hours -UNACKNOWLEDGED_COUNT = 2000 - - -class Trust(IntEnum): - UNTRUSTED = 0 - VERIFIED = 1 - UNDECIDED = 2 - BLIND = 3 - - -def get_fingerprint(identity_key, formatted=False): - public_key = identity_key.getPublicKey().serialize() - fingerprint = binascii.hexlify(public_key).decode()[2:] - if not formatted: - return fingerprint - fplen = len(fingerprint) - wordsize = fplen // 8 - buf = '' - for w in range(0, fplen, wordsize): - buf += '{0} '.format(fingerprint[w:w + wordsize]) - buf = textwrap.fill(buf, width=36) - return buf.rstrip().upper() - - -class IdentityKeyExtended(IdentityKey): - def __hash__(self): - return hash(self.publicKey.serialize()) - - def get_fingerprint(self, formatted=False): - return get_fingerprint(self, formatted=formatted) |