Commit 44534c13 authored by Philipp Hörist's avatar Philipp Hörist

asd2

parent c33eb4a0
import omemo
from .sql import SQLDatabase
class SessionManager:
def __init__(self, own_jid, db_con, account, xmpp_con):
# Database Inferface
self._store.SQLiteDatabase()
# OmemoSessionManager
self._sm = omemo.OmemoSessionManager(jid, self._store)
self._dm
def build_session(self, bundle):
self._store.createSession()
from omemo.signal.storage import Storage
class SQLite(Storage):
def loadState(self):
def storeState(self, state):
def listDevices(self, jid):
def loadSession(self, jid, device_id):
def storeSession(self, jid, device_id, session):
......@@ -20,7 +20,7 @@ from omemo.xmpp import (
BundleInformationQuery, DeviceListAnnouncement, DevicelistQuery,
OmemoMessage, successful, unpack_device_bundle,
unpack_device_list_update, unpack_encrypted)
from omemo.omemo.state import OmemoState
from omemoimpl.session_manager import SessionManager
DB_DIR_OLD = app.gajimpaths.data_root
DB_DIR_NEW = app.gajimpaths['MY_DATA']
......@@ -81,23 +81,22 @@ class OMEMOConnection:
return self.get_con().get_own_jid()
def migrate_dbpath(self):
old_dbpath = os.path.join(DB_DIR_OLD, 'omemo_' + self.account + '.db')
new_dbpath = os.path.join(DB_DIR_NEW, 'omemo_' + self.own_jid + '.db')
if os.path.exists(old_dbpath):
log.debug('Migrating DBName and Path ..')
try:
shutil.move(old_dbpath, new_dbpath)
return new_dbpath
except Exception:
log.exception('Migration Error:')
return old_dbpath
# old_dbpath = os.path.join(DB_DIR_OLD, 'omemo_' + self.account + '.db')
new_dbpath = os.path.join(DB_DIR_NEW, 'omemo2_' + self.own_jid + '.db')
# if os.path.exists(old_dbpath):
# log.debug('Migrating DBName and Path ..')
# try:
# shutil.move(old_dbpath, new_dbpath)
# return new_dbpath
# except Exception:
# log.exception('Migration Error:')
# return old_dbpath
return new_dbpath
def __get_omemo(self):
""" Returns the the OmemoState for the specified account.
Creates the OmemoState if it does not exist yet.
""" Creates the SessionManager for an account.
Parameters
----------
......@@ -106,11 +105,10 @@ class OMEMOConnection:
Returns
-------
OmemoState
SessionManager
"""
db_path = self.migrate_dbpath()
conn = sqlite3.connect(db_path, check_same_thread=False)
return SessionManager(self.own_jid, conn, self.account, self)
return SessionManager(self.own_jid, db_path, self.account, self)
def signed_in(self, event):
""" Method called on SignIn
......@@ -627,26 +625,22 @@ class OMEMOConnection:
if self.get_own_jid().bareMatch(contact_jid):
log.info('%s => Received own device list: %s',
self.account, devices_list)
self.omemo.set_own_devices(devices_list)
self.omemo.store.sessionStore.setActiveState(
devices_list, self.own_jid)
self.omemo.set_devicelist(contact_jid, devices_list)
# remove contact from list, so on send button pressed
# we query for bundle and build a session
if contact_jid in self.query_for_bundles:
self.query_for_bundles.remove(contact_jid)
if not self.omemo.own_device_id_published():
if self.omemo.get_own_device_id() not in devices_list:
# Our own device_id is not in the list, it could be
# overwritten by some other client
self.publish_own_devices_list()
else:
log.info('%s => Received device list for %s: %s',
self.account, contact_jid, devices_list)
self.omemo.set_devices(contact_jid, devices_list)
self.omemo.store.sessionStore.setActiveState(
devices_list, contact_jid)
self.omemo.set_devicelist(contact_jid, devices_list)
# remove contact from list, so on send button pressed
# we query for bundle and build a session
if contact_jid in self.query_for_bundles:
......@@ -666,17 +660,10 @@ class OMEMOConnection:
if True, a devicelist with only one
(the current id of this instance) device id is pushed
"""
if new:
devices_list = [self.omemo.own_device_id]
else:
devices_list = self.omemo.own_devices
devices_list.append(self.omemo.own_device_id)
devices_list = list(set(devices_list))
self.omemo.set_own_devices(devices_list)
own_devices = self.omemo.get_own_devices()
log.info('%s => Publishing own Devices: %s',
self.account, devices_list)
device_announce = DeviceListAnnouncement(devices_list)
self.account, own_devices)
device_announce = DeviceListAnnouncement(own_devices)
self.send_with_callback(device_announce,
self.device_list_publish_result)
......@@ -704,7 +691,7 @@ class OMEMOConnection:
if self.own_jid not in self.query_for_bundles:
devices_without_session = self.omemo \
.devices_without_sessions(self.own_jid)
.get_devices_without_session(self.own_jid)
self.query_for_bundles.append(self.own_jid)
......@@ -717,7 +704,7 @@ class OMEMOConnection:
if contact_jid not in self.query_for_bundles:
devices_without_session = self.omemo \
.devices_without_sessions(contact_jid)
.get_devices_without_session(contact_jid)
self.query_for_bundles.append(contact_jid)
......@@ -767,20 +754,21 @@ class OMEMOConnection:
The device id
"""
bundle_dict = unpack_device_bundle(stanza, device_id)
if not bundle_dict:
bundle = unpack_device_bundle(stanza, device_id)
if not bundle:
log.warning('Failed to build Session with %s', jid)
return
if self.omemo.build_session(jid, device_id, bundle_dict):
if self.omemo.build_session(jid, device_id, bundle):
log.info('%s => session created for: %s',
self.account, jid)
# Trigger dialog to trust new Fingerprints if
# the Chat Window is Open
ctrl = app.interface.msg_win_mgr.get_control(
jid, self.account)
if ctrl:
self.plugin.new_fingerprints_available(ctrl)
# TODO
# ctrl = app.interface.msg_win_mgr.get_control(
# jid, self.account)
# if ctrl:
# self.plugin.new_fingerprints_available(ctrl)
def query_devicelist(self, jid=None, fetch_bundle=False):
""" Query own devicelist from the server """
......@@ -803,7 +791,7 @@ class OMEMOConnection:
""" Publish our bundle information to the PEP node """
bundle_announce = BundleInformationAnnouncement(
self.omemo.bundle, self.omemo.own_device_id)
self.omemo.get_bundle(), self.omemo.get_own_device_id())
log.info('%s => Publishing bundle ...', self.account)
self.send_with_callback(bundle_announce, self.handle_publish_result)
......@@ -836,11 +824,9 @@ class OMEMOConnection:
self.publish_own_devices_list(new=True)
return
self.omemo.set_own_devices(devices_list)
self.omemo.store.sessionStore.setActiveState(
devices_list, self.own_jid)
self.omemo.set_devicelist(devices_list)
log.info('%s => Devicelistquery was successful', self.account)
if not self.omemo.own_device_id_published():
if self.omemo.get_own_device_id() not in devices_list:
# Our own device_id is not in the list, it could be
# overwritten by some other client
self.publish_own_devices_list()
......@@ -855,11 +841,10 @@ class OMEMOConnection:
"""
if not app.account_is_connected(self.account):
return
devices_list = [self.omemo.own_device_id]
self.omemo.set_own_devices(devices_list)
log.info('%s => Clearing devices_list %s', self.account, devices_list)
device_announce = DeviceListAnnouncement(devices_list)
self.omemo.clear_devicelist()
log.info('%s => Clearing devicelist', self.account)
device_announce = DeviceListAnnouncement(self.omemo.get_own_device_id())
self.send_with_callback(device_announce, self.clear_device_list_result)
@staticmethod
......
''' Database helper functions '''
def table_exists(db, name):
def table_exists(db_con, name):
""" Check if the specified table exists in the db. """
query = """ SELECT name FROM sqlite_master
WHERE type='table' AND name=?;
"""
return db.execute(query, (name, )).fetchone() is not None
return db_con.execute(query, (name, )).fetchone() is not None
def user_version(db):
def user_version(db_con):
""" Return the value of PRAGMA user_version. """
return db.execute('PRAGMA user_version').fetchone()[0]
return db_con.execute('PRAGMA user_version').fetchone()[0]
from omemo.util import generateDeviceID
class DeviceManager:
def __init__(self, store):
def __init__(self):
self._devices = {}
self._store = store
self.own_device_id = None
self._init_devices()
def _init_devices(self):
self._devices = self._store.getActiveDevices()
self.own_device_id = self._store.getOwnDeviceId()
self._devices = self.getActiveDevices()
self.own_device_id = self.getOwnDeviceId()
if self.own_device_id is None:
self.own_device_id = generateDeviceID()
def extend_devicelist(self, jid, device_list):
# Remove duplicates
device_list = set(device_list)
......@@ -19,7 +22,7 @@ class DeviceManager:
self.set_devicelist(jid, device_list)
else:
self._devices[jid].extend(device_list)
self._store.setDevicesActive(device_list)
self.setDevicesActive(device_list)
def set_devicelist(self, jid, device_list):
# Remove duplicates
......@@ -29,14 +32,14 @@ class DeviceManager:
device_list.remove(self.own_device_id)
if jid not in self._devices:
self._devices[jid] = device_list
self._store.setDevicesActive(device_list)
self.setDevicesActive(device_list)
else:
active = [d for d in device_list if d not in self._devices[jid]]
inactive = [d for d in self._devices[jid] if d not in device_list]
self._devices[jid] = device_list
self._store.setDevicesActive(active)
self._store.setDevicesInactive(inactive)
self.setDevicesActive(active)
self.setDevicesInactive(inactive)
def get_devicelist(self, jid):
if jid not in self._devices:
return []
......
from omemo import SessionManager
from .storage import SQLiteDatabase
class SessionManager:
def __init__(self, own_jid, db_path):
# Database Inferface
self._store = SQLiteDatabase(db_path)
# OmemoSessionManager
self._sm = SessionManager(own_jid, self._store)
def build_session(self, bundle):
self._store.createSession()
def get_bundle(self):
return self._sm.getState().getPublicBundle()
def set_devicelist(self, device_list, jid=None):
self._sm.newDeviceList(device_list, jid)
def get_own_device_id(self):
return self._store.own_device_id
def get_own_devices(self):
return self._sm.getDevices()['active']
def get_devices_without_session(self, jid):
return self._store.getDevicesWithoutSession(jid)
def get_trusted_fingerprints(self, jid):
return self._store.getTrustedFingerprints(jid)
def clear_devicelist(self):
return
\ No newline at end of file
......@@ -16,18 +16,30 @@
# You should have received a copy of the GNU General Public License along with
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
#
from .db_helpers import user_version
from omemo.signal.storage import Storage
import sqlite3
import pickle
import logging
from collections import namedtuple
from omemo.signal.storage import Storage
from omemo.x3dhdoubleratchet import X3DHDoubleRatchet
from omemo.signal.doubleratchet.doubleratchet import DoubleRatchet
from .db_helpers import user_version
log = logging.getLogger('gajim.plugin_system.omemo.db')
class SQLiteDatabase(Storage):
class SQLiteDatabase(Storage, DeviceManager):
""" SQLite Database """
def __init__(self, db_path):
sqlite3.register_adapter(Session, self._store_session)
sqlite3.register_converter("omemo_session", self._load_session)
sqlite3.register_adapter(X3DHDoubleRatchet, self._pickle_object)
sqlite3.register_adapter(DoubleRatchet, self._pickle_object)
sqlite3.register_converter("omemo_state", self._unpickle_object)
sqlite3.register_converter("omemo_session", self._unpickle_object)
self._con = sqlite3.connect(db_path,
detect_types=sqlite3.PARSE_DECLTYPES)
self._con.text_factory = bytes
......@@ -45,11 +57,19 @@ class SQLiteDatabase(Storage):
jid TEXT,
device_id INTEGER,
session omemo_session BLOB,
timestamp INTEGER,
fingerprint TEXT,
active INTEGER DEFAULT 1,
trust INTEGER DEFAULT 1,
UNIQUE(recipient_id, device_id));
UNIQUE(jid, device_id));
'''
create_tables = '''
CREATE TABLE IF NOT EXISTS state (
_id INTEGER PRIMARY KEY,
device_id INTEGER,
state omemo_state BLOB;
'''
create_db_sql = """
BEGIN TRANSACTION;
%s
......@@ -64,13 +84,13 @@ class SQLiteDatabase(Storage):
pass
@staticmethod
def _store_session(session):
def _pickle_object(session):
return pickle.dumps(session, pickle.HIGHEST_PROTOCOL)
@staticmethod
def _load_session(session):
def _unpickle_object(session):
return pickle.loads(session)
@staticmethod
def namedtuple_factory(cursor, row):
fields = [col[0] for col in cursor.description]
......@@ -79,27 +99,73 @@ class SQLiteDatabase(Storage):
return named_row
def loadState(self):
q = 'SELECT state FROM state'
result = self._con.execute(q).fetchone()
if result is not None:
return result.session
def storeState(self, state):
def listDevices(self, jid):
q = 'INSERT OR REPLACE INTO state(_id, state) VALUES(?)'
self._con.execute(q, (1, state))
self._con.commit()
def loadSession(self, jid, device_id):
q = "SELECT session FROM sessions WHERE jid = ? AND device_id = ?"
q = 'SELECT session FROM sessions WHERE jid = ? AND device_id = ?'
result = self._con.execute(q, (jid, device_id)).fetchone()
if result is not None:
return result.session
return result.session
def storeSession(self, jid, device_id, session):
q = "UPDATE sessions SET session = ? WHERE jid= ? AND device_id = ?"
q = 'UPDATE sessions SET session = ? WHERE jid= ? AND device_id = ?'
self._con.execute(q, (session, jid, device_id))
self._con.commit()
def createSession(self, jid, device_id, session):
q = "INSERT INTO sessions(jid, device_id, session, trust, active) VALUES (?, ?, ?, 1, 1)"
q = '''INSERT INTO sessions(jid, device_id, session, trust, active)
VALUES (?, ?, ?, 1, 1)'''
self._con.execute(q, (jid, device_id, session))
self._con.commit()
def loadActiveDevices(self, jid):
return self.loadDevices(jid, 1)
def loadInactiveDevices(self, jid):
return self.loadDevices(jid, 0)
def loadDevices(self, jid, active):
q = 'SELECT device_id FROM devices WHERE jid = ? AND active = ?'
result = self._con.execute(q, (jid, active)).fetchall()
if result:
return [row.device_id for row in result]
return []
def storeActiveDevices(self, jid, devices):
self.storeDevices(self, jid, devices, 1)
def storeInactiveDevices(self, jid, devices):
self.storeDevices(self, jid, devices, 0)
def storeDevices(self, jid, devices, active):
for device_id in devices:
try:
insert = '''INSERT INTO sessions(jid, device_id, active)
VALUES(?, ?, ?)'''
self._con.execute(insert, (jid, device_id, active))
self._con.commit()
except Exception:
log.exception('storeDevices()')
update = '''UPDATE sessions SET active = ?
WHERE jid = ? AND device_id = ?'''
self._con.execute(update, (active, jid, device_id))
self._con.commit()
def getDevicesWithoutSession(self, jid):
q = '''SELECT device_id FROM sessions
WHERE jid = ? AND (session IS NULL OR session = "")'''
result = self._con.execute(q, (jid,)).fetchall()
if result:
return [row.device_id for row in result]
return []
def getTrustedFingerprints(self, jid):
return True
......@@ -221,85 +221,89 @@ class OmemoPlugin(GajimPlugin):
return self.connections[account].omemo
def before_sendmessage(self, chat_control):
account = chat_control.account
contact = chat_control.contact
con = self.connections[account]
self.new_fingerprints_available(chat_control)
if isinstance(chat_control, GroupchatControl):
room = chat_control.room_jid
missing = True
own_jid = app.get_jid_from_account(account)
for nick in con.groupchat[room]:
real_jid = con.groupchat[room][nick]
if real_jid == own_jid:
continue
if not con.are_keys_missing(real_jid):
missing = False
if missing:
log.info('%s => No Trusted Fingerprints for %s',
account, room)
self.print_message(chat_control, UserMessages.NO_FINGERPRINTS)
else:
# check if we have devices for the contact
if not self.get_omemo(account).device_list_for(contact.jid):
con.query_devicelist(contact.jid, True)
self.print_message(chat_control, UserMessages.QUERY_DEVICES)
chat_control.sendmessage = False
return
# check if bundles are missing for some devices
if con.are_keys_missing(contact.jid):
log.info('%s => No Trusted Fingerprints for %s',
account, contact.jid)
self.print_message(chat_control, UserMessages.NO_FINGERPRINTS)
chat_control.sendmessage = False
else:
log.debug('%s => Sending Message to %s',
account, contact.jid)
return
#TODO
# account = chat_control.account
# contact = chat_control.contact
# con = self.connections[account]
# self.new_fingerprints_available(chat_control)
# if isinstance(chat_control, GroupchatControl):
# room = chat_control.room_jid
# missing = True
# own_jid = app.get_jid_from_account(account)
# for nick in con.groupchat[room]:
# real_jid = con.groupchat[room][nick]
# if real_jid == own_jid:
# continue
# if not con.are_keys_missing(real_jid):
# missing = False
# if missing:
# log.info('%s => No Trusted Fingerprints for %s',
# account, room)
# self.print_message(chat_control, UserMessages.NO_FINGERPRINTS)
# else:
# # check if we have devices for the contact
# if not self.get_omemo(account).device_list_for(contact.jid):
# con.query_devicelist(contact.jid, True)
# self.print_message(chat_control, UserMessages.QUERY_DEVICES)
# chat_control.sendmessage = False
# return
# # check if bundles are missing for some devices
# if con.are_keys_missing(contact.jid):
# log.info('%s => No Trusted Fingerprints for %s',
# account, contact.jid)
# self.print_message(chat_control, UserMessages.NO_FINGERPRINTS)
# chat_control.sendmessage = False
# else:
# log.debug('%s => Sending Message to %s',
# account, contact.jid)
def new_fingerprints_available(self, chat_control):
jid = chat_control.contact.jid
account = chat_control.account
con = self.connections[account]
omemo = self.get_omemo(account)
if isinstance(chat_control, GroupchatControl):
room_jid = chat_control.room_jid
if room_jid in con.groupchat:
for nick in con.groupchat[room_jid]:
real_jid = con.groupchat[room_jid][nick]
fingerprints = omemo.store. \
getNewFingerprints(real_jid)
if fingerprints:
self.show_fingerprint_window(
chat_control, fingerprints)
elif not isinstance(chat_control, GroupchatControl):
fingerprints = omemo.store.getNewFingerprints(jid)
if fingerprints:
self.show_fingerprint_window(
chat_control, fingerprints)
return
# jid = chat_control.contact.jid
# account = chat_control.account
# con = self.connections[account]
# omemo = self.get_omemo(account)
# if isinstance(chat_control, GroupchatControl):
# room_jid = chat_control.room_jid
# if room_jid in con.groupchat:
# for nick in con.groupchat[room_jid]:
# real_jid = con.groupchat[room_jid][nick]
# fingerprints = omemo.store. \
# getNewFingerprints(real_jid)
# if fingerprints:
# self.show_fingerprint_window(
# chat_control, fingerprints)
# elif not isinstance(chat_control, GroupchatControl):
# fingerprints = omemo.store.getNewFingerprints(jid)
# if fingerprints:
# self.show_fingerprint_window(
# chat_control, fingerprints)
def show_fingerprint_window(self, chat_control, fingerprints=None):
contact = chat_control.contact
account = chat_control.account
omemo = self.get_omemo(account)
transient = chat_control.parent_win.window
if 'dialog' not in self.windowinstances:
if isinstance(chat_control, GroupchatControl):
self.windowinstances['dialog'] = \
FingerprintWindow(self, contact, transient,
self.windowinstances, groupchat=True)
else:
self.windowinstances['dialog'] = \
FingerprintWindow(self, contact, transient,
self.windowinstances)
self.windowinstances['dialog'].show_all()
if fingerprints:
log.debug('%s => Showing Fingerprint Prompt for %s',
account, contact.jid)
omemo.store.setShownFingerprints(fingerprints)
else:
self.windowinstances['dialog'].update_context_list()
if fingerprints:
omemo.store.setShownFingerprints(fingerprints)
return
# contact = chat_control.contact
# account = chat_control.account
# omemo = self.get_omemo(account)
# transient = chat_control.parent_win.window
# if 'dialog' not in self.windowinstances:
# if isinstance(chat_control, GroupchatControl):
# self.windowinstances['dialog'] = \
# FingerprintWindow(self, contact, transient,
# self.windowinstances, groupchat=True)
# else:
# self.windowinstances['dialog'] = \
# FingerprintWindow(self, contact, transient,
# self.windowinstances)
# self.windowinstances['dialog'].show_all()
# if fingerprints:
# log.debug('%s => Showing Fingerprint Prompt for %s',
# account, contact.jid)
# omemo.store.setShownFingerprints(fingerprints)
# else:
# self.windowinstances['dialog'].update_context_list()
# if fingerprints:
# omemo.store.setShownFingerprints(fingerprints)
@staticmethod
def print_message(chat_control, kind):
......
......@@ -33,6 +33,7 @@ from gajim.common.pep import AbstractPEP # pylint: disable=import-error
from gajim.plugins.helpers import log_calls # pylint: disable=import-error
from omemo.signal.x3dh import ExtendedPublicBundle
from omemo.wireformat import decodePublicKey
NS_PUBSUB_EVENT = NS_PUBSUB + '#event'
......@@ -109,35 +110,33 @@ class BundleInformationQuery(Iq):
class BundleInformationAnnouncement(Iq):
def __init__(self, state_bundle, device_id):
def __init__(self, bundle, device_id):
id_ = app.get_an_id()
attrs = {'id': id_}
Iq.__init__(self, typ='set', attrs=attrs)
bundle_node = self.make_bundle_node(state_bundle)
bundle_node = self.make_bundle_node(bundle)
publish = PublishNode(NS_BUNDLES + str(device_id), bundle_node)
pubsub = PubsubNode(publish)
self.addChild(node=pubsub)
def make_bundle_node(self, state_bundle):
def make_bundle_node(self, bundle):
result = Node('bundle', attrs={'xmlns': NS_OMEMO})
prekey_pub_node = result.addChild(
'signedPreKeyPublic',
attrs={'signedPreKeyId': state_bundle['signedPreKeyId']})
prekey_pub_node.addData(state_bundle['signedPreKeyPublic']
.decode('utf-8'))
attrs={'signedPreKeyId': bundle.spk['id']})
prekey_pub_node.addData(bundle.spk['key'].decode('utf-8'))
prekey_sig_node = result.addChild('signedPreKeySignature')
prekey_sig_node.addData(state_bundle['signedPreKeySignature']
.decode('utf-8'))
prekey_sig_node.addData(bundle.spk_signature.decode('utf-8'))
identity_key_node = result.addChild('identityKey')
identity_key_node.addData(state_bundle['identityKey'].decode('utf-8'))
identity_key_node.addData(bundle.ik.decode('utf-8'))
prekeys = result.addChild('prekeys')
for key in state_bundle['prekeys']:
for key in bundle.otpks:
prekeys.addChild('preKeyPublic',
attrs={'preKeyId': key[0]}) \
.addData(key[1].decode('utf-8'))
attrs={'preKeyId': key['id']}) \
.addData(key['key'].decode('utf-8'))
return result
......@@ -187,7 +186,7 @@ def unpack_device_bundle(bundle, device_id):
return
spk = {}
spk['key'] = wireformat.decodePublicKey(decode_data(signed_prekey_node))
spk['key'