From 8b63fd9badc5eb74db155eeb0c04c23ce73bd019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Tue, 12 Feb 2019 22:38:30 +0100 Subject: [PATCH] [omemo] Refactor AxolotlStore - Merge all stores into AxolotlStore --- omemo/backend/db_helpers.py | 15 - omemo/backend/encryption.py | 64 --- omemo/backend/liteaxolotlstore.py | 650 ++++++++++++++++++++----- omemo/backend/liteidentitykeystore.py | 174 ------- omemo/backend/liteprekeystore.py | 87 ---- omemo/backend/litesessionstore.py | 143 ------ omemo/backend/litesignedprekeystore.py | 113 ----- omemo/backend/sql.py | 154 ------ omemo/backend/state.py | 107 ++-- omemo/file_crypto.py | 2 +- omemo/modules/omemo.py | 5 +- 11 files changed, 595 insertions(+), 919 deletions(-) delete mode 100644 omemo/backend/db_helpers.py delete mode 100644 omemo/backend/encryption.py delete mode 100644 omemo/backend/liteidentitykeystore.py delete mode 100644 omemo/backend/liteprekeystore.py delete mode 100644 omemo/backend/litesessionstore.py delete mode 100644 omemo/backend/litesignedprekeystore.py delete mode 100644 omemo/backend/sql.py diff --git a/omemo/backend/db_helpers.py b/omemo/backend/db_helpers.py deleted file mode 100644 index 2045955..0000000 --- a/omemo/backend/db_helpers.py +++ /dev/null @@ -1,15 +0,0 @@ -''' Database helper functions ''' - - -def table_exists(db, 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 - - -def user_version(db): - """ Return the value of PRAGMA user_version. """ - return db.execute('PRAGMA user_version').fetchone()[0] diff --git a/omemo/backend/encryption.py b/omemo/backend/encryption.py deleted file mode 100644 index 6cb6983..0000000 --- a/omemo/backend/encryption.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2015 Bahtiar `kalkin-` Gadimov -# Copyright 2015 Daniel Gultsch -# -# This file is part of Gajim-OMEMO plugin. -# -# The Gajim-OMEMO 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, either version 3 of the License, or (at your option) any -# later version. -# -# Gajim-OMEMO 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 -# the Gajim-OMEMO plugin. If not, see . -# - - -class EncryptionState(): - """ Used to store if OMEMO is enabled or not between gajim restarts """ - - def __init__(self, dbConn): - """ - :type dbConn: Connection - """ - self.dbConn = dbConn - - def activate(self, jid): - q = """INSERT OR REPLACE INTO encryption_state (jid, encryption) - VALUES (?, 1) """ - - c = self.dbConn.cursor() - c.execute(q, (jid, )) - self.dbConn.commit() - - def deactivate(self, jid): - q = """INSERT OR REPLACE INTO encryption_state (jid, encryption) - VALUES (?, 0)""" - - c = self.dbConn.cursor() - c.execute(q, (jid, )) - self.dbConn.commit() - - def is_active(self, jid): - q = 'SELECT encryption FROM encryption_state where jid = ?;' - c = self.dbConn.cursor() - c.execute(q, (jid, )) - result = c.fetchone() - if result is None: - return False - return result[0] - - def exist(self, jid): - q = 'SELECT encryption FROM encryption_state where jid = ?;' - c = self.dbConn.cursor() - c.execute(q, (jid, )) - result = c.fetchone() - if result is None: - return False - else: - return True diff --git a/omemo/backend/liteaxolotlstore.py b/omemo/backend/liteaxolotlstore.py index 4ee00d1..0edd4b5 100644 --- a/omemo/backend/liteaxolotlstore.py +++ b/omemo/backend/liteaxolotlstore.py @@ -1,33 +1,35 @@ -# -*- coding: utf-8 -*- +# Copyright (C) 2019 Philipp Hörist +# Copyright (C) 2015 Tarek Galal # -# Copyright 2015 Tarek Galal +# This file is part of OMEMO Gajim Plugin. # -# This file is part of Gajim-OMEMO 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. # -# The Gajim-OMEMO 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, either version 3 of the License, or (at your option) any -# later version. -# -# Gajim-OMEMO 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 -# the Gajim-OMEMO plugin. If not, see . +# 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 . + import logging 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.identitykey import IdentityKey +from axolotl.identitykeypair import IdentityKeyPair +from axolotl.util.medium import Medium from axolotl.util.keyhelper import KeyHelper -from .liteidentitykeystore import LiteIdentityKeyStore -from .liteprekeystore import LitePreKeyStore -from .litesessionstore import LiteSessionStore -from .litesignedprekeystore import LiteSignedPreKeyStore -from .encryption import EncryptionState -from .sql import SQLDatabase log = logging.getLogger('gajim.plugin_system.omemo') @@ -36,24 +38,28 @@ MIN_PREKEY_AMOUNT = 80 SPK_ARCHIVE_TIME = 86400 * 15 # 15 Days SPK_CYCLE_TIME = 86400 # 24 Hours +UNDECIDED = 2 +TRUSTED = 1 +UNTRUSTED = 0 + class LiteAxolotlStore(AxolotlStore): def __init__(self, connection): - try: - connection.text_factory = bytes - except(AttributeError): - raise AssertionError('Expected a sqlite3.Connection got ' + - str(connection)) - - self.sql = SQLDatabase(connection) - self.identityKeyStore = LiteIdentityKeyStore(connection) - self.preKeyStore = LitePreKeyStore(connection) - self.signedPreKeyStore = LiteSignedPreKeyStore(connection) - self.sessionStore = LiteSessionStore(connection) - self.encryptionStore = EncryptionState(connection) + self.dbConn = connection + self.dbConn.text_factory = bytes + self.createDb() + self.migrateDb() + c = self.dbConn.cursor() + c.execute("PRAGMA synchronous=NORMAL;") + c.execute("PRAGMA journal_mode;") + mode = c.fetchone()[0] + # WAL is a persistent DB mode, don't override it if user has set it + if mode != 'wal': + c.execute("PRAGMA journal_mode=MEMORY;") + self.dbConn.commit() if not self.getLocalRegistrationId(): - log.info("Generating Axolotl keys") + log.info("Generating OMEMO keys") self._generate_axolotl_keys() def _generate_axolotl_keys(self): @@ -71,116 +77,542 @@ class LiteAxolotlStore(AxolotlStore): for preKey in preKeys: self.storePreKey(preKey.getId(), preKey) - def getIdentityKeyPair(self): - return self.identityKeyStore.getIdentityKeyPair() + def user_version(self): + return self.dbConn.execute('PRAGMA user_version').fetchone()[0] - def storeLocalData(self, registrationId, identityKeyPair): - self.identityKeyStore.storeLocalData(registrationId, identityKeyPair) + def createDb(self): + if self.user_version() == 0: - def getLocalRegistrationId(self): - return self.identityKeyStore.getLocalRegistrationId() + create_tables = ''' + CREATE TABLE IF NOT EXISTS identities ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, recipient_id TEXT, + registration_id INTEGER, public_key BLOB, private_key BLOB, + next_prekey_id INTEGER, timestamp INTEGER, trust INTEGER, + shown INTEGER DEFAULT 0); - def saveIdentity(self, recepientId, identityKey): - self.identityKeyStore.saveIdentity(recepientId, identityKey) + CREATE UNIQUE INDEX IF NOT EXISTS + public_key_index ON identities (public_key, recipient_id); - def deleteIdentity(self, recipientId, identityKey): - self.identityKeyStore.deleteIdentity(recipientId, identityKey) + CREATE TABLE IF NOT EXISTS prekeys( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + prekey_id INTEGER UNIQUE, sent_to_server BOOLEAN, + record BLOB); - def isTrustedIdentity(self, recepientId, identityKey): - return self.identityKeyStore.isTrustedIdentity(recepientId, - identityKey) + CREATE TABLE IF NOT EXISTS signed_prekeys ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + prekey_id INTEGER UNIQUE, + timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB); - def setTrust(self, identityKey, trust): - return self.identityKeyStore.setTrust(identityKey, trust) + 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)); - def getTrustedFingerprints(self, jid): - return self.identityKeyStore.getTrustedFingerprints(jid) + CREATE TABLE IF NOT EXISTS encryption_state ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + jid TEXT UNIQUE, + encryption INTEGER + ); + ''' - def getUndecidedFingerprints(self, jid): - return self.identityKeyStore.getUndecidedFingerprints(jid) + create_db_sql = """ + BEGIN TRANSACTION; + %s + PRAGMA user_version=5; + END TRANSACTION; + """ % (create_tables) + self.dbConn.executescript(create_db_sql) - def setShownFingerprints(self, jid): - return self.identityKeyStore.setShownFingerprints(jid) + def migrateDb(self): + """ Migrates the DB + """ - def getNewFingerprints(self, jid): - return self.identityKeyStore.getNewFingerprints(jid) + # 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 + ); + """ - def loadPreKey(self, preKeyId): - return self.preKeyStore.loadPreKey(preKeyId) + self.dbConn.executescript(""" BEGIN TRANSACTION; + %s + PRAGMA user_version=2; + END TRANSACTION; + """ % (delete_dupes)) - def loadPreKeys(self): - return self.preKeyStore.loadPendingPreKeys() + 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); + """ - def storePreKey(self, preKeyId, preKeyRecord): - self.preKeyStore.storePreKey(preKeyId, preKeyRecord) + self.dbConn.executescript(""" BEGIN TRANSACTION; + %s + PRAGMA user_version=3; + END TRANSACTION; + """ % (add_index)) - def containsPreKey(self, preKeyId): - return self.preKeyStore.containsPreKey(preKeyId) + if self.user_version() < 4: + # Adds column "active" to the sessions table + add_active = """ ALTER TABLE sessions + ADD COLUMN active INTEGER DEFAULT 1; + """ - def removePreKey(self, preKeyId): - self.preKeyStore.removePreKey(preKeyId) + self.dbConn.executescript(""" BEGIN TRANSACTION; + %s + PRAGMA user_version=4; + END TRANSACTION; + """ % (add_active)) - def loadSession(self, recepientId, deviceId): - return self.sessionStore.loadSession(recepientId, deviceId) + 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; + """ - def getActiveDeviceTuples(self): - return self.sessionStore.getActiveDeviceTuples() + self.dbConn.executescript(""" BEGIN TRANSACTION; + %s + PRAGMA user_version=5; + END TRANSACTION; + """ % (add_timestamp)) - def getInactiveSessionsKeys(self, recipientId): - return self.sessionStore.getInactiveSessionsKeys(recipientId) - def getSubDeviceSessions(self, recepientId): - # TODO Reuse this - return self.sessionStore.getSubDeviceSessions(recepientId) - - def getJidFromDevice(self, device_id): - return self.sessionStore.getJidFromDevice(device_id) - - def storeSession(self, recepientId, deviceId, sessionRecord): - self.sessionStore.storeSession(recepientId, deviceId, sessionRecord) - - def containsSession(self, recepientId, deviceId): - return self.sessionStore.containsSession(recepientId, deviceId) - - def deleteSession(self, recepientId, deviceId): - self.sessionStore.deleteSession(recepientId, deviceId) - - def deleteAllSessions(self, recepientId): - self.sessionStore.deleteAllSessions(recepientId) - - def getSessionsFromJid(self, recipientId): - return self.sessionStore.getSessionsFromJid(recipientId) - - def getSessionsFromJids(self, recipientId): - return self.sessionStore.getSessionsFromJids(recipientId) - - def getAllSessions(self): - return self.sessionStore.getAllSessions() def loadSignedPreKey(self, signedPreKeyId): - return self.signedPreKeyStore.loadSignedPreKey(signedPreKeyId) + q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?" + + cursor = self.dbConn.cursor() + cursor.execute(q, (signedPreKeyId, )) + + result = cursor.fetchone() + if not result: + raise InvalidKeyIdException("No such signedprekeyrecord! %s " % + signedPreKeyId) + + return SignedPreKeyRecord(serialized=result[0]) def loadSignedPreKeys(self): - return self.signedPreKeyStore.loadSignedPreKeys() + q = "SELECT record FROM signed_prekeys" + + cursor = self.dbConn.cursor() + cursor.execute(q, ) + result = cursor.fetchall() + results = [] + for row in result: + results.append(SignedPreKeyRecord(serialized=row[0])) + + return results def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord): - self.signedPreKeyStore.storeSignedPreKey(signedPreKeyId, - signedPreKeyRecord) + q = "INSERT INTO signed_prekeys (prekey_id, record) VALUES(?,?)" + cursor = self.dbConn.cursor() + cursor.execute(q, (signedPreKeyId, signedPreKeyRecord.serialize())) + self.dbConn.commit() def containsSignedPreKey(self, signedPreKeyId): - return self.signedPreKeyStore.containsSignedPreKey(signedPreKeyId) + q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?" + cursor = self.dbConn.cursor() + cursor.execute(q, (signedPreKeyId, )) + return cursor.fetchone() is not None def removeSignedPreKey(self, signedPreKeyId): - self.signedPreKeyStore.removeSignedPreKey(signedPreKeyId) + q = "DELETE FROM signed_prekeys WHERE prekey_id = ?" + cursor = self.dbConn.cursor() + cursor.execute(q, (signedPreKeyId, )) + self.dbConn.commit() def getNextSignedPreKeyId(self): - return self.signedPreKeyStore.getNextSignedPreKeyId() + result = self.getCurrentSignedPreKeyId() + if not result: + return 1 # StartId if no SignedPreKeys exist + else: + return (result % (Medium.MAX_VALUE - 1)) + 1 def getCurrentSignedPreKeyId(self): - return self.signedPreKeyStore.getCurrentSignedPreKeyId() + q = "SELECT MAX(prekey_id) FROM signed_prekeys" + + cursor = self.dbConn.cursor() + cursor.execute(q) + result = cursor.fetchone() + if not result: + return None + else: + return result[0] def getSignedPreKeyTimestamp(self, signedPreKeyId): - return self.signedPreKeyStore.getSignedPreKeyTimestamp(signedPreKeyId) + q = "SELECT strftime('%s', timestamp) FROM " \ + "signed_prekeys WHERE prekey_id = ?" + + cursor = self.dbConn.cursor() + cursor.execute(q, (signedPreKeyId, )) + + result = cursor.fetchone() + if not result: + raise InvalidKeyIdException("No such signedprekeyrecord! %s " % + signedPreKeyId) + + return result[0] def removeOldSignedPreKeys(self, timestamp): - self.signedPreKeyStore.removeOldSignedPreKeys(timestamp) + q = "DELETE FROM signed_prekeys " \ + "WHERE timestamp < datetime(?, 'unixepoch')" + cursor = self.dbConn.cursor() + cursor.execute(q, (timestamp, )) + self.dbConn.commit() + + def loadSession(self, recipientId, deviceId): + q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?" + c = self.dbConn.cursor() + c.execute(q, (recipientId, deviceId)) + result = c.fetchone() + + if result: + return SessionRecord(serialized=result[0]) + else: + return SessionRecord() + + def getSubDeviceSessions(self, recipientId): + q = "SELECT device_id from sessions WHERE recipient_id = ?" + c = self.dbConn.cursor() + c.execute(q, (recipientId, )) + result = c.fetchall() + + deviceIds = [r[0] for r in result] + return deviceIds + + def getJidFromDevice(self, device_id): + q = "SELECT recipient_id from sessions WHERE device_id = ?" + c = self.dbConn.cursor() + c.execute(q, (device_id, )) + result = c.fetchone() + + return result[0].decode('utf-8') if result else None + + def getActiveDeviceTuples(self): + q = "SELECT recipient_id, device_id FROM sessions WHERE active = 1" + c = self.dbConn.cursor() + result = [] + for row in c.execute(q): + result.append((row[0].decode('utf-8'), row[1])) + return result + + def storeSession(self, recipientId, deviceId, sessionRecord): + self.deleteSession(recipientId, deviceId) + + q = "INSERT INTO sessions(recipient_id, device_id, record) VALUES(?,?,?)" + c = self.dbConn.cursor() + c.execute(q, (recipientId, deviceId, sessionRecord.serialize())) + self.dbConn.commit() + + def containsSession(self, recipientId, deviceId): + q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?" + c = self.dbConn.cursor() + c.execute(q, (recipientId, deviceId)) + result = c.fetchone() + + return result is not None + + def deleteSession(self, recipientId, deviceId): + q = "DELETE FROM sessions WHERE recipient_id = ? AND device_id = ?" + self.dbConn.cursor().execute(q, (recipientId, deviceId)) + self.dbConn.commit() + + def deleteAllSessions(self, recipientId): + q = "DELETE FROM sessions WHERE recipient_id = ?" + self.dbConn.cursor().execute(q, (recipientId, )) + self.dbConn.commit() + + def getAllSessions(self): + q = "SELECT _id, recipient_id, device_id, record, active from sessions" + c = self.dbConn.cursor() + result = [] + for row in c.execute(q): + result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4])) + return result + + def getSessionsFromJid(self, recipientId): + q = "SELECT _id, recipient_id, device_id, record, active from sessions" \ + " WHERE recipient_id = ?" + c = self.dbConn.cursor() + result = [] + for row in c.execute(q, (recipientId,)): + result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4])) + return result + + def getSessionsFromJids(self, recipientId): + q = "SELECT _id, recipient_id, device_id, record, active from sessions" \ + " WHERE recipient_id IN ({})" \ + .format(', '.join(['?'] * len(recipientId))) + c = self.dbConn.cursor() + result = [] + for row in c.execute(q, recipientId): + result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4])) + return result + + def setActiveState(self, deviceList, jid): + c = self.dbConn.cursor() + + q = "UPDATE sessions SET active = {} " \ + "WHERE recipient_id = '{}' AND device_id IN ({})" \ + .format(1, jid, ', '.join(['?'] * len(deviceList))) + c.execute(q, deviceList) + + q = "UPDATE sessions SET active = {} " \ + "WHERE recipient_id = '{}' AND device_id NOT IN ({})" \ + .format(0, jid, ', '.join(['?'] * len(deviceList))) + c.execute(q, deviceList) + self.dbConn.commit() + + def getInactiveSessionsKeys(self, recipientId): + q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?" + c = self.dbConn.cursor() + result = [] + for row in c.execute(q, (recipientId,)): + public_key = (SessionRecord(serialized=row[0]). + getSessionState().getRemoteIdentityKey(). + getPublicKey()) + result.append(public_key.serialize()) + return result + + def loadPreKey(self, preKeyId): + q = "SELECT record FROM prekeys WHERE prekey_id = ?" + + cursor = self.dbConn.cursor() + cursor.execute(q, (preKeyId, )) + + result = cursor.fetchone() + if not result: + raise Exception("No such prekeyRecord!") + + return PreKeyRecord(serialized=result[0]) + + def loadPendingPreKeys(self): + q = "SELECT record FROM prekeys" + cursor = self.dbConn.cursor() + cursor.execute(q) + result = cursor.fetchall() + + return [PreKeyRecord(serialized=r[0]) for r in result] + + def storePreKey(self, preKeyId, preKeyRecord): + q = "INSERT INTO prekeys (prekey_id, record) VALUES(?,?)" + cursor = self.dbConn.cursor() + cursor.execute(q, (preKeyId, preKeyRecord.serialize())) + self.dbConn.commit() + + def containsPreKey(self, preKeyId): + q = "SELECT record FROM prekeys WHERE prekey_id = ?" + cursor = self.dbConn.cursor() + cursor.execute(q, (preKeyId, )) + return cursor.fetchone() is not None + + def removePreKey(self, preKeyId): + q = "DELETE FROM prekeys WHERE prekey_id = ?" + cursor = self.dbConn.cursor() + cursor.execute(q, (preKeyId, )) + self.dbConn.commit() + + def getCurrentPreKeyId(self): + q = "SELECT MAX(prekey_id) FROM prekeys" + cursor = self.dbConn.cursor() + cursor.execute(q) + return cursor.fetchone()[0] + + def getPreKeyCount(self): + q = "SELECT COUNT(prekey_id) FROM prekeys" + cursor = self.dbConn.cursor() + cursor.execute(q) + return cursor.fetchone()[0] + + def generateNewPreKeys(self, count): + startId = self.getCurrentPreKeyId() + 1 + preKeys = KeyHelper.generatePreKeys(startId, count) + + for preKey in preKeys: + self.storePreKey(preKey.getId(), preKey) + + def getIdentityKeyPair(self): + q = "SELECT public_key, private_key FROM identities " + \ + "WHERE recipient_id = -1" + c = self.dbConn.cursor() + c.execute(q) + result = c.fetchone() + + publicKey, privateKey = result + return IdentityKeyPair( + IdentityKey(DjbECPublicKey(publicKey[1:])), + DjbECPrivateKey(privateKey)) + + def getLocalRegistrationId(self): + q = "SELECT registration_id FROM identities WHERE recipient_id = -1" + c = self.dbConn.cursor() + c.execute(q) + result = c.fetchone() + return result[0] if result else None + + def storeLocalData(self, registrationId, identityKeyPair): + q = "INSERT INTO identities( " + \ + "recipient_id, registration_id, public_key, private_key) " + \ + "VALUES(-1, ?, ?, ?)" + c = self.dbConn.cursor() + c.execute(q, + (registrationId, + identityKeyPair.getPublicKey().getPublicKey().serialize(), + identityKeyPair.getPrivateKey().serialize())) + + self.dbConn.commit() + + def saveIdentity(self, recipientId, identityKey): + q = "INSERT INTO identities (recipient_id, public_key, trust) " \ + "VALUES(?, ?, ?)" + c = self.dbConn.cursor() + + if not self.getIdentity(recipientId, identityKey): + c.execute(q, (recipientId, + identityKey.getPublicKey().serialize(), + UNDECIDED)) + self.dbConn.commit() + + def getIdentity(self, recipientId, identityKey): + q = "SELECT * FROM identities WHERE recipient_id = ? " \ + "AND public_key = ?" + c = self.dbConn.cursor() + + c.execute(q, (recipientId, identityKey.getPublicKey().serialize())) + result = c.fetchone() + + return result is not None + + def deleteIdentity(self, recipientId, identityKey): + q = "DELETE FROM identities WHERE recipient_id = ? AND public_key = ?" + c = self.dbConn.cursor() + c.execute(q, (recipientId, + identityKey.getPublicKey().serialize())) + self.dbConn.commit() + + def isTrustedIdentity(self, recipientId, identityKey): + q = "SELECT trust FROM identities WHERE recipient_id = ? " \ + "AND public_key = ?" + c = self.dbConn.cursor() + + c.execute(q, (recipientId, identityKey.getPublicKey().serialize())) + result = c.fetchone() + + states = [UNTRUSTED, TRUSTED, UNDECIDED] + + if result and result[0] in states: + return result[0] + else: + return True + + def getAllFingerprints(self): + q = "SELECT _id, recipient_id, public_key, trust FROM identities " \ + "WHERE recipient_id != -1 ORDER BY recipient_id ASC" + c = self.dbConn.cursor() + + result = [] + for row in c.execute(q): + result.append((row[0], row[1], row[2], row[3])) + return result + + def getFingerprints(self, jid): + q = "SELECT _id, recipient_id, public_key, trust FROM identities " \ + "WHERE recipient_id =? ORDER BY trust ASC" + c = self.dbConn.cursor() + + result = [] + c.execute(q, (jid,)) + rows = c.fetchall() + for row in rows: + result.append((row[0], row[1], row[2], row[3])) + return result + + def getTrustedFingerprints(self, jid): + q = "SELECT public_key FROM identities WHERE recipient_id = ? AND trust = ?" + c = self.dbConn.cursor() + + result = [] + c.execute(q, (jid, TRUSTED)) + rows = c.fetchall() + for row in rows: + result.append(row[0]) + return result + + def getUndecidedFingerprints(self, jid): + q = "SELECT trust FROM identities WHERE recipient_id = ? AND trust = ?" + c = self.dbConn.cursor() + + result = [] + c.execute(q, (jid, UNDECIDED)) + result = c.fetchall() + + return result + + def getNewFingerprints(self, jid): + q = "SELECT _id FROM identities WHERE shown = 0 AND " \ + "recipient_id = ?" + c = self.dbConn.cursor() + result = [] + for row in c.execute(q, (jid,)): + result.append(row[0]) + return result + + def setShownFingerprints(self, fingerprints): + q = "UPDATE identities SET shown = 1 WHERE _id IN ({})" \ + .format(', '.join(['?'] * len(fingerprints))) + c = self.dbConn.cursor() + c.execute(q, fingerprints) + self.dbConn.commit() + + def setTrust(self, identityKey, trust): + q = "UPDATE identities SET trust = ? WHERE public_key = ?" + c = self.dbConn.cursor() + c.execute(q, (trust, identityKey.getPublicKey().serialize())) + self.dbConn.commit() + + def activate(self, jid): + q = """INSERT OR REPLACE INTO encryption_state (jid, encryption) + VALUES (?, 1) """ + + c = self.dbConn.cursor() + c.execute(q, (jid, )) + self.dbConn.commit() + + def deactivate(self, jid): + q = """INSERT OR REPLACE INTO encryption_state (jid, encryption) + VALUES (?, 0)""" + + c = self.dbConn.cursor() + c.execute(q, (jid, )) + self.dbConn.commit() + + def is_active(self, jid): + q = 'SELECT encryption FROM encryption_state where jid = ?;' + c = self.dbConn.cursor() + c.execute(q, (jid, )) + result = c.fetchone() + if result is None: + return False + return result[0] + + def exist(self, jid): + q = 'SELECT encryption FROM encryption_state where jid = ?;' + c = self.dbConn.cursor() + c.execute(q, (jid, )) + result = c.fetchone() + if result is None: + return False + else: + return True diff --git a/omemo/backend/liteidentitykeystore.py b/omemo/backend/liteidentitykeystore.py deleted file mode 100644 index 1e02e63..0000000 --- a/omemo/backend/liteidentitykeystore.py +++ /dev/null @@ -1,174 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2015 Tarek Galal -# -# This file is part of Gajim-OMEMO plugin. -# -# The Gajim-OMEMO 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, either version 3 of the License, or (at your option) any -# later version. -# -# Gajim-OMEMO 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 -# the Gajim-OMEMO plugin. If not, see . -# - -from axolotl.ecc.djbec import DjbECPrivateKey, DjbECPublicKey -from axolotl.identitykey import IdentityKey -from axolotl.identitykeypair import IdentityKeyPair -from axolotl.state.identitykeystore import IdentityKeyStore - -UNDECIDED = 2 -TRUSTED = 1 -UNTRUSTED = 0 - - -class LiteIdentityKeyStore(IdentityKeyStore): - def __init__(self, dbConn): - """ - :type dbConn: Connection - """ - self.dbConn = dbConn - - def getIdentityKeyPair(self): - q = "SELECT public_key, private_key FROM identities " + \ - "WHERE recipient_id = -1" - c = self.dbConn.cursor() - c.execute(q) - result = c.fetchone() - - publicKey, privateKey = result - return IdentityKeyPair( - IdentityKey(DjbECPublicKey(publicKey[1:])), - DjbECPrivateKey(privateKey)) - - def getLocalRegistrationId(self): - q = "SELECT registration_id FROM identities WHERE recipient_id = -1" - c = self.dbConn.cursor() - c.execute(q) - result = c.fetchone() - return result[0] if result else None - - def storeLocalData(self, registrationId, identityKeyPair): - q = "INSERT INTO identities( " + \ - "recipient_id, registration_id, public_key, private_key) " + \ - "VALUES(-1, ?, ?, ?)" - c = self.dbConn.cursor() - c.execute(q, - (registrationId, - identityKeyPair.getPublicKey().getPublicKey().serialize(), - identityKeyPair.getPrivateKey().serialize())) - - self.dbConn.commit() - - def saveIdentity(self, recipientId, identityKey): - q = "INSERT INTO identities (recipient_id, public_key, trust) " \ - "VALUES(?, ?, ?)" - c = self.dbConn.cursor() - - if not self.getIdentity(recipientId, identityKey): - c.execute(q, (recipientId, - identityKey.getPublicKey().serialize(), - UNDECIDED)) - self.dbConn.commit() - - def getIdentity(self, recipientId, identityKey): - q = "SELECT * FROM identities WHERE recipient_id = ? " \ - "AND public_key = ?" - c = self.dbConn.cursor() - - c.execute(q, (recipientId, identityKey.getPublicKey().serialize())) - result = c.fetchone() - - return result is not None - - def deleteIdentity(self, recipientId, identityKey): - q = "DELETE FROM identities WHERE recipient_id = ? AND public_key = ?" - c = self.dbConn.cursor() - c.execute(q, (recipientId, - identityKey.getPublicKey().serialize())) - self.dbConn.commit() - - def isTrustedIdentity(self, recipientId, identityKey): - q = "SELECT trust FROM identities WHERE recipient_id = ? " \ - "AND public_key = ?" - c = self.dbConn.cursor() - - c.execute(q, (recipientId, identityKey.getPublicKey().serialize())) - result = c.fetchone() - - states = [UNTRUSTED, TRUSTED, UNDECIDED] - - if result and result[0] in states: - return result[0] - else: - return True - - def getAllFingerprints(self): - q = "SELECT _id, recipient_id, public_key, trust FROM identities " \ - "WHERE recipient_id != -1 ORDER BY recipient_id ASC" - c = self.dbConn.cursor() - - result = [] - for row in c.execute(q): - result.append((row[0], row[1], row[2], row[3])) - return result - - def getFingerprints(self, jid): - q = "SELECT _id, recipient_id, public_key, trust FROM identities " \ - "WHERE recipient_id =? ORDER BY trust ASC" - c = self.dbConn.cursor() - - result = [] - c.execute(q, (jid,)) - rows = c.fetchall() - for row in rows: - result.append((row[0], row[1], row[2], row[3])) - return result - - def getTrustedFingerprints(self, jid): - q = "SELECT public_key FROM identities WHERE recipient_id = ? AND trust = ?" - c = self.dbConn.cursor() - - result = [] - c.execute(q, (jid, TRUSTED)) - rows = c.fetchall() - for row in rows: - result.append(row[0]) - return result - - def getUndecidedFingerprints(self, jid): - q = "SELECT trust FROM identities WHERE recipient_id = ? AND trust = ?" - c = self.dbConn.cursor() - - result = [] - c.execute(q, (jid, UNDECIDED)) - result = c.fetchall() - - return result - - def getNewFingerprints(self, jid): - q = "SELECT _id FROM identities WHERE shown = 0 AND " \ - "recipient_id = ?" - c = self.dbConn.cursor() - result = [] - for row in c.execute(q, (jid,)): - result.append(row[0]) - return result - - def setShownFingerprints(self, fingerprints): - q = "UPDATE identities SET shown = 1 WHERE _id IN ({})" \ - .format(', '.join(['?'] * len(fingerprints))) - c = self.dbConn.cursor() - c.execute(q, fingerprints) - self.dbConn.commit() - - def setTrust(self, identityKey, trust): - q = "UPDATE identities SET trust = ? WHERE public_key = ?" - c = self.dbConn.cursor() - c.execute(q, (trust, identityKey.getPublicKey().serialize())) - self.dbConn.commit() diff --git a/omemo/backend/liteprekeystore.py b/omemo/backend/liteprekeystore.py deleted file mode 100644 index fb671aa..0000000 --- a/omemo/backend/liteprekeystore.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2015 Tarek Galal -# -# This file is part of Gajim-OMEMO plugin. -# -# The Gajim-OMEMO 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, either version 3 of the License, or (at your option) any -# later version. -# -# Gajim-OMEMO 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 -# the Gajim-OMEMO plugin. If not, see . -# - -from axolotl.state.prekeyrecord import PreKeyRecord -from axolotl.state.prekeystore import PreKeyStore -from axolotl.util.keyhelper import KeyHelper - - -class LitePreKeyStore(PreKeyStore): - def __init__(self, dbConn): - """ - :type dbConn: Connection - """ - self.dbConn = dbConn - - def loadPreKey(self, preKeyId): - q = "SELECT record FROM prekeys WHERE prekey_id = ?" - - cursor = self.dbConn.cursor() - cursor.execute(q, (preKeyId, )) - - result = cursor.fetchone() - if not result: - raise Exception("No such prekeyRecord!") - - return PreKeyRecord(serialized=result[0]) - - def loadPendingPreKeys(self): - q = "SELECT record FROM prekeys" - cursor = self.dbConn.cursor() - cursor.execute(q) - result = cursor.fetchall() - - return [PreKeyRecord(serialized=r[0]) for r in result] - - def storePreKey(self, preKeyId, preKeyRecord): - q = "INSERT INTO prekeys (prekey_id, record) VALUES(?,?)" - cursor = self.dbConn.cursor() - cursor.execute(q, (preKeyId, preKeyRecord.serialize())) - self.dbConn.commit() - - def containsPreKey(self, preKeyId): - q = "SELECT record FROM prekeys WHERE prekey_id = ?" - cursor = self.dbConn.cursor() - cursor.execute(q, (preKeyId, )) - return cursor.fetchone() is not None - - def removePreKey(self, preKeyId): - q = "DELETE FROM prekeys WHERE prekey_id = ?" - cursor = self.dbConn.cursor() - cursor.execute(q, (preKeyId, )) - self.dbConn.commit() - - def getCurrentPreKeyId(self): - q = "SELECT MAX(prekey_id) FROM prekeys" - cursor = self.dbConn.cursor() - cursor.execute(q) - return cursor.fetchone()[0] - - def getPreKeyCount(self): - q = "SELECT COUNT(prekey_id) FROM prekeys" - cursor = self.dbConn.cursor() - cursor.execute(q) - return cursor.fetchone()[0] - - def generateNewPreKeys(self, count): - startId = self.getCurrentPreKeyId() + 1 - preKeys = KeyHelper.generatePreKeys(startId, count) - - for preKey in preKeys: - self.storePreKey(preKey.getId(), preKey) diff --git a/omemo/backend/litesessionstore.py b/omemo/backend/litesessionstore.py deleted file mode 100644 index 6bfae99..0000000 --- a/omemo/backend/litesessionstore.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2015 Tarek Galal -# -# This file is part of Gajim-OMEMO plugin. -# -# The Gajim-OMEMO 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, either version 3 of the License, or (at your option) any -# later version. -# -# Gajim-OMEMO 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 -# the Gajim-OMEMO plugin. If not, see . -# - -from axolotl.state.sessionrecord import SessionRecord -from axolotl.state.sessionstore import SessionStore - - -class LiteSessionStore(SessionStore): - def __init__(self, dbConn): - """ - :type dbConn: Connection - """ - self.dbConn = dbConn - - def loadSession(self, recipientId, deviceId): - q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?" - c = self.dbConn.cursor() - c.execute(q, (recipientId, deviceId)) - result = c.fetchone() - - if result: - return SessionRecord(serialized=result[0]) - else: - return SessionRecord() - - def getSubDeviceSessions(self, recipientId): - q = "SELECT device_id from sessions WHERE recipient_id = ?" - c = self.dbConn.cursor() - c.execute(q, (recipientId, )) - result = c.fetchall() - - deviceIds = [r[0] for r in result] - return deviceIds - - def getJidFromDevice(self, device_id): - q = "SELECT recipient_id from sessions WHERE device_id = ?" - c = self.dbConn.cursor() - c.execute(q, (device_id, )) - result = c.fetchone() - - return result[0].decode('utf-8') if result else None - - def getActiveDeviceTuples(self): - q = "SELECT recipient_id, device_id FROM sessions WHERE active = 1" - c = self.dbConn.cursor() - result = [] - for row in c.execute(q): - result.append((row[0].decode('utf-8'), row[1])) - return result - - def storeSession(self, recipientId, deviceId, sessionRecord): - self.deleteSession(recipientId, deviceId) - - q = "INSERT INTO sessions(recipient_id, device_id, record) VALUES(?,?,?)" - c = self.dbConn.cursor() - c.execute(q, (recipientId, deviceId, sessionRecord.serialize())) - self.dbConn.commit() - - def containsSession(self, recipientId, deviceId): - q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?" - c = self.dbConn.cursor() - c.execute(q, (recipientId, deviceId)) - result = c.fetchone() - - return result is not None - - def deleteSession(self, recipientId, deviceId): - q = "DELETE FROM sessions WHERE recipient_id = ? AND device_id = ?" - self.dbConn.cursor().execute(q, (recipientId, deviceId)) - self.dbConn.commit() - - def deleteAllSessions(self, recipientId): - q = "DELETE FROM sessions WHERE recipient_id = ?" - self.dbConn.cursor().execute(q, (recipientId, )) - self.dbConn.commit() - - def getAllSessions(self): - q = "SELECT _id, recipient_id, device_id, record, active from sessions" - c = self.dbConn.cursor() - result = [] - for row in c.execute(q): - result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4])) - return result - - def getSessionsFromJid(self, recipientId): - q = "SELECT _id, recipient_id, device_id, record, active from sessions" \ - " WHERE recipient_id = ?" - c = self.dbConn.cursor() - result = [] - for row in c.execute(q, (recipientId,)): - result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4])) - return result - - def getSessionsFromJids(self, recipientId): - q = "SELECT _id, recipient_id, device_id, record, active from sessions" \ - " WHERE recipient_id IN ({})" \ - .format(', '.join(['?'] * len(recipientId))) - c = self.dbConn.cursor() - result = [] - for row in c.execute(q, recipientId): - result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4])) - return result - - def setActiveState(self, deviceList, jid): - c = self.dbConn.cursor() - - q = "UPDATE sessions SET active = {} " \ - "WHERE recipient_id = '{}' AND device_id IN ({})" \ - .format(1, jid, ', '.join(['?'] * len(deviceList))) - c.execute(q, deviceList) - - q = "UPDATE sessions SET active = {} " \ - "WHERE recipient_id = '{}' AND device_id NOT IN ({})" \ - .format(0, jid, ', '.join(['?'] * len(deviceList))) - c.execute(q, deviceList) - self.dbConn.commit() - - def getInactiveSessionsKeys(self, recipientId): - q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?" - c = self.dbConn.cursor() - result = [] - for row in c.execute(q, (recipientId,)): - public_key = (SessionRecord(serialized=row[0]). - getSessionState().getRemoteIdentityKey(). - getPublicKey()) - result.append(public_key.serialize()) - return result diff --git a/omemo/backend/litesignedprekeystore.py b/omemo/backend/litesignedprekeystore.py deleted file mode 100644 index fc9cccf..0000000 --- a/omemo/backend/litesignedprekeystore.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2015 Tarek Galal -# -# This file is part of Gajim-OMEMO plugin. -# -# The Gajim-OMEMO 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, either version 3 of the License, or (at your option) any -# later version. -# -# Gajim-OMEMO 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 -# the Gajim-OMEMO plugin. If not, see . -# - -from axolotl.invalidkeyidexception import InvalidKeyIdException -from axolotl.state.signedprekeyrecord import SignedPreKeyRecord -from axolotl.state.signedprekeystore import SignedPreKeyStore -from axolotl.util.medium import Medium - - -class LiteSignedPreKeyStore(SignedPreKeyStore): - def __init__(self, dbConn): - """ - :type dbConn: Connection - """ - self.dbConn = dbConn - - def loadSignedPreKey(self, signedPreKeyId): - q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?" - - cursor = self.dbConn.cursor() - cursor.execute(q, (signedPreKeyId, )) - - result = cursor.fetchone() - if not result: - raise InvalidKeyIdException("No such signedprekeyrecord! %s " % - signedPreKeyId) - - return SignedPreKeyRecord(serialized=result[0]) - - def loadSignedPreKeys(self): - q = "SELECT record FROM signed_prekeys" - - cursor = self.dbConn.cursor() - cursor.execute(q, ) - result = cursor.fetchall() - results = [] - for row in result: - results.append(SignedPreKeyRecord(serialized=row[0])) - - return results - - def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord): - q = "INSERT INTO signed_prekeys (prekey_id, record) VALUES(?,?)" - cursor = self.dbConn.cursor() - cursor.execute(q, (signedPreKeyId, signedPreKeyRecord.serialize())) - self.dbConn.commit() - - def containsSignedPreKey(self, signedPreKeyId): - q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?" - cursor = self.dbConn.cursor() - cursor.execute(q, (signedPreKeyId, )) - return cursor.fetchone() is not None - - def removeSignedPreKey(self, signedPreKeyId): - q = "DELETE FROM signed_prekeys WHERE prekey_id = ?" - cursor = self.dbConn.cursor() - cursor.execute(q, (signedPreKeyId, )) - self.dbConn.commit() - - def getNextSignedPreKeyId(self): - result = self.getCurrentSignedPreKeyId() - if not result: - return 1 # StartId if no SignedPreKeys exist - else: - return (result % (Medium.MAX_VALUE - 1)) + 1 - - def getCurrentSignedPreKeyId(self): - q = "SELECT MAX(prekey_id) FROM signed_prekeys" - - cursor = self.dbConn.cursor() - cursor.execute(q) - result = cursor.fetchone() - if not result: - return None - else: - return result[0] - - def getSignedPreKeyTimestamp(self, signedPreKeyId): - q = "SELECT strftime('%s', timestamp) FROM " \ - "signed_prekeys WHERE prekey_id = ?" - - cursor = self.dbConn.cursor() - cursor.execute(q, (signedPreKeyId, )) - - result = cursor.fetchone() - if not result: - raise InvalidKeyIdException("No such signedprekeyrecord! %s " % - signedPreKeyId) - - return result[0] - - def removeOldSignedPreKeys(self, timestamp): - q = "DELETE FROM signed_prekeys " \ - "WHERE timestamp < datetime(?, 'unixepoch')" - cursor = self.dbConn.cursor() - cursor.execute(q, (timestamp, )) - self.dbConn.commit() diff --git a/omemo/backend/sql.py b/omemo/backend/sql.py deleted file mode 100644 index f23b1cc..0000000 --- a/omemo/backend/sql.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2015 Tarek Galal -# -# This file is part of Gajim-OMEMO plugin. -# -# The Gajim-OMEMO 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, either version 3 of the License, or (at your option) any -# later version. -# -# Gajim-OMEMO 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 -# the Gajim-OMEMO plugin. If not, see . -# -from .db_helpers import user_version - - -class SQLDatabase(): - """ SQL Database """ - - def __init__(self, dbConn): - """ - :type dbConn: Connection - """ - self.dbConn = dbConn - self.createDb() - self.migrateDb() - c = self.dbConn.cursor() - c.execute("PRAGMA synchronous=NORMAL;") - c.execute("PRAGMA journal_mode;") - mode = c.fetchone()[0] - # WAL is a persistent DB mode, don't override it if user has set it - if mode != 'wal': - c.execute("PRAGMA journal_mode=MEMORY;") - self.dbConn.commit() - - def createDb(self): - if user_version(self.dbConn) == 0: - - # Creates - # IdentityKeyStore - # PreKeyStore - # SignedPreKeyStore - # SessionStore - # EncryptionStore - - create_tables = ''' - CREATE TABLE IF NOT EXISTS identities ( - _id INTEGER PRIMARY KEY AUTOINCREMENT, recipient_id TEXT, - registration_id INTEGER, public_key BLOB, private_key BLOB, - next_prekey_id INTEGER, 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 TABLE IF NOT EXISTS encryption_state ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - jid TEXT UNIQUE, - encryption INTEGER - ); - ''' - - create_db_sql = """ - BEGIN TRANSACTION; - %s - PRAGMA user_version=5; - END TRANSACTION; - """ % (create_tables) - self.dbConn.executescript(create_db_sql) - - def migrateDb(self): - """ Migrates the DB - """ - - # Find all double entries and delete them - if user_version(self.dbConn) < 2: - delete_dupes = """ DELETE FROM identities WHERE _id not in ( - SELECT MIN(_id) - FROM identities - GROUP BY - recipient_id, public_key - ); - """ - - self.dbConn.executescript(""" BEGIN TRANSACTION; - %s - PRAGMA user_version=2; - END TRANSACTION; - """ % (delete_dupes)) - - if user_version(self.dbConn) < 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.dbConn.executescript(""" BEGIN TRANSACTION; - %s - PRAGMA user_version=3; - END TRANSACTION; - """ % (add_index)) - - if user_version(self.dbConn) < 4: - # Adds column "active" to the sessions table - add_active = """ ALTER TABLE sessions - ADD COLUMN active INTEGER DEFAULT 1; - """ - - self.dbConn.executescript(""" BEGIN TRANSACTION; - %s - PRAGMA user_version=4; - END TRANSACTION; - """ % (add_active)) - - if user_version(self.dbConn) < 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.dbConn.executescript(""" BEGIN TRANSACTION; - %s - PRAGMA user_version=5; - END TRANSACTION; - """ % (add_timestamp)) diff --git a/omemo/backend/state.py b/omemo/backend/state.py index 165cdbf..8a913d3 100644 --- a/omemo/backend/state.py +++ b/omemo/backend/state.py @@ -1,21 +1,19 @@ -# -*- coding: utf-8 -*- +# Copyright (C) 2019 Philipp Hörist +# Copyright (C) 2015 Bahtiar `kalkin-` Gadimov # -# Copyright 2015 Bahtiar `kalkin-` Gadimov +# This file is part of OMEMO Gajim Plugin. # -# This file is part of Gajim-OMEMO 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. # -# The Gajim-OMEMO 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, either version 3 of the License, or (at your option) any -# later version. -# -# Gajim-OMEMO 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 -# the Gajim-OMEMO plugin. If not, see . +# 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 . import logging import time @@ -35,9 +33,12 @@ from axolotl.state.prekeybundle import PreKeyBundle from axolotl.util.keyhelper import KeyHelper from omemo.backend.aes import aes_decrypt, aes_encrypt -from .liteaxolotlstore import (LiteAxolotlStore, DEFAULT_PREKEY_AMOUNT, - MIN_PREKEY_AMOUNT, SPK_CYCLE_TIME, - SPK_ARCHIVE_TIME) +from omemo.backend.liteaxolotlstore import LiteAxolotlStore +from omemo.backend.liteaxolotlstore import DEFAULT_PREKEY_AMOUNT +from omemo.backend.liteaxolotlstore import MIN_PREKEY_AMOUNT +from omemo.backend.liteaxolotlstore import SPK_CYCLE_TIME +from omemo.backend.liteaxolotlstore import SPK_ARCHIVE_TIME + log = logging.getLogger('gajim.plugin_system.omemo') @@ -49,10 +50,6 @@ UNDECIDED = 2 class OmemoState: def __init__(self, own_jid, db_con, account, xmpp_con): - """ Instantiates an OmemoState object. - - :param connection: an :py:class:`sqlite3.Connection` - """ self.account = account self.xmpp_con = xmpp_con self.session_ciphers = {} @@ -60,20 +57,19 @@ class OmemoState: self.device_ids = {} self.own_devices = [] self.store = LiteAxolotlStore(db_con) - self.encryption = self.store.encryptionStore for jid, device_id in self.store.getActiveDeviceTuples(): if jid != own_jid: self.add_device(jid, device_id) else: self.add_own_device(device_id) - log.info(self.account + ' => Roster devices after boot:' + - str(self.device_ids)) - log.info(self.account + ' => Own devices after boot:' + - str(self.own_devices)) - log.debug(self.account + ' => ' + - str(self.store.preKeyStore.getPreKeyCount()) + - ' PreKeys available') + log.info('%s => Roster devices after boot: %s', + self.account, self.device_ids) + log.info('%s => Own devices after boot: %s', + self.account, self.own_devices) + log.debug('%s => %s PreKeys available', + self.account, + self.store.getPreKeyCount()) def build_session(self, recipient_id, device_id, bundle): sessionBuilder = SessionBuilder(self.store, self.store, self.store, @@ -110,7 +106,7 @@ class OmemoState: """ self.device_ids[name] = devices - log.info(self.account + ' => Saved devices for ' + name) + log.info('%s => Saved devices for %s', self.account, name) def add_device(self, name, device_id): if name not in self.device_ids: @@ -128,7 +124,7 @@ class OmemoState: A list of device_ids """ self.own_devices = devices - log.info(self.account + ' => Saved own devices') + log.info('%s => Saved own devices', self.account) def add_own_device(self, device_id): if device_id not in self.own_devices: @@ -140,7 +136,7 @@ class OmemoState: assert reg_id is not None, \ "Requested device_id but there is no generated" - return ((reg_id % 2147483646) + 1) + return (reg_id % 2147483646) + 1 def own_device_id_published(self): """ Return `True` only if own device id was added via @@ -153,7 +149,7 @@ class OmemoState: self.checkPreKeyAmount() bundle = {'otpks': []} - for k in self.store.loadPreKeys(): + for k in self.store.loadPendingPreKeys(): key = k.getKeyPair().getPublicKey().serialize() bundle['otpks'].append({'key': key, 'id': k.getId()}) @@ -261,7 +257,7 @@ class OmemoState: devices_list = self.device_list_for(jid, True) - result = aes_encrypt(plaintext, append_tag=True) + result = aes_encrypt(plaintext) for tup in devices_list: self.get_session_cipher(tup[0], tup[1]) @@ -382,8 +378,8 @@ class OmemoState: for dev in known_devices if not self.store.containsSession(jid, dev)] if missing_devices: - log.info(self.account + ' => Missing device sessions for ' + - jid + ': ' + str(missing_devices)) + log.info('%s => Missing device sessions for %s: %s', + self.account, jid, missing_devices) return missing_devices def get_session_cipher(self, jid, device_id): @@ -400,44 +396,43 @@ class OmemoState: def handlePreKeyWhisperMessage(self, recipient_id, device_id, key): preKeyWhisperMessage = PreKeyWhisperMessage(serialized=key) if not preKeyWhisperMessage.getPreKeyId(): - raise Exception("Received PreKeyWhisperMessage without PreKey =>" + - recipient_id) + raise Exception('Received PreKeyWhisperMessage ' + 'without PreKey => %s' % recipient_id) sessionCipher = self.get_session_cipher(recipient_id, device_id) try: - log.debug(self.account + - " => Received PreKeyWhisperMessage from " + - recipient_id) + log.debug('%s => Received PreKeyWhisperMessage from %s', + self.account, recipient_id) key = sessionCipher.decryptPkmsg(preKeyWhisperMessage) # Publish new bundle after PreKey has been used # for building a new Session self.xmpp_con.set_bundle() self.add_device(recipient_id, device_id) return key - except UntrustedIdentityException as e: - log.info(self.account + " => Received WhisperMessage " + - "from Untrusted Fingerprint! => " + e.getName()) + except UntrustedIdentityException as error: + log.info('%s => Received WhisperMessage ' + 'from Untrusted Fingerprint! => %s', + self.account, error.getName()) def handleWhisperMessage(self, recipient_id, device_id, key): whisperMessage = WhisperMessage(serialized=key) - log.debug(self.account + " => Received WhisperMessage from " + - recipient_id) + log.debug('%s => Received WhisperMessage from %s', + self.account, recipient_id) if self.isTrusted(recipient_id, device_id): sessionCipher = self.get_session_cipher(recipient_id, device_id) key = sessionCipher.decryptMsg(whisperMessage, textMsg=False) self.add_device(recipient_id, device_id) return key - else: - raise Exception("Received WhisperMessage " - "from Untrusted Fingerprint! => " + recipient_id) + + raise Exception('Received WhisperMessage ' + 'from Untrusted Fingerprint! => %s' % recipient_id) def checkPreKeyAmount(self): # Check if enough PreKeys are available - preKeyCount = self.store.preKeyStore.getPreKeyCount() + preKeyCount = self.store.getPreKeyCount() if preKeyCount < MIN_PREKEY_AMOUNT: newKeys = DEFAULT_PREKEY_AMOUNT - preKeyCount - self.store.preKeyStore.generateNewPreKeys(newKeys) - log.info(self.account + ' => ' + str(newKeys) + - ' PreKeys created') + self.store.generateNewPreKeys(newKeys) + log.info('%s => %s PreKeys created', self.account, newKeys) def cycleSignedPreKey(self, identityKeyPair): # Publish every SPK_CYCLE_TIME a new SignedPreKey @@ -449,8 +444,8 @@ class OmemoState: signedPreKey = KeyHelper.generateSignedPreKey( identityKeyPair, self.store.getNextSignedPreKeyId()) self.store.storeSignedPreKey(signedPreKey.getId(), signedPreKey) - log.debug(self.account + - ' => New SignedPreKey created, because none existed') + log.debug('%s => New SignedPreKey created, because none existed', + self.account) # if SPK_CYCLE_TIME is reached, generate a new SignedPreKey now = int(time.time()) @@ -461,7 +456,7 @@ class OmemoState: signedPreKey = KeyHelper.generateSignedPreKey( identityKeyPair, self.store.getNextSignedPreKeyId()) self.store.storeSignedPreKey(signedPreKey.getId(), signedPreKey) - log.debug(self.account + ' => Cycled SignedPreKey') + log.debug('%s => Cycled SignedPreKey', self.account) # Delete all SignedPreKeys that are older than SPK_ARCHIVE_TIME timestamp = now - SPK_ARCHIVE_TIME diff --git a/omemo/file_crypto.py b/omemo/file_crypto.py index 7dea01e..a4ed30c 100644 --- a/omemo/file_crypto.py +++ b/omemo/file_crypto.py @@ -88,7 +88,7 @@ class FileDecryption: if not self.is_encrypted(file): log.info('Url not encrypted: %s', url) return - print('ADASD') + self.create_paths(file) if os.path.exists(file.filepath): diff --git a/omemo/modules/omemo.py b/omemo/modules/omemo.py index 9cf6e37..b8ab57f 100644 --- a/omemo/modules/omemo.py +++ b/omemo/modules/omemo.py @@ -534,8 +534,7 @@ class OMEMO(BaseModule): log.info('%s => Received own device list: %s', self._account, devicelist) self.omemo.set_own_devices(devicelist) - self.omemo.store.sessionStore.setActiveState( - devicelist, self.own_jid) + self.omemo.store.setActiveState(devicelist, self.own_jid) # remove contact from list, so on send button pressed # we query for bundle and build a session @@ -551,7 +550,7 @@ class OMEMO(BaseModule): log.info('%s => Received device list for %s: %s', self._account, jid, devicelist) self.omemo.set_devices(jid, devicelist) - self.omemo.store.sessionStore.setActiveState(devicelist, jid) + self.omemo.store.setActiveState(devicelist, jid) # remove contact from list, so on send button pressed # we query for bundle and build a session