[omemo] Refactor Plugin
- Adapt to nbxmpp supporting OMEMO - Move python-axolotl code into backend folder
This commit is contained in:
@@ -1 +1 @@
|
|||||||
from .omemoplugin import OmemoPlugin
|
from omemo.plugin import OmemoPlugin
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.0"
|
__version__ = "0.1.0"
|
||||||
86
omemo/backend/aes.py
Normal file
86
omemo/backend/aes.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# 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.plugin_system.omemo')
|
||||||
|
|
||||||
|
EncryptionResult = namedtuple('EncryptionResult', 'payload key iv')
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
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, 16)
|
||||||
|
key += tag
|
||||||
|
return EncryptionResult(payload=payload, key=key, iv=iv)
|
||||||
|
|
||||||
|
|
||||||
|
def aes_encrypt_file(data):
|
||||||
|
key, iv, tag, payload, = _encrypt(data, 32, 16)
|
||||||
|
payload += tag
|
||||||
|
return EncryptionResult(payload=payload, key=key, iv=iv)
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
''' Database helper functions '''
|
''' Database helper functions '''
|
||||||
|
|
||||||
|
|
||||||
def table_exists(db, name):
|
def table_exists(db, name):
|
||||||
""" Check if the specified table exists in the db. """
|
""" Check if the specified table exists in the db. """
|
||||||
|
|
||||||
query = """ SELECT name FROM sqlite_master
|
query = """ SELECT name FROM sqlite_master
|
||||||
WHERE type='table' AND name=?;
|
WHERE type='table' AND name=?;
|
||||||
"""
|
"""
|
||||||
return db.execute(query, (name, )).fetchone() is not None
|
return db.execute(query, (name, )).fetchone() is not None
|
||||||
|
|
||||||
|
|
||||||
def user_version(db):
|
def user_version(db):
|
||||||
""" Return the value of PRAGMA user_version. """
|
""" Return the value of PRAGMA user_version. """
|
||||||
return db.execute('PRAGMA user_version').fetchone()[0]
|
return db.execute('PRAGMA user_version').fetchone()[0]
|
||||||
@@ -1,64 +1,64 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
||||||
# Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
|
# Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
|
||||||
#
|
#
|
||||||
# This file is part of Gajim-OMEMO plugin.
|
# This file is part of Gajim-OMEMO plugin.
|
||||||
#
|
#
|
||||||
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
# 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
|
# 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
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
# later version.
|
# later version.
|
||||||
#
|
#
|
||||||
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
# 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
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
# 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
|
# 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/>.
|
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class EncryptionState():
|
class EncryptionState():
|
||||||
""" Used to store if OMEMO is enabled or not between gajim restarts """
|
""" Used to store if OMEMO is enabled or not between gajim restarts """
|
||||||
|
|
||||||
def __init__(self, dbConn):
|
def __init__(self, dbConn):
|
||||||
"""
|
"""
|
||||||
:type dbConn: Connection
|
:type dbConn: Connection
|
||||||
"""
|
"""
|
||||||
self.dbConn = dbConn
|
self.dbConn = dbConn
|
||||||
|
|
||||||
def activate(self, jid):
|
def activate(self, jid):
|
||||||
q = """INSERT OR REPLACE INTO encryption_state (jid, encryption)
|
q = """INSERT OR REPLACE INTO encryption_state (jid, encryption)
|
||||||
VALUES (?, 1) """
|
VALUES (?, 1) """
|
||||||
|
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (jid, ))
|
c.execute(q, (jid, ))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def deactivate(self, jid):
|
def deactivate(self, jid):
|
||||||
q = """INSERT OR REPLACE INTO encryption_state (jid, encryption)
|
q = """INSERT OR REPLACE INTO encryption_state (jid, encryption)
|
||||||
VALUES (?, 0)"""
|
VALUES (?, 0)"""
|
||||||
|
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (jid, ))
|
c.execute(q, (jid, ))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def is_active(self, jid):
|
def is_active(self, jid):
|
||||||
q = 'SELECT encryption FROM encryption_state where jid = ?;'
|
q = 'SELECT encryption FROM encryption_state where jid = ?;'
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (jid, ))
|
c.execute(q, (jid, ))
|
||||||
result = c.fetchone()
|
result = c.fetchone()
|
||||||
if result is None:
|
if result is None:
|
||||||
return False
|
return False
|
||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
def exist(self, jid):
|
def exist(self, jid):
|
||||||
q = 'SELECT encryption FROM encryption_state where jid = ?;'
|
q = 'SELECT encryption FROM encryption_state where jid = ?;'
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (jid, ))
|
c.execute(q, (jid, ))
|
||||||
result = c.fetchone()
|
result = c.fetchone()
|
||||||
if result is None:
|
if result is None:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
@@ -1,186 +1,186 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of Gajim-OMEMO plugin.
|
# This file is part of Gajim-OMEMO plugin.
|
||||||
#
|
#
|
||||||
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
# 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
|
# 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
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
# later version.
|
# later version.
|
||||||
#
|
#
|
||||||
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
# 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
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
# 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
|
# 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/>.
|
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from axolotl.state.axolotlstore import AxolotlStore
|
from axolotl.state.axolotlstore import AxolotlStore
|
||||||
from axolotl.util.keyhelper import KeyHelper
|
from axolotl.util.keyhelper import KeyHelper
|
||||||
|
|
||||||
from .liteidentitykeystore import LiteIdentityKeyStore
|
from .liteidentitykeystore import LiteIdentityKeyStore
|
||||||
from .liteprekeystore import LitePreKeyStore
|
from .liteprekeystore import LitePreKeyStore
|
||||||
from .litesessionstore import LiteSessionStore
|
from .litesessionstore import LiteSessionStore
|
||||||
from .litesignedprekeystore import LiteSignedPreKeyStore
|
from .litesignedprekeystore import LiteSignedPreKeyStore
|
||||||
from .encryption import EncryptionState
|
from .encryption import EncryptionState
|
||||||
from .sql import SQLDatabase
|
from .sql import SQLDatabase
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||||
|
|
||||||
DEFAULT_PREKEY_AMOUNT = 100
|
DEFAULT_PREKEY_AMOUNT = 100
|
||||||
MIN_PREKEY_AMOUNT = 80
|
MIN_PREKEY_AMOUNT = 80
|
||||||
SPK_ARCHIVE_TIME = 86400 * 15 # 15 Days
|
SPK_ARCHIVE_TIME = 86400 * 15 # 15 Days
|
||||||
SPK_CYCLE_TIME = 86400 # 24 Hours
|
SPK_CYCLE_TIME = 86400 # 24 Hours
|
||||||
|
|
||||||
|
|
||||||
class LiteAxolotlStore(AxolotlStore):
|
class LiteAxolotlStore(AxolotlStore):
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
try:
|
try:
|
||||||
connection.text_factory = bytes
|
connection.text_factory = bytes
|
||||||
except(AttributeError):
|
except(AttributeError):
|
||||||
raise AssertionError('Expected a sqlite3.Connection got ' +
|
raise AssertionError('Expected a sqlite3.Connection got ' +
|
||||||
str(connection))
|
str(connection))
|
||||||
|
|
||||||
self.sql = SQLDatabase(connection)
|
self.sql = SQLDatabase(connection)
|
||||||
self.identityKeyStore = LiteIdentityKeyStore(connection)
|
self.identityKeyStore = LiteIdentityKeyStore(connection)
|
||||||
self.preKeyStore = LitePreKeyStore(connection)
|
self.preKeyStore = LitePreKeyStore(connection)
|
||||||
self.signedPreKeyStore = LiteSignedPreKeyStore(connection)
|
self.signedPreKeyStore = LiteSignedPreKeyStore(connection)
|
||||||
self.sessionStore = LiteSessionStore(connection)
|
self.sessionStore = LiteSessionStore(connection)
|
||||||
self.encryptionStore = EncryptionState(connection)
|
self.encryptionStore = EncryptionState(connection)
|
||||||
|
|
||||||
if not self.getLocalRegistrationId():
|
if not self.getLocalRegistrationId():
|
||||||
log.info("Generating Axolotl keys")
|
log.info("Generating Axolotl keys")
|
||||||
self._generate_axolotl_keys()
|
self._generate_axolotl_keys()
|
||||||
|
|
||||||
def _generate_axolotl_keys(self):
|
def _generate_axolotl_keys(self):
|
||||||
identityKeyPair = KeyHelper.generateIdentityKeyPair()
|
identityKeyPair = KeyHelper.generateIdentityKeyPair()
|
||||||
registrationId = KeyHelper.generateRegistrationId()
|
registrationId = KeyHelper.generateRegistrationId()
|
||||||
preKeys = KeyHelper.generatePreKeys(KeyHelper.getRandomSequence(),
|
preKeys = KeyHelper.generatePreKeys(KeyHelper.getRandomSequence(),
|
||||||
DEFAULT_PREKEY_AMOUNT)
|
DEFAULT_PREKEY_AMOUNT)
|
||||||
self.storeLocalData(registrationId, identityKeyPair)
|
self.storeLocalData(registrationId, identityKeyPair)
|
||||||
|
|
||||||
signedPreKey = KeyHelper.generateSignedPreKey(
|
signedPreKey = KeyHelper.generateSignedPreKey(
|
||||||
identityKeyPair, KeyHelper.getRandomSequence(65536))
|
identityKeyPair, KeyHelper.getRandomSequence(65536))
|
||||||
|
|
||||||
self.storeSignedPreKey(signedPreKey.getId(), signedPreKey)
|
self.storeSignedPreKey(signedPreKey.getId(), signedPreKey)
|
||||||
|
|
||||||
for preKey in preKeys:
|
for preKey in preKeys:
|
||||||
self.storePreKey(preKey.getId(), preKey)
|
self.storePreKey(preKey.getId(), preKey)
|
||||||
|
|
||||||
def getIdentityKeyPair(self):
|
def getIdentityKeyPair(self):
|
||||||
return self.identityKeyStore.getIdentityKeyPair()
|
return self.identityKeyStore.getIdentityKeyPair()
|
||||||
|
|
||||||
def storeLocalData(self, registrationId, identityKeyPair):
|
def storeLocalData(self, registrationId, identityKeyPair):
|
||||||
self.identityKeyStore.storeLocalData(registrationId, identityKeyPair)
|
self.identityKeyStore.storeLocalData(registrationId, identityKeyPair)
|
||||||
|
|
||||||
def getLocalRegistrationId(self):
|
def getLocalRegistrationId(self):
|
||||||
return self.identityKeyStore.getLocalRegistrationId()
|
return self.identityKeyStore.getLocalRegistrationId()
|
||||||
|
|
||||||
def saveIdentity(self, recepientId, identityKey):
|
def saveIdentity(self, recepientId, identityKey):
|
||||||
self.identityKeyStore.saveIdentity(recepientId, identityKey)
|
self.identityKeyStore.saveIdentity(recepientId, identityKey)
|
||||||
|
|
||||||
def deleteIdentity(self, recipientId, identityKey):
|
def deleteIdentity(self, recipientId, identityKey):
|
||||||
self.identityKeyStore.deleteIdentity(recipientId, identityKey)
|
self.identityKeyStore.deleteIdentity(recipientId, identityKey)
|
||||||
|
|
||||||
def isTrustedIdentity(self, recepientId, identityKey):
|
def isTrustedIdentity(self, recepientId, identityKey):
|
||||||
return self.identityKeyStore.isTrustedIdentity(recepientId,
|
return self.identityKeyStore.isTrustedIdentity(recepientId,
|
||||||
identityKey)
|
identityKey)
|
||||||
|
|
||||||
def setTrust(self, identityKey, trust):
|
def setTrust(self, identityKey, trust):
|
||||||
return self.identityKeyStore.setTrust(identityKey, trust)
|
return self.identityKeyStore.setTrust(identityKey, trust)
|
||||||
|
|
||||||
def getTrustedFingerprints(self, jid):
|
def getTrustedFingerprints(self, jid):
|
||||||
return self.identityKeyStore.getTrustedFingerprints(jid)
|
return self.identityKeyStore.getTrustedFingerprints(jid)
|
||||||
|
|
||||||
def getUndecidedFingerprints(self, jid):
|
def getUndecidedFingerprints(self, jid):
|
||||||
return self.identityKeyStore.getUndecidedFingerprints(jid)
|
return self.identityKeyStore.getUndecidedFingerprints(jid)
|
||||||
|
|
||||||
def setShownFingerprints(self, jid):
|
def setShownFingerprints(self, jid):
|
||||||
return self.identityKeyStore.setShownFingerprints(jid)
|
return self.identityKeyStore.setShownFingerprints(jid)
|
||||||
|
|
||||||
def getNewFingerprints(self, jid):
|
def getNewFingerprints(self, jid):
|
||||||
return self.identityKeyStore.getNewFingerprints(jid)
|
return self.identityKeyStore.getNewFingerprints(jid)
|
||||||
|
|
||||||
def loadPreKey(self, preKeyId):
|
def loadPreKey(self, preKeyId):
|
||||||
return self.preKeyStore.loadPreKey(preKeyId)
|
return self.preKeyStore.loadPreKey(preKeyId)
|
||||||
|
|
||||||
def loadPreKeys(self):
|
def loadPreKeys(self):
|
||||||
return self.preKeyStore.loadPendingPreKeys()
|
return self.preKeyStore.loadPendingPreKeys()
|
||||||
|
|
||||||
def storePreKey(self, preKeyId, preKeyRecord):
|
def storePreKey(self, preKeyId, preKeyRecord):
|
||||||
self.preKeyStore.storePreKey(preKeyId, preKeyRecord)
|
self.preKeyStore.storePreKey(preKeyId, preKeyRecord)
|
||||||
|
|
||||||
def containsPreKey(self, preKeyId):
|
def containsPreKey(self, preKeyId):
|
||||||
return self.preKeyStore.containsPreKey(preKeyId)
|
return self.preKeyStore.containsPreKey(preKeyId)
|
||||||
|
|
||||||
def removePreKey(self, preKeyId):
|
def removePreKey(self, preKeyId):
|
||||||
self.preKeyStore.removePreKey(preKeyId)
|
self.preKeyStore.removePreKey(preKeyId)
|
||||||
|
|
||||||
def loadSession(self, recepientId, deviceId):
|
def loadSession(self, recepientId, deviceId):
|
||||||
return self.sessionStore.loadSession(recepientId, deviceId)
|
return self.sessionStore.loadSession(recepientId, deviceId)
|
||||||
|
|
||||||
def getActiveDeviceTuples(self):
|
def getActiveDeviceTuples(self):
|
||||||
return self.sessionStore.getActiveDeviceTuples()
|
return self.sessionStore.getActiveDeviceTuples()
|
||||||
|
|
||||||
def getInactiveSessionsKeys(self, recipientId):
|
def getInactiveSessionsKeys(self, recipientId):
|
||||||
return self.sessionStore.getInactiveSessionsKeys(recipientId)
|
return self.sessionStore.getInactiveSessionsKeys(recipientId)
|
||||||
|
|
||||||
def getSubDeviceSessions(self, recepientId):
|
def getSubDeviceSessions(self, recepientId):
|
||||||
# TODO Reuse this
|
# TODO Reuse this
|
||||||
return self.sessionStore.getSubDeviceSessions(recepientId)
|
return self.sessionStore.getSubDeviceSessions(recepientId)
|
||||||
|
|
||||||
def getJidFromDevice(self, device_id):
|
def getJidFromDevice(self, device_id):
|
||||||
return self.sessionStore.getJidFromDevice(device_id)
|
return self.sessionStore.getJidFromDevice(device_id)
|
||||||
|
|
||||||
def storeSession(self, recepientId, deviceId, sessionRecord):
|
def storeSession(self, recepientId, deviceId, sessionRecord):
|
||||||
self.sessionStore.storeSession(recepientId, deviceId, sessionRecord)
|
self.sessionStore.storeSession(recepientId, deviceId, sessionRecord)
|
||||||
|
|
||||||
def containsSession(self, recepientId, deviceId):
|
def containsSession(self, recepientId, deviceId):
|
||||||
return self.sessionStore.containsSession(recepientId, deviceId)
|
return self.sessionStore.containsSession(recepientId, deviceId)
|
||||||
|
|
||||||
def deleteSession(self, recepientId, deviceId):
|
def deleteSession(self, recepientId, deviceId):
|
||||||
self.sessionStore.deleteSession(recepientId, deviceId)
|
self.sessionStore.deleteSession(recepientId, deviceId)
|
||||||
|
|
||||||
def deleteAllSessions(self, recepientId):
|
def deleteAllSessions(self, recepientId):
|
||||||
self.sessionStore.deleteAllSessions(recepientId)
|
self.sessionStore.deleteAllSessions(recepientId)
|
||||||
|
|
||||||
def getSessionsFromJid(self, recipientId):
|
def getSessionsFromJid(self, recipientId):
|
||||||
return self.sessionStore.getSessionsFromJid(recipientId)
|
return self.sessionStore.getSessionsFromJid(recipientId)
|
||||||
|
|
||||||
def getSessionsFromJids(self, recipientId):
|
def getSessionsFromJids(self, recipientId):
|
||||||
return self.sessionStore.getSessionsFromJids(recipientId)
|
return self.sessionStore.getSessionsFromJids(recipientId)
|
||||||
|
|
||||||
def getAllSessions(self):
|
def getAllSessions(self):
|
||||||
return self.sessionStore.getAllSessions()
|
return self.sessionStore.getAllSessions()
|
||||||
|
|
||||||
def loadSignedPreKey(self, signedPreKeyId):
|
def loadSignedPreKey(self, signedPreKeyId):
|
||||||
return self.signedPreKeyStore.loadSignedPreKey(signedPreKeyId)
|
return self.signedPreKeyStore.loadSignedPreKey(signedPreKeyId)
|
||||||
|
|
||||||
def loadSignedPreKeys(self):
|
def loadSignedPreKeys(self):
|
||||||
return self.signedPreKeyStore.loadSignedPreKeys()
|
return self.signedPreKeyStore.loadSignedPreKeys()
|
||||||
|
|
||||||
def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord):
|
def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord):
|
||||||
self.signedPreKeyStore.storeSignedPreKey(signedPreKeyId,
|
self.signedPreKeyStore.storeSignedPreKey(signedPreKeyId,
|
||||||
signedPreKeyRecord)
|
signedPreKeyRecord)
|
||||||
|
|
||||||
def containsSignedPreKey(self, signedPreKeyId):
|
def containsSignedPreKey(self, signedPreKeyId):
|
||||||
return self.signedPreKeyStore.containsSignedPreKey(signedPreKeyId)
|
return self.signedPreKeyStore.containsSignedPreKey(signedPreKeyId)
|
||||||
|
|
||||||
def removeSignedPreKey(self, signedPreKeyId):
|
def removeSignedPreKey(self, signedPreKeyId):
|
||||||
self.signedPreKeyStore.removeSignedPreKey(signedPreKeyId)
|
self.signedPreKeyStore.removeSignedPreKey(signedPreKeyId)
|
||||||
|
|
||||||
def getNextSignedPreKeyId(self):
|
def getNextSignedPreKeyId(self):
|
||||||
return self.signedPreKeyStore.getNextSignedPreKeyId()
|
return self.signedPreKeyStore.getNextSignedPreKeyId()
|
||||||
|
|
||||||
def getCurrentSignedPreKeyId(self):
|
def getCurrentSignedPreKeyId(self):
|
||||||
return self.signedPreKeyStore.getCurrentSignedPreKeyId()
|
return self.signedPreKeyStore.getCurrentSignedPreKeyId()
|
||||||
|
|
||||||
def getSignedPreKeyTimestamp(self, signedPreKeyId):
|
def getSignedPreKeyTimestamp(self, signedPreKeyId):
|
||||||
return self.signedPreKeyStore.getSignedPreKeyTimestamp(signedPreKeyId)
|
return self.signedPreKeyStore.getSignedPreKeyTimestamp(signedPreKeyId)
|
||||||
|
|
||||||
def removeOldSignedPreKeys(self, timestamp):
|
def removeOldSignedPreKeys(self, timestamp):
|
||||||
self.signedPreKeyStore.removeOldSignedPreKeys(timestamp)
|
self.signedPreKeyStore.removeOldSignedPreKeys(timestamp)
|
||||||
@@ -1,174 +1,174 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of Gajim-OMEMO plugin.
|
# This file is part of Gajim-OMEMO plugin.
|
||||||
#
|
#
|
||||||
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
# 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
|
# 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
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
# later version.
|
# later version.
|
||||||
#
|
#
|
||||||
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
# 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
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
# 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
|
# 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/>.
|
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
from axolotl.ecc.djbec import DjbECPrivateKey, DjbECPublicKey
|
from axolotl.ecc.djbec import DjbECPrivateKey, DjbECPublicKey
|
||||||
from axolotl.identitykey import IdentityKey
|
from axolotl.identitykey import IdentityKey
|
||||||
from axolotl.identitykeypair import IdentityKeyPair
|
from axolotl.identitykeypair import IdentityKeyPair
|
||||||
from axolotl.state.identitykeystore import IdentityKeyStore
|
from axolotl.state.identitykeystore import IdentityKeyStore
|
||||||
|
|
||||||
UNDECIDED = 2
|
UNDECIDED = 2
|
||||||
TRUSTED = 1
|
TRUSTED = 1
|
||||||
UNTRUSTED = 0
|
UNTRUSTED = 0
|
||||||
|
|
||||||
|
|
||||||
class LiteIdentityKeyStore(IdentityKeyStore):
|
class LiteIdentityKeyStore(IdentityKeyStore):
|
||||||
def __init__(self, dbConn):
|
def __init__(self, dbConn):
|
||||||
"""
|
"""
|
||||||
:type dbConn: Connection
|
:type dbConn: Connection
|
||||||
"""
|
"""
|
||||||
self.dbConn = dbConn
|
self.dbConn = dbConn
|
||||||
|
|
||||||
def getIdentityKeyPair(self):
|
def getIdentityKeyPair(self):
|
||||||
q = "SELECT public_key, private_key FROM identities " + \
|
q = "SELECT public_key, private_key FROM identities " + \
|
||||||
"WHERE recipient_id = -1"
|
"WHERE recipient_id = -1"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q)
|
c.execute(q)
|
||||||
result = c.fetchone()
|
result = c.fetchone()
|
||||||
|
|
||||||
publicKey, privateKey = result
|
publicKey, privateKey = result
|
||||||
return IdentityKeyPair(
|
return IdentityKeyPair(
|
||||||
IdentityKey(DjbECPublicKey(publicKey[1:])),
|
IdentityKey(DjbECPublicKey(publicKey[1:])),
|
||||||
DjbECPrivateKey(privateKey))
|
DjbECPrivateKey(privateKey))
|
||||||
|
|
||||||
def getLocalRegistrationId(self):
|
def getLocalRegistrationId(self):
|
||||||
q = "SELECT registration_id FROM identities WHERE recipient_id = -1"
|
q = "SELECT registration_id FROM identities WHERE recipient_id = -1"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q)
|
c.execute(q)
|
||||||
result = c.fetchone()
|
result = c.fetchone()
|
||||||
return result[0] if result else None
|
return result[0] if result else None
|
||||||
|
|
||||||
def storeLocalData(self, registrationId, identityKeyPair):
|
def storeLocalData(self, registrationId, identityKeyPair):
|
||||||
q = "INSERT INTO identities( " + \
|
q = "INSERT INTO identities( " + \
|
||||||
"recipient_id, registration_id, public_key, private_key) " + \
|
"recipient_id, registration_id, public_key, private_key) " + \
|
||||||
"VALUES(-1, ?, ?, ?)"
|
"VALUES(-1, ?, ?, ?)"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q,
|
c.execute(q,
|
||||||
(registrationId,
|
(registrationId,
|
||||||
identityKeyPair.getPublicKey().getPublicKey().serialize(),
|
identityKeyPair.getPublicKey().getPublicKey().serialize(),
|
||||||
identityKeyPair.getPrivateKey().serialize()))
|
identityKeyPair.getPrivateKey().serialize()))
|
||||||
|
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def saveIdentity(self, recipientId, identityKey):
|
def saveIdentity(self, recipientId, identityKey):
|
||||||
q = "INSERT INTO identities (recipient_id, public_key, trust) " \
|
q = "INSERT INTO identities (recipient_id, public_key, trust) " \
|
||||||
"VALUES(?, ?, ?)"
|
"VALUES(?, ?, ?)"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
|
|
||||||
if not self.getIdentity(recipientId, identityKey):
|
if not self.getIdentity(recipientId, identityKey):
|
||||||
c.execute(q, (recipientId,
|
c.execute(q, (recipientId,
|
||||||
identityKey.getPublicKey().serialize(),
|
identityKey.getPublicKey().serialize(),
|
||||||
UNDECIDED))
|
UNDECIDED))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def getIdentity(self, recipientId, identityKey):
|
def getIdentity(self, recipientId, identityKey):
|
||||||
q = "SELECT * FROM identities WHERE recipient_id = ? " \
|
q = "SELECT * FROM identities WHERE recipient_id = ? " \
|
||||||
"AND public_key = ?"
|
"AND public_key = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
|
|
||||||
c.execute(q, (recipientId, identityKey.getPublicKey().serialize()))
|
c.execute(q, (recipientId, identityKey.getPublicKey().serialize()))
|
||||||
result = c.fetchone()
|
result = c.fetchone()
|
||||||
|
|
||||||
return result is not None
|
return result is not None
|
||||||
|
|
||||||
def deleteIdentity(self, recipientId, identityKey):
|
def deleteIdentity(self, recipientId, identityKey):
|
||||||
q = "DELETE FROM identities WHERE recipient_id = ? AND public_key = ?"
|
q = "DELETE FROM identities WHERE recipient_id = ? AND public_key = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (recipientId,
|
c.execute(q, (recipientId,
|
||||||
identityKey.getPublicKey().serialize()))
|
identityKey.getPublicKey().serialize()))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def isTrustedIdentity(self, recipientId, identityKey):
|
def isTrustedIdentity(self, recipientId, identityKey):
|
||||||
q = "SELECT trust FROM identities WHERE recipient_id = ? " \
|
q = "SELECT trust FROM identities WHERE recipient_id = ? " \
|
||||||
"AND public_key = ?"
|
"AND public_key = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
|
|
||||||
c.execute(q, (recipientId, identityKey.getPublicKey().serialize()))
|
c.execute(q, (recipientId, identityKey.getPublicKey().serialize()))
|
||||||
result = c.fetchone()
|
result = c.fetchone()
|
||||||
|
|
||||||
states = [UNTRUSTED, TRUSTED, UNDECIDED]
|
states = [UNTRUSTED, TRUSTED, UNDECIDED]
|
||||||
|
|
||||||
if result and result[0] in states:
|
if result and result[0] in states:
|
||||||
return result[0]
|
return result[0]
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def getAllFingerprints(self):
|
def getAllFingerprints(self):
|
||||||
q = "SELECT _id, recipient_id, public_key, trust FROM identities " \
|
q = "SELECT _id, recipient_id, public_key, trust FROM identities " \
|
||||||
"WHERE recipient_id != -1 ORDER BY recipient_id ASC"
|
"WHERE recipient_id != -1 ORDER BY recipient_id ASC"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for row in c.execute(q):
|
for row in c.execute(q):
|
||||||
result.append((row[0], row[1], row[2], row[3]))
|
result.append((row[0], row[1], row[2], row[3]))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def getFingerprints(self, jid):
|
def getFingerprints(self, jid):
|
||||||
q = "SELECT _id, recipient_id, public_key, trust FROM identities " \
|
q = "SELECT _id, recipient_id, public_key, trust FROM identities " \
|
||||||
"WHERE recipient_id =? ORDER BY trust ASC"
|
"WHERE recipient_id =? ORDER BY trust ASC"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
c.execute(q, (jid,))
|
c.execute(q, (jid,))
|
||||||
rows = c.fetchall()
|
rows = c.fetchall()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
result.append((row[0], row[1], row[2], row[3]))
|
result.append((row[0], row[1], row[2], row[3]))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def getTrustedFingerprints(self, jid):
|
def getTrustedFingerprints(self, jid):
|
||||||
q = "SELECT public_key FROM identities WHERE recipient_id = ? AND trust = ?"
|
q = "SELECT public_key FROM identities WHERE recipient_id = ? AND trust = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
c.execute(q, (jid, TRUSTED))
|
c.execute(q, (jid, TRUSTED))
|
||||||
rows = c.fetchall()
|
rows = c.fetchall()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
result.append(row[0])
|
result.append(row[0])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def getUndecidedFingerprints(self, jid):
|
def getUndecidedFingerprints(self, jid):
|
||||||
q = "SELECT trust FROM identities WHERE recipient_id = ? AND trust = ?"
|
q = "SELECT trust FROM identities WHERE recipient_id = ? AND trust = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
c.execute(q, (jid, UNDECIDED))
|
c.execute(q, (jid, UNDECIDED))
|
||||||
result = c.fetchall()
|
result = c.fetchall()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def getNewFingerprints(self, jid):
|
def getNewFingerprints(self, jid):
|
||||||
q = "SELECT _id FROM identities WHERE shown = 0 AND " \
|
q = "SELECT _id FROM identities WHERE shown = 0 AND " \
|
||||||
"recipient_id = ?"
|
"recipient_id = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
result = []
|
result = []
|
||||||
for row in c.execute(q, (jid,)):
|
for row in c.execute(q, (jid,)):
|
||||||
result.append(row[0])
|
result.append(row[0])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def setShownFingerprints(self, fingerprints):
|
def setShownFingerprints(self, fingerprints):
|
||||||
q = "UPDATE identities SET shown = 1 WHERE _id IN ({})" \
|
q = "UPDATE identities SET shown = 1 WHERE _id IN ({})" \
|
||||||
.format(', '.join(['?'] * len(fingerprints)))
|
.format(', '.join(['?'] * len(fingerprints)))
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, fingerprints)
|
c.execute(q, fingerprints)
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def setTrust(self, identityKey, trust):
|
def setTrust(self, identityKey, trust):
|
||||||
q = "UPDATE identities SET trust = ? WHERE public_key = ?"
|
q = "UPDATE identities SET trust = ? WHERE public_key = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (trust, identityKey.getPublicKey().serialize()))
|
c.execute(q, (trust, identityKey.getPublicKey().serialize()))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
@@ -1,87 +1,87 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of Gajim-OMEMO plugin.
|
# This file is part of Gajim-OMEMO plugin.
|
||||||
#
|
#
|
||||||
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
# 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
|
# 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
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
# later version.
|
# later version.
|
||||||
#
|
#
|
||||||
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
# 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
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
# 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
|
# 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/>.
|
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
from axolotl.state.prekeyrecord import PreKeyRecord
|
from axolotl.state.prekeyrecord import PreKeyRecord
|
||||||
from axolotl.state.prekeystore import PreKeyStore
|
from axolotl.state.prekeystore import PreKeyStore
|
||||||
from axolotl.util.keyhelper import KeyHelper
|
from axolotl.util.keyhelper import KeyHelper
|
||||||
|
|
||||||
|
|
||||||
class LitePreKeyStore(PreKeyStore):
|
class LitePreKeyStore(PreKeyStore):
|
||||||
def __init__(self, dbConn):
|
def __init__(self, dbConn):
|
||||||
"""
|
"""
|
||||||
:type dbConn: Connection
|
:type dbConn: Connection
|
||||||
"""
|
"""
|
||||||
self.dbConn = dbConn
|
self.dbConn = dbConn
|
||||||
|
|
||||||
def loadPreKey(self, preKeyId):
|
def loadPreKey(self, preKeyId):
|
||||||
q = "SELECT record FROM prekeys WHERE prekey_id = ?"
|
q = "SELECT record FROM prekeys WHERE prekey_id = ?"
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, (preKeyId, ))
|
cursor.execute(q, (preKeyId, ))
|
||||||
|
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
if not result:
|
if not result:
|
||||||
raise Exception("No such prekeyRecord!")
|
raise Exception("No such prekeyRecord!")
|
||||||
|
|
||||||
return PreKeyRecord(serialized=result[0])
|
return PreKeyRecord(serialized=result[0])
|
||||||
|
|
||||||
def loadPendingPreKeys(self):
|
def loadPendingPreKeys(self):
|
||||||
q = "SELECT record FROM prekeys"
|
q = "SELECT record FROM prekeys"
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q)
|
cursor.execute(q)
|
||||||
result = cursor.fetchall()
|
result = cursor.fetchall()
|
||||||
|
|
||||||
return [PreKeyRecord(serialized=r[0]) for r in result]
|
return [PreKeyRecord(serialized=r[0]) for r in result]
|
||||||
|
|
||||||
def storePreKey(self, preKeyId, preKeyRecord):
|
def storePreKey(self, preKeyId, preKeyRecord):
|
||||||
q = "INSERT INTO prekeys (prekey_id, record) VALUES(?,?)"
|
q = "INSERT INTO prekeys (prekey_id, record) VALUES(?,?)"
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, (preKeyId, preKeyRecord.serialize()))
|
cursor.execute(q, (preKeyId, preKeyRecord.serialize()))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def containsPreKey(self, preKeyId):
|
def containsPreKey(self, preKeyId):
|
||||||
q = "SELECT record FROM prekeys WHERE prekey_id = ?"
|
q = "SELECT record FROM prekeys WHERE prekey_id = ?"
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, (preKeyId, ))
|
cursor.execute(q, (preKeyId, ))
|
||||||
return cursor.fetchone() is not None
|
return cursor.fetchone() is not None
|
||||||
|
|
||||||
def removePreKey(self, preKeyId):
|
def removePreKey(self, preKeyId):
|
||||||
q = "DELETE FROM prekeys WHERE prekey_id = ?"
|
q = "DELETE FROM prekeys WHERE prekey_id = ?"
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, (preKeyId, ))
|
cursor.execute(q, (preKeyId, ))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def getCurrentPreKeyId(self):
|
def getCurrentPreKeyId(self):
|
||||||
q = "SELECT MAX(prekey_id) FROM prekeys"
|
q = "SELECT MAX(prekey_id) FROM prekeys"
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q)
|
cursor.execute(q)
|
||||||
return cursor.fetchone()[0]
|
return cursor.fetchone()[0]
|
||||||
|
|
||||||
def getPreKeyCount(self):
|
def getPreKeyCount(self):
|
||||||
q = "SELECT COUNT(prekey_id) FROM prekeys"
|
q = "SELECT COUNT(prekey_id) FROM prekeys"
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q)
|
cursor.execute(q)
|
||||||
return cursor.fetchone()[0]
|
return cursor.fetchone()[0]
|
||||||
|
|
||||||
def generateNewPreKeys(self, count):
|
def generateNewPreKeys(self, count):
|
||||||
startId = self.getCurrentPreKeyId() + 1
|
startId = self.getCurrentPreKeyId() + 1
|
||||||
preKeys = KeyHelper.generatePreKeys(startId, count)
|
preKeys = KeyHelper.generatePreKeys(startId, count)
|
||||||
|
|
||||||
for preKey in preKeys:
|
for preKey in preKeys:
|
||||||
self.storePreKey(preKey.getId(), preKey)
|
self.storePreKey(preKey.getId(), preKey)
|
||||||
@@ -1,143 +1,143 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of Gajim-OMEMO plugin.
|
# This file is part of Gajim-OMEMO plugin.
|
||||||
#
|
#
|
||||||
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
# 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
|
# 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
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
# later version.
|
# later version.
|
||||||
#
|
#
|
||||||
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
# 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
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
# 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
|
# 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/>.
|
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
from axolotl.state.sessionrecord import SessionRecord
|
from axolotl.state.sessionrecord import SessionRecord
|
||||||
from axolotl.state.sessionstore import SessionStore
|
from axolotl.state.sessionstore import SessionStore
|
||||||
|
|
||||||
|
|
||||||
class LiteSessionStore(SessionStore):
|
class LiteSessionStore(SessionStore):
|
||||||
def __init__(self, dbConn):
|
def __init__(self, dbConn):
|
||||||
"""
|
"""
|
||||||
:type dbConn: Connection
|
:type dbConn: Connection
|
||||||
"""
|
"""
|
||||||
self.dbConn = dbConn
|
self.dbConn = dbConn
|
||||||
|
|
||||||
def loadSession(self, recipientId, deviceId):
|
def loadSession(self, recipientId, deviceId):
|
||||||
q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?"
|
q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (recipientId, deviceId))
|
c.execute(q, (recipientId, deviceId))
|
||||||
result = c.fetchone()
|
result = c.fetchone()
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return SessionRecord(serialized=result[0])
|
return SessionRecord(serialized=result[0])
|
||||||
else:
|
else:
|
||||||
return SessionRecord()
|
return SessionRecord()
|
||||||
|
|
||||||
def getSubDeviceSessions(self, recipientId):
|
def getSubDeviceSessions(self, recipientId):
|
||||||
q = "SELECT device_id from sessions WHERE recipient_id = ?"
|
q = "SELECT device_id from sessions WHERE recipient_id = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (recipientId, ))
|
c.execute(q, (recipientId, ))
|
||||||
result = c.fetchall()
|
result = c.fetchall()
|
||||||
|
|
||||||
deviceIds = [r[0] for r in result]
|
deviceIds = [r[0] for r in result]
|
||||||
return deviceIds
|
return deviceIds
|
||||||
|
|
||||||
def getJidFromDevice(self, device_id):
|
def getJidFromDevice(self, device_id):
|
||||||
q = "SELECT recipient_id from sessions WHERE device_id = ?"
|
q = "SELECT recipient_id from sessions WHERE device_id = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (device_id, ))
|
c.execute(q, (device_id, ))
|
||||||
result = c.fetchone()
|
result = c.fetchone()
|
||||||
|
|
||||||
return result[0].decode('utf-8') if result else None
|
return result[0].decode('utf-8') if result else None
|
||||||
|
|
||||||
def getActiveDeviceTuples(self):
|
def getActiveDeviceTuples(self):
|
||||||
q = "SELECT recipient_id, device_id FROM sessions WHERE active = 1"
|
q = "SELECT recipient_id, device_id FROM sessions WHERE active = 1"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
result = []
|
result = []
|
||||||
for row in c.execute(q):
|
for row in c.execute(q):
|
||||||
result.append((row[0].decode('utf-8'), row[1]))
|
result.append((row[0].decode('utf-8'), row[1]))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def storeSession(self, recipientId, deviceId, sessionRecord):
|
def storeSession(self, recipientId, deviceId, sessionRecord):
|
||||||
self.deleteSession(recipientId, deviceId)
|
self.deleteSession(recipientId, deviceId)
|
||||||
|
|
||||||
q = "INSERT INTO sessions(recipient_id, device_id, record) VALUES(?,?,?)"
|
q = "INSERT INTO sessions(recipient_id, device_id, record) VALUES(?,?,?)"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (recipientId, deviceId, sessionRecord.serialize()))
|
c.execute(q, (recipientId, deviceId, sessionRecord.serialize()))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def containsSession(self, recipientId, deviceId):
|
def containsSession(self, recipientId, deviceId):
|
||||||
q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?"
|
q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute(q, (recipientId, deviceId))
|
c.execute(q, (recipientId, deviceId))
|
||||||
result = c.fetchone()
|
result = c.fetchone()
|
||||||
|
|
||||||
return result is not None
|
return result is not None
|
||||||
|
|
||||||
def deleteSession(self, recipientId, deviceId):
|
def deleteSession(self, recipientId, deviceId):
|
||||||
q = "DELETE FROM sessions WHERE recipient_id = ? AND device_id = ?"
|
q = "DELETE FROM sessions WHERE recipient_id = ? AND device_id = ?"
|
||||||
self.dbConn.cursor().execute(q, (recipientId, deviceId))
|
self.dbConn.cursor().execute(q, (recipientId, deviceId))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def deleteAllSessions(self, recipientId):
|
def deleteAllSessions(self, recipientId):
|
||||||
q = "DELETE FROM sessions WHERE recipient_id = ?"
|
q = "DELETE FROM sessions WHERE recipient_id = ?"
|
||||||
self.dbConn.cursor().execute(q, (recipientId, ))
|
self.dbConn.cursor().execute(q, (recipientId, ))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def getAllSessions(self):
|
def getAllSessions(self):
|
||||||
q = "SELECT _id, recipient_id, device_id, record, active from sessions"
|
q = "SELECT _id, recipient_id, device_id, record, active from sessions"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
result = []
|
result = []
|
||||||
for row in c.execute(q):
|
for row in c.execute(q):
|
||||||
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def getSessionsFromJid(self, recipientId):
|
def getSessionsFromJid(self, recipientId):
|
||||||
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
|
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
|
||||||
" WHERE recipient_id = ?"
|
" WHERE recipient_id = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
result = []
|
result = []
|
||||||
for row in c.execute(q, (recipientId,)):
|
for row in c.execute(q, (recipientId,)):
|
||||||
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def getSessionsFromJids(self, recipientId):
|
def getSessionsFromJids(self, recipientId):
|
||||||
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
|
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
|
||||||
" WHERE recipient_id IN ({})" \
|
" WHERE recipient_id IN ({})" \
|
||||||
.format(', '.join(['?'] * len(recipientId)))
|
.format(', '.join(['?'] * len(recipientId)))
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
result = []
|
result = []
|
||||||
for row in c.execute(q, recipientId):
|
for row in c.execute(q, recipientId):
|
||||||
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def setActiveState(self, deviceList, jid):
|
def setActiveState(self, deviceList, jid):
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
|
|
||||||
q = "UPDATE sessions SET active = {} " \
|
q = "UPDATE sessions SET active = {} " \
|
||||||
"WHERE recipient_id = '{}' AND device_id IN ({})" \
|
"WHERE recipient_id = '{}' AND device_id IN ({})" \
|
||||||
.format(1, jid, ', '.join(['?'] * len(deviceList)))
|
.format(1, jid, ', '.join(['?'] * len(deviceList)))
|
||||||
c.execute(q, deviceList)
|
c.execute(q, deviceList)
|
||||||
|
|
||||||
q = "UPDATE sessions SET active = {} " \
|
q = "UPDATE sessions SET active = {} " \
|
||||||
"WHERE recipient_id = '{}' AND device_id NOT IN ({})" \
|
"WHERE recipient_id = '{}' AND device_id NOT IN ({})" \
|
||||||
.format(0, jid, ', '.join(['?'] * len(deviceList)))
|
.format(0, jid, ', '.join(['?'] * len(deviceList)))
|
||||||
c.execute(q, deviceList)
|
c.execute(q, deviceList)
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def getInactiveSessionsKeys(self, recipientId):
|
def getInactiveSessionsKeys(self, recipientId):
|
||||||
q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?"
|
q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?"
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
result = []
|
result = []
|
||||||
for row in c.execute(q, (recipientId,)):
|
for row in c.execute(q, (recipientId,)):
|
||||||
public_key = (SessionRecord(serialized=row[0]).
|
public_key = (SessionRecord(serialized=row[0]).
|
||||||
getSessionState().getRemoteIdentityKey().
|
getSessionState().getRemoteIdentityKey().
|
||||||
getPublicKey())
|
getPublicKey())
|
||||||
result.append(public_key.serialize())
|
result.append(public_key.serialize())
|
||||||
return result
|
return result
|
||||||
@@ -1,113 +1,113 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of Gajim-OMEMO plugin.
|
# This file is part of Gajim-OMEMO plugin.
|
||||||
#
|
#
|
||||||
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
# 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
|
# 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
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
# later version.
|
# later version.
|
||||||
#
|
#
|
||||||
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
# 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
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
# 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
|
# 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/>.
|
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
from axolotl.invalidkeyidexception import InvalidKeyIdException
|
from axolotl.invalidkeyidexception import InvalidKeyIdException
|
||||||
from axolotl.state.signedprekeyrecord import SignedPreKeyRecord
|
from axolotl.state.signedprekeyrecord import SignedPreKeyRecord
|
||||||
from axolotl.state.signedprekeystore import SignedPreKeyStore
|
from axolotl.state.signedprekeystore import SignedPreKeyStore
|
||||||
from axolotl.util.medium import Medium
|
from axolotl.util.medium import Medium
|
||||||
|
|
||||||
|
|
||||||
class LiteSignedPreKeyStore(SignedPreKeyStore):
|
class LiteSignedPreKeyStore(SignedPreKeyStore):
|
||||||
def __init__(self, dbConn):
|
def __init__(self, dbConn):
|
||||||
"""
|
"""
|
||||||
:type dbConn: Connection
|
:type dbConn: Connection
|
||||||
"""
|
"""
|
||||||
self.dbConn = dbConn
|
self.dbConn = dbConn
|
||||||
|
|
||||||
def loadSignedPreKey(self, signedPreKeyId):
|
def loadSignedPreKey(self, signedPreKeyId):
|
||||||
q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?"
|
q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?"
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, (signedPreKeyId, ))
|
cursor.execute(q, (signedPreKeyId, ))
|
||||||
|
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
if not result:
|
if not result:
|
||||||
raise InvalidKeyIdException("No such signedprekeyrecord! %s " %
|
raise InvalidKeyIdException("No such signedprekeyrecord! %s " %
|
||||||
signedPreKeyId)
|
signedPreKeyId)
|
||||||
|
|
||||||
return SignedPreKeyRecord(serialized=result[0])
|
return SignedPreKeyRecord(serialized=result[0])
|
||||||
|
|
||||||
def loadSignedPreKeys(self):
|
def loadSignedPreKeys(self):
|
||||||
q = "SELECT record FROM signed_prekeys"
|
q = "SELECT record FROM signed_prekeys"
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, )
|
cursor.execute(q, )
|
||||||
result = cursor.fetchall()
|
result = cursor.fetchall()
|
||||||
results = []
|
results = []
|
||||||
for row in result:
|
for row in result:
|
||||||
results.append(SignedPreKeyRecord(serialized=row[0]))
|
results.append(SignedPreKeyRecord(serialized=row[0]))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord):
|
def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord):
|
||||||
q = "INSERT INTO signed_prekeys (prekey_id, record) VALUES(?,?)"
|
q = "INSERT INTO signed_prekeys (prekey_id, record) VALUES(?,?)"
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, (signedPreKeyId, signedPreKeyRecord.serialize()))
|
cursor.execute(q, (signedPreKeyId, signedPreKeyRecord.serialize()))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def containsSignedPreKey(self, signedPreKeyId):
|
def containsSignedPreKey(self, signedPreKeyId):
|
||||||
q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?"
|
q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?"
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, (signedPreKeyId, ))
|
cursor.execute(q, (signedPreKeyId, ))
|
||||||
return cursor.fetchone() is not None
|
return cursor.fetchone() is not None
|
||||||
|
|
||||||
def removeSignedPreKey(self, signedPreKeyId):
|
def removeSignedPreKey(self, signedPreKeyId):
|
||||||
q = "DELETE FROM signed_prekeys WHERE prekey_id = ?"
|
q = "DELETE FROM signed_prekeys WHERE prekey_id = ?"
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, (signedPreKeyId, ))
|
cursor.execute(q, (signedPreKeyId, ))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def getNextSignedPreKeyId(self):
|
def getNextSignedPreKeyId(self):
|
||||||
result = self.getCurrentSignedPreKeyId()
|
result = self.getCurrentSignedPreKeyId()
|
||||||
if not result:
|
if not result:
|
||||||
return 1 # StartId if no SignedPreKeys exist
|
return 1 # StartId if no SignedPreKeys exist
|
||||||
else:
|
else:
|
||||||
return (result % (Medium.MAX_VALUE - 1)) + 1
|
return (result % (Medium.MAX_VALUE - 1)) + 1
|
||||||
|
|
||||||
def getCurrentSignedPreKeyId(self):
|
def getCurrentSignedPreKeyId(self):
|
||||||
q = "SELECT MAX(prekey_id) FROM signed_prekeys"
|
q = "SELECT MAX(prekey_id) FROM signed_prekeys"
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q)
|
cursor.execute(q)
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
if not result:
|
if not result:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
def getSignedPreKeyTimestamp(self, signedPreKeyId):
|
def getSignedPreKeyTimestamp(self, signedPreKeyId):
|
||||||
q = "SELECT strftime('%s', timestamp) FROM " \
|
q = "SELECT strftime('%s', timestamp) FROM " \
|
||||||
"signed_prekeys WHERE prekey_id = ?"
|
"signed_prekeys WHERE prekey_id = ?"
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, (signedPreKeyId, ))
|
cursor.execute(q, (signedPreKeyId, ))
|
||||||
|
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
if not result:
|
if not result:
|
||||||
raise InvalidKeyIdException("No such signedprekeyrecord! %s " %
|
raise InvalidKeyIdException("No such signedprekeyrecord! %s " %
|
||||||
signedPreKeyId)
|
signedPreKeyId)
|
||||||
|
|
||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
def removeOldSignedPreKeys(self, timestamp):
|
def removeOldSignedPreKeys(self, timestamp):
|
||||||
q = "DELETE FROM signed_prekeys " \
|
q = "DELETE FROM signed_prekeys " \
|
||||||
"WHERE timestamp < datetime(?, 'unixepoch')"
|
"WHERE timestamp < datetime(?, 'unixepoch')"
|
||||||
cursor = self.dbConn.cursor()
|
cursor = self.dbConn.cursor()
|
||||||
cursor.execute(q, (timestamp, ))
|
cursor.execute(q, (timestamp, ))
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
@@ -1,154 +1,154 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of Gajim-OMEMO plugin.
|
# This file is part of Gajim-OMEMO plugin.
|
||||||
#
|
#
|
||||||
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
# 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
|
# 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
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
# later version.
|
# later version.
|
||||||
#
|
#
|
||||||
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
# 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
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
# 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
|
# 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/>.
|
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from .db_helpers import user_version
|
from .db_helpers import user_version
|
||||||
|
|
||||||
|
|
||||||
class SQLDatabase():
|
class SQLDatabase():
|
||||||
""" SQL Database """
|
""" SQL Database """
|
||||||
|
|
||||||
def __init__(self, dbConn):
|
def __init__(self, dbConn):
|
||||||
"""
|
"""
|
||||||
:type dbConn: Connection
|
:type dbConn: Connection
|
||||||
"""
|
"""
|
||||||
self.dbConn = dbConn
|
self.dbConn = dbConn
|
||||||
self.createDb()
|
self.createDb()
|
||||||
self.migrateDb()
|
self.migrateDb()
|
||||||
c = self.dbConn.cursor()
|
c = self.dbConn.cursor()
|
||||||
c.execute("PRAGMA synchronous=NORMAL;")
|
c.execute("PRAGMA synchronous=NORMAL;")
|
||||||
c.execute("PRAGMA journal_mode;")
|
c.execute("PRAGMA journal_mode;")
|
||||||
mode = c.fetchone()[0]
|
mode = c.fetchone()[0]
|
||||||
# WAL is a persistent DB mode, don't override it if user has set it
|
# WAL is a persistent DB mode, don't override it if user has set it
|
||||||
if mode != 'wal':
|
if mode != 'wal':
|
||||||
c.execute("PRAGMA journal_mode=MEMORY;")
|
c.execute("PRAGMA journal_mode=MEMORY;")
|
||||||
self.dbConn.commit()
|
self.dbConn.commit()
|
||||||
|
|
||||||
def createDb(self):
|
def createDb(self):
|
||||||
if user_version(self.dbConn) == 0:
|
if user_version(self.dbConn) == 0:
|
||||||
|
|
||||||
# Creates
|
# Creates
|
||||||
# IdentityKeyStore
|
# IdentityKeyStore
|
||||||
# PreKeyStore
|
# PreKeyStore
|
||||||
# SignedPreKeyStore
|
# SignedPreKeyStore
|
||||||
# SessionStore
|
# SessionStore
|
||||||
# EncryptionStore
|
# EncryptionStore
|
||||||
|
|
||||||
create_tables = '''
|
create_tables = '''
|
||||||
CREATE TABLE IF NOT EXISTS identities (
|
CREATE TABLE IF NOT EXISTS identities (
|
||||||
_id INTEGER PRIMARY KEY AUTOINCREMENT, recipient_id TEXT,
|
_id INTEGER PRIMARY KEY AUTOINCREMENT, recipient_id TEXT,
|
||||||
registration_id INTEGER, public_key BLOB, private_key BLOB,
|
registration_id INTEGER, public_key BLOB, private_key BLOB,
|
||||||
next_prekey_id INTEGER, timestamp INTEGER, trust INTEGER,
|
next_prekey_id INTEGER, timestamp INTEGER, trust INTEGER,
|
||||||
shown INTEGER DEFAULT 0);
|
shown INTEGER DEFAULT 0);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS
|
CREATE UNIQUE INDEX IF NOT EXISTS
|
||||||
public_key_index ON identities (public_key, recipient_id);
|
public_key_index ON identities (public_key, recipient_id);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS prekeys(
|
CREATE TABLE IF NOT EXISTS prekeys(
|
||||||
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
prekey_id INTEGER UNIQUE, sent_to_server BOOLEAN,
|
prekey_id INTEGER UNIQUE, sent_to_server BOOLEAN,
|
||||||
record BLOB);
|
record BLOB);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS signed_prekeys (
|
CREATE TABLE IF NOT EXISTS signed_prekeys (
|
||||||
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
prekey_id INTEGER UNIQUE,
|
prekey_id INTEGER UNIQUE,
|
||||||
timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB);
|
timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
recipient_id TEXT, device_id INTEGER,
|
recipient_id TEXT, device_id INTEGER,
|
||||||
record BLOB, timestamp INTEGER, active INTEGER DEFAULT 1,
|
record BLOB, timestamp INTEGER, active INTEGER DEFAULT 1,
|
||||||
UNIQUE(recipient_id, device_id));
|
UNIQUE(recipient_id, device_id));
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS encryption_state (
|
CREATE TABLE IF NOT EXISTS encryption_state (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
jid TEXT UNIQUE,
|
jid TEXT UNIQUE,
|
||||||
encryption INTEGER
|
encryption INTEGER
|
||||||
);
|
);
|
||||||
'''
|
'''
|
||||||
|
|
||||||
create_db_sql = """
|
create_db_sql = """
|
||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
%s
|
%s
|
||||||
PRAGMA user_version=5;
|
PRAGMA user_version=5;
|
||||||
END TRANSACTION;
|
END TRANSACTION;
|
||||||
""" % (create_tables)
|
""" % (create_tables)
|
||||||
self.dbConn.executescript(create_db_sql)
|
self.dbConn.executescript(create_db_sql)
|
||||||
|
|
||||||
def migrateDb(self):
|
def migrateDb(self):
|
||||||
""" Migrates the DB
|
""" Migrates the DB
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Find all double entries and delete them
|
# Find all double entries and delete them
|
||||||
if user_version(self.dbConn) < 2:
|
if user_version(self.dbConn) < 2:
|
||||||
delete_dupes = """ DELETE FROM identities WHERE _id not in (
|
delete_dupes = """ DELETE FROM identities WHERE _id not in (
|
||||||
SELECT MIN(_id)
|
SELECT MIN(_id)
|
||||||
FROM identities
|
FROM identities
|
||||||
GROUP BY
|
GROUP BY
|
||||||
recipient_id, public_key
|
recipient_id, public_key
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.dbConn.executescript(""" BEGIN TRANSACTION;
|
self.dbConn.executescript(""" BEGIN TRANSACTION;
|
||||||
%s
|
%s
|
||||||
PRAGMA user_version=2;
|
PRAGMA user_version=2;
|
||||||
END TRANSACTION;
|
END TRANSACTION;
|
||||||
""" % (delete_dupes))
|
""" % (delete_dupes))
|
||||||
|
|
||||||
if user_version(self.dbConn) < 3:
|
if user_version(self.dbConn) < 3:
|
||||||
# Create a UNIQUE INDEX so every public key/recipient_id tuple
|
# Create a UNIQUE INDEX so every public key/recipient_id tuple
|
||||||
# can only be once in the db
|
# can only be once in the db
|
||||||
add_index = """ CREATE UNIQUE INDEX IF NOT EXISTS
|
add_index = """ CREATE UNIQUE INDEX IF NOT EXISTS
|
||||||
public_key_index
|
public_key_index
|
||||||
ON identities (public_key, recipient_id);
|
ON identities (public_key, recipient_id);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.dbConn.executescript(""" BEGIN TRANSACTION;
|
self.dbConn.executescript(""" BEGIN TRANSACTION;
|
||||||
%s
|
%s
|
||||||
PRAGMA user_version=3;
|
PRAGMA user_version=3;
|
||||||
END TRANSACTION;
|
END TRANSACTION;
|
||||||
""" % (add_index))
|
""" % (add_index))
|
||||||
|
|
||||||
if user_version(self.dbConn) < 4:
|
if user_version(self.dbConn) < 4:
|
||||||
# Adds column "active" to the sessions table
|
# Adds column "active" to the sessions table
|
||||||
add_active = """ ALTER TABLE sessions
|
add_active = """ ALTER TABLE sessions
|
||||||
ADD COLUMN active INTEGER DEFAULT 1;
|
ADD COLUMN active INTEGER DEFAULT 1;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.dbConn.executescript(""" BEGIN TRANSACTION;
|
self.dbConn.executescript(""" BEGIN TRANSACTION;
|
||||||
%s
|
%s
|
||||||
PRAGMA user_version=4;
|
PRAGMA user_version=4;
|
||||||
END TRANSACTION;
|
END TRANSACTION;
|
||||||
""" % (add_active))
|
""" % (add_active))
|
||||||
|
|
||||||
if user_version(self.dbConn) < 5:
|
if user_version(self.dbConn) < 5:
|
||||||
# Adds DEFAULT Timestamp
|
# Adds DEFAULT Timestamp
|
||||||
add_timestamp = """
|
add_timestamp = """
|
||||||
DROP TABLE signed_prekeys;
|
DROP TABLE signed_prekeys;
|
||||||
CREATE TABLE IF NOT EXISTS signed_prekeys (
|
CREATE TABLE IF NOT EXISTS signed_prekeys (
|
||||||
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
prekey_id INTEGER UNIQUE,
|
prekey_id INTEGER UNIQUE,
|
||||||
timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB);
|
timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB);
|
||||||
ALTER TABLE identities ADD COLUMN shown INTEGER DEFAULT 0;
|
ALTER TABLE identities ADD COLUMN shown INTEGER DEFAULT 0;
|
||||||
UPDATE identities SET shown = 1;
|
UPDATE identities SET shown = 1;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.dbConn.executescript(""" BEGIN TRANSACTION;
|
self.dbConn.executescript(""" BEGIN TRANSACTION;
|
||||||
%s
|
%s
|
||||||
PRAGMA user_version=5;
|
PRAGMA user_version=5;
|
||||||
END TRANSACTION;
|
END TRANSACTION;
|
||||||
""" % (add_timestamp))
|
""" % (add_timestamp))
|
||||||
@@ -19,16 +19,14 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import os
|
|
||||||
from base64 import b64encode
|
from nbxmpp.structs import OMEMOBundle
|
||||||
|
from nbxmpp.structs import OMEMOMessage
|
||||||
|
|
||||||
from axolotl.ecc.djbec import DjbECPublicKey
|
from axolotl.ecc.djbec import DjbECPublicKey
|
||||||
from axolotl.identitykey import IdentityKey
|
from axolotl.identitykey import IdentityKey
|
||||||
from axolotl.duplicatemessagexception import DuplicateMessageException
|
|
||||||
from axolotl.invalidmessageexception import InvalidMessageException
|
|
||||||
from axolotl.invalidversionexception import InvalidVersionException
|
|
||||||
from axolotl.untrustedidentityexception import UntrustedIdentityException
|
from axolotl.untrustedidentityexception import UntrustedIdentityException
|
||||||
from axolotl.nosessionexception import NoSessionException
|
|
||||||
from axolotl.protocol.prekeywhispermessage import PreKeyWhisperMessage
|
from axolotl.protocol.prekeywhispermessage import PreKeyWhisperMessage
|
||||||
from axolotl.protocol.whispermessage import WhisperMessage
|
from axolotl.protocol.whispermessage import WhisperMessage
|
||||||
from axolotl.sessionbuilder import SessionBuilder
|
from axolotl.sessionbuilder import SessionBuilder
|
||||||
@@ -36,13 +34,12 @@ from axolotl.sessioncipher import SessionCipher
|
|||||||
from axolotl.state.prekeybundle import PreKeyBundle
|
from axolotl.state.prekeybundle import PreKeyBundle
|
||||||
from axolotl.util.keyhelper import KeyHelper
|
from axolotl.util.keyhelper import KeyHelper
|
||||||
|
|
||||||
from .aes_gcm import NoValidSessions, decrypt, encrypt
|
from omemo.backend.aes import aes_decrypt, aes_encrypt
|
||||||
from .liteaxolotlstore import (LiteAxolotlStore, DEFAULT_PREKEY_AMOUNT,
|
from .liteaxolotlstore import (LiteAxolotlStore, DEFAULT_PREKEY_AMOUNT,
|
||||||
MIN_PREKEY_AMOUNT, SPK_CYCLE_TIME,
|
MIN_PREKEY_AMOUNT, SPK_CYCLE_TIME,
|
||||||
SPK_ARCHIVE_TIME)
|
SPK_ARCHIVE_TIME)
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||||
logAxolotl = logging.getLogger('axolotl')
|
|
||||||
|
|
||||||
|
|
||||||
UNTRUSTED = 0
|
UNTRUSTED = 0
|
||||||
@@ -78,23 +75,24 @@ class OmemoState:
|
|||||||
str(self.store.preKeyStore.getPreKeyCount()) +
|
str(self.store.preKeyStore.getPreKeyCount()) +
|
||||||
' PreKeys available')
|
' PreKeys available')
|
||||||
|
|
||||||
def build_session(self, recipient_id, device_id, bundle_dict):
|
def build_session(self, recipient_id, device_id, bundle):
|
||||||
sessionBuilder = SessionBuilder(self.store, self.store, self.store,
|
sessionBuilder = SessionBuilder(self.store, self.store, self.store,
|
||||||
self.store, recipient_id, device_id)
|
self.store, recipient_id, device_id)
|
||||||
|
|
||||||
registration_id = self.store.getLocalRegistrationId()
|
registration_id = self.store.getLocalRegistrationId()
|
||||||
|
|
||||||
preKeyPublic = DjbECPublicKey(bundle_dict['preKeyPublic'][1:])
|
prekey = bundle.pick_prekey()
|
||||||
|
preKeyPublic = DjbECPublicKey(prekey['key'][1:])
|
||||||
|
|
||||||
signedPreKeyPublic = DjbECPublicKey(bundle_dict['signedPreKeyPublic'][
|
signedPreKeyPublic = DjbECPublicKey(bundle.spk['key'][1:])
|
||||||
1:])
|
identityKey = IdentityKey(DjbECPublicKey(bundle.ik[1:]))
|
||||||
identityKey = IdentityKey(DjbECPublicKey(bundle_dict['identityKey'][
|
|
||||||
1:]))
|
|
||||||
|
|
||||||
prekey_bundle = PreKeyBundle(
|
prekey_bundle = PreKeyBundle(
|
||||||
registration_id, device_id, bundle_dict['preKeyId'], preKeyPublic,
|
registration_id, device_id,
|
||||||
bundle_dict['signedPreKeyId'], signedPreKeyPublic,
|
prekey['id'], preKeyPublic,
|
||||||
bundle_dict['signedPreKeySignature'], identityKey)
|
bundle.spk['id'], signedPreKeyPublic,
|
||||||
|
bundle.spk_signature,
|
||||||
|
identityKey)
|
||||||
|
|
||||||
sessionBuilder.processPreKeyBundle(prekey_bundle)
|
sessionBuilder.processPreKeyBundle(prekey_bundle)
|
||||||
return self.get_session_cipher(recipient_id, device_id)
|
return self.get_session_cipher(recipient_id, device_id)
|
||||||
@@ -153,101 +151,85 @@ class OmemoState:
|
|||||||
@property
|
@property
|
||||||
def bundle(self):
|
def bundle(self):
|
||||||
self.checkPreKeyAmount()
|
self.checkPreKeyAmount()
|
||||||
prekeys = [
|
|
||||||
(k.getId(), b64encode(k.getKeyPair().getPublicKey().serialize()))
|
bundle = {'otpks': []}
|
||||||
for k in self.store.loadPreKeys()
|
for k in self.store.loadPreKeys():
|
||||||
]
|
key = k.getKeyPair().getPublicKey().serialize()
|
||||||
|
bundle['otpks'].append({'key': key, 'id': k.getId()})
|
||||||
|
|
||||||
identityKeyPair = self.store.getIdentityKeyPair()
|
identityKeyPair = self.store.getIdentityKeyPair()
|
||||||
|
bundle['ik'] = identityKeyPair.getPublicKey().serialize()
|
||||||
|
|
||||||
self.cycleSignedPreKey(identityKeyPair)
|
self.cycleSignedPreKey(identityKeyPair)
|
||||||
|
|
||||||
signedPreKey = self.store.loadSignedPreKey(
|
signedPreKey = self.store.loadSignedPreKey(
|
||||||
self.store.getCurrentSignedPreKeyId())
|
self.store.getCurrentSignedPreKeyId())
|
||||||
|
bundle['spk_signature'] = signedPreKey.getSignature()
|
||||||
|
bundle['spk'] = {'key': signedPreKey.getKeyPair().getPublicKey().serialize(),
|
||||||
|
'id': signedPreKey.getId()}
|
||||||
|
|
||||||
result = {
|
return OMEMOBundle(**bundle)
|
||||||
'signedPreKeyId': signedPreKey.getId(),
|
|
||||||
'signedPreKeyPublic':
|
|
||||||
b64encode(signedPreKey.getKeyPair().getPublicKey().serialize()),
|
|
||||||
'signedPreKeySignature': b64encode(signedPreKey.getSignature()),
|
|
||||||
'identityKey':
|
|
||||||
b64encode(identityKeyPair.getPublicKey().serialize()),
|
|
||||||
'prekeys': prekeys
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
|
|
||||||
def decrypt_msg(self, msg_dict):
|
def decrypt_msg(self, omemo_message, jid):
|
||||||
own_id = self.own_device_id
|
own_id = self.own_device_id
|
||||||
if msg_dict['sid'] == own_id:
|
if omemo_message.sid == own_id:
|
||||||
log.info('Received previously sent message by us')
|
log.info('Received previously sent message by us')
|
||||||
return
|
return
|
||||||
if own_id not in msg_dict['keys']:
|
if own_id not in omemo_message.keys:
|
||||||
log.warning('OMEMO message does not contain our device key')
|
log.warning('OMEMO message does not contain our device key')
|
||||||
return
|
return
|
||||||
|
|
||||||
iv = msg_dict['iv']
|
encrypted_key, prekey = omemo_message.keys[own_id]
|
||||||
sid = msg_dict['sid']
|
|
||||||
sender_jid = msg_dict['sender_jid']
|
|
||||||
payload = msg_dict['payload']
|
|
||||||
|
|
||||||
encrypted_key = msg_dict['keys'][own_id]
|
if prekey:
|
||||||
|
|
||||||
try:
|
|
||||||
key = self.handlePreKeyWhisperMessage(sender_jid, sid,
|
|
||||||
encrypted_key)
|
|
||||||
except (InvalidVersionException, InvalidMessageException):
|
|
||||||
try:
|
try:
|
||||||
key = self.handleWhisperMessage(sender_jid, sid, encrypted_key)
|
key = self.handlePreKeyWhisperMessage(
|
||||||
except (NoSessionException, InvalidMessageException) as e:
|
jid, omemo_message.sid, encrypted_key)
|
||||||
log.warning(e)
|
except Exception as error:
|
||||||
log.warning('sender_jid => ' + str(sender_jid) + ' sid =>' +
|
log.warning(error)
|
||||||
str(sid))
|
|
||||||
return
|
|
||||||
except (DuplicateMessageException) as e:
|
|
||||||
log.warning('Duplicate message found ' + str(e.args))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
except (DuplicateMessageException) as e:
|
else:
|
||||||
log.warning('Duplicate message found ' + str(e.args))
|
try:
|
||||||
return
|
key = self.handleWhisperMessage(
|
||||||
|
jid, omemo_message.sid, encrypted_key)
|
||||||
|
except Exception as error:
|
||||||
|
log.warning(error)
|
||||||
|
return
|
||||||
|
|
||||||
if payload is None:
|
if omemo_message.payload is None:
|
||||||
result = None
|
result = None
|
||||||
log.debug("Decrypted Key Exchange Message")
|
log.debug("Decrypted Key Exchange Message")
|
||||||
else:
|
else:
|
||||||
result = decrypt(key, iv, payload)
|
result = aes_decrypt(key, omemo_message.iv, omemo_message.payload)
|
||||||
log.debug("Decrypted Message => " + result)
|
log.debug("Decrypted Message => %s", result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def create_msg(self, from_jid, jid, plaintext):
|
def create_msg(self, from_jid, jid, plaintext):
|
||||||
key = os.urandom(16)
|
|
||||||
iv = os.urandom(16)
|
|
||||||
encrypted_keys = {}
|
encrypted_keys = {}
|
||||||
|
|
||||||
devices_list = self.device_list_for(jid)
|
devices_list = self.device_list_for(jid)
|
||||||
if len(devices_list) == 0:
|
if not devices_list:
|
||||||
log.error('No known devices')
|
log.error('No known devices')
|
||||||
return
|
return
|
||||||
|
|
||||||
payload, tag = encrypt(key, iv, plaintext)
|
result = aes_encrypt(plaintext)
|
||||||
|
|
||||||
key += tag
|
|
||||||
|
|
||||||
# Encrypt the message key with for each of receivers devices
|
# Encrypt the message key with for each of receivers devices
|
||||||
for device in devices_list:
|
for device in devices_list:
|
||||||
try:
|
try:
|
||||||
if self.isTrusted(jid, device) == TRUSTED:
|
if self.isTrusted(jid, device) == TRUSTED:
|
||||||
cipher = self.get_session_cipher(jid, device)
|
cipher = self.get_session_cipher(jid, device)
|
||||||
cipher_key = cipher.encrypt(key)
|
cipher_key = cipher.encrypt(result.key)
|
||||||
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
||||||
encrypted_keys[device] = (cipher_key.serialize(), prekey)
|
encrypted_keys[device] = (cipher_key.serialize(), prekey)
|
||||||
else:
|
else:
|
||||||
log.debug('Skipped Device because Trust is: ' +
|
log.debug('Skipped Device because Trust is: %s',
|
||||||
str(self.isTrusted(jid, device)))
|
self.isTrusted(jid, device))
|
||||||
except:
|
except Exception:
|
||||||
log.warning('Failed to find key for device ' + str(device))
|
log.warning('Failed to find key for device: %s', device)
|
||||||
|
|
||||||
if len(encrypted_keys) == 0:
|
if not encrypted_keys:
|
||||||
log.error('Encrypted keys empty')
|
log.error('Encrypted keys empty')
|
||||||
raise NoValidSessions('Encrypted keys empty')
|
raise NoValidSessions('Encrypted keys empty')
|
||||||
|
|
||||||
@@ -257,36 +239,29 @@ class OmemoState:
|
|||||||
try:
|
try:
|
||||||
if self.isTrusted(from_jid, device) == TRUSTED:
|
if self.isTrusted(from_jid, device) == TRUSTED:
|
||||||
cipher = self.get_session_cipher(from_jid, device)
|
cipher = self.get_session_cipher(from_jid, device)
|
||||||
cipher_key = cipher.encrypt(key)
|
cipher_key = cipher.encrypt(result.key)
|
||||||
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
||||||
encrypted_keys[device] = (cipher_key.serialize(), prekey)
|
encrypted_keys[device] = (cipher_key.serialize(), prekey)
|
||||||
else:
|
else:
|
||||||
log.debug('Skipped own Device because Trust is: ' +
|
log.debug('Skipped own Device because Trust is: %s',
|
||||||
str(self.isTrusted(from_jid, device)))
|
self.isTrusted(from_jid, device))
|
||||||
except:
|
except Exception:
|
||||||
log.warning('Failed to find key for device ' + str(device))
|
log.warning('Failed to find key for device: %s', device)
|
||||||
|
|
||||||
result = {'sid': self.own_device_id,
|
|
||||||
'keys': encrypted_keys,
|
|
||||||
'jid': jid,
|
|
||||||
'iv': iv,
|
|
||||||
'payload': payload}
|
|
||||||
|
|
||||||
log.debug('Finished encrypting message')
|
log.debug('Finished encrypting message')
|
||||||
return result
|
return OMEMOMessage(sid=self.own_device_id,
|
||||||
|
keys=encrypted_keys,
|
||||||
|
iv=result.iv,
|
||||||
|
payload=result.payload)
|
||||||
|
|
||||||
def create_gc_msg(self, from_jid, jid, plaintext):
|
def create_gc_msg(self, from_jid, jid, plaintext):
|
||||||
key = os.urandom(16)
|
|
||||||
iv = os.urandom(16)
|
|
||||||
encrypted_keys = {}
|
encrypted_keys = {}
|
||||||
room = jid
|
room = jid
|
||||||
encrypted_jids = []
|
encrypted_jids = []
|
||||||
|
|
||||||
devices_list = self.device_list_for(jid, True)
|
devices_list = self.device_list_for(jid, True)
|
||||||
|
|
||||||
payload, tag = encrypt(key, iv, plaintext)
|
result = aes_encrypt(plaintext, append_tag=True)
|
||||||
|
|
||||||
key += tag
|
|
||||||
|
|
||||||
for tup in devices_list:
|
for tup in devices_list:
|
||||||
self.get_session_cipher(tup[0], tup[1])
|
self.get_session_cipher(tup[0], tup[1])
|
||||||
@@ -303,16 +278,15 @@ class OmemoState:
|
|||||||
for rid, cipher in self.session_ciphers[jid_to].items():
|
for rid, cipher in self.session_ciphers[jid_to].items():
|
||||||
try:
|
try:
|
||||||
if self.isTrusted(jid_to, rid) == TRUSTED:
|
if self.isTrusted(jid_to, rid) == TRUSTED:
|
||||||
cipher_key = cipher.encrypt(key)
|
cipher_key = cipher.encrypt(result.key)
|
||||||
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
||||||
encrypted_keys[rid] = (cipher_key.serialize(), prekey)
|
encrypted_keys[rid] = (cipher_key.serialize(), prekey)
|
||||||
else:
|
else:
|
||||||
log.debug('Skipped Device because Trust is: ' +
|
log.debug('Skipped Device because Trust is: %s',
|
||||||
str(self.isTrusted(jid_to, rid)))
|
self.isTrusted(jid_to, rid))
|
||||||
except:
|
except Exception:
|
||||||
log.exception('ERROR:')
|
log.exception('ERROR:')
|
||||||
log.warning('Failed to find key for device ' +
|
log.warning('Failed to find key for device %s', rid)
|
||||||
str(rid))
|
|
||||||
encrypted_jids.append(jid_to)
|
encrypted_jids.append(jid_to)
|
||||||
|
|
||||||
my_other_devices = set(self.own_devices) - set({self.own_device_id})
|
my_other_devices = set(self.own_devices) - set({self.own_device_id})
|
||||||
@@ -321,28 +295,25 @@ class OmemoState:
|
|||||||
try:
|
try:
|
||||||
cipher = self.get_session_cipher(from_jid, dev)
|
cipher = self.get_session_cipher(from_jid, dev)
|
||||||
if self.isTrusted(from_jid, dev) == TRUSTED:
|
if self.isTrusted(from_jid, dev) == TRUSTED:
|
||||||
cipher_key = cipher.encrypt(key)
|
cipher_key = cipher.encrypt(result.key)
|
||||||
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
|
||||||
encrypted_keys[dev] = (cipher_key.serialize(), prekey)
|
encrypted_keys[dev] = (cipher_key.serialize(), prekey)
|
||||||
else:
|
else:
|
||||||
log.debug('Skipped own Device because Trust is: ' +
|
log.debug('Skipped own Device because Trust is: %s',
|
||||||
str(self.isTrusted(from_jid, dev)))
|
self.isTrusted(from_jid, dev))
|
||||||
except:
|
except Exception:
|
||||||
log.exception('ERROR:')
|
log.exception('ERROR:')
|
||||||
log.warning('Failed to find key for device ' + str(dev))
|
log.warning('Failed to find key for device: %s', dev)
|
||||||
|
|
||||||
if not encrypted_keys:
|
if not encrypted_keys:
|
||||||
log.error('Encrypted keys empty')
|
log.error('Encrypted keys empty')
|
||||||
raise NoValidSessions('Encrypted keys empty')
|
raise NoValidSessions('Encrypted keys empty')
|
||||||
|
|
||||||
result = {'sid': self.own_device_id,
|
|
||||||
'keys': encrypted_keys,
|
|
||||||
'jid': jid,
|
|
||||||
'iv': iv,
|
|
||||||
'payload': payload}
|
|
||||||
|
|
||||||
log.debug('Finished encrypting message')
|
log.debug('Finished encrypting message')
|
||||||
return result
|
return OMEMOMessage(sid=self.own_device_id,
|
||||||
|
keys=encrypted_keys,
|
||||||
|
iv=result.iv,
|
||||||
|
payload=result.payload)
|
||||||
|
|
||||||
def device_list_for(self, jid, gc=False):
|
def device_list_for(self, jid, gc=False):
|
||||||
""" Return a list of known device ids for the specified jid.
|
""" Return a list of known device ids for the specified jid.
|
||||||
@@ -439,7 +410,7 @@ class OmemoState:
|
|||||||
key = sessionCipher.decryptPkmsg(preKeyWhisperMessage)
|
key = sessionCipher.decryptPkmsg(preKeyWhisperMessage)
|
||||||
# Publish new bundle after PreKey has been used
|
# Publish new bundle after PreKey has been used
|
||||||
# for building a new Session
|
# for building a new Session
|
||||||
self.xmpp_con.publish_bundle()
|
self.xmpp_con.set_bundle()
|
||||||
self.add_device(recipient_id, device_id)
|
self.add_device(recipient_id, device_id)
|
||||||
return key
|
return key
|
||||||
except UntrustedIdentityException as e:
|
except UntrustedIdentityException as e:
|
||||||
@@ -495,3 +466,7 @@ class OmemoState:
|
|||||||
# Delete all SignedPreKeys that are older than SPK_ARCHIVE_TIME
|
# Delete all SignedPreKeys that are older than SPK_ARCHIVE_TIME
|
||||||
timestamp = now - SPK_ARCHIVE_TIME
|
timestamp = now - SPK_ARCHIVE_TIME
|
||||||
self.store.removeOldSignedPreKeys(timestamp)
|
self.store.removeOldSignedPreKeys(timestamp)
|
||||||
|
|
||||||
|
|
||||||
|
class NoValidSessions(Exception):
|
||||||
|
pass
|
||||||
@@ -1,21 +1,18 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
|
||||||
#
|
#
|
||||||
# Copyright 2017 Philipp Hörist <philipp@hoerist.com>
|
# 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
|
# OMEMO Gajim Plugin is distributed in the hope that it will be useful,
|
||||||
# it under the terms of the GNU General Public License as published by the Free
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# Software Foundation, either version 3 of the License, or (at your option) any
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# later version.
|
# GNU General Public License for more details.
|
||||||
#
|
|
||||||
# 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 <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
#
|
||||||
|
# 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 os
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -32,29 +29,23 @@ from urllib.parse import urlparse, urldefrag
|
|||||||
from io import BufferedWriter, FileIO, BytesIO
|
from io import BufferedWriter, FileIO, BytesIO
|
||||||
|
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common import configpaths
|
from gajim.common import configpaths
|
||||||
from gajim.plugins.plugins_i18n import _
|
from gajim.plugins.plugins_i18n import _
|
||||||
from gajim.gtk.dialogs import ErrorDialog, YesNoDialog
|
from gajim.gtk.dialogs import ErrorDialog, YesNoDialog
|
||||||
|
|
||||||
from omemo.gtk.progress import ProgressWindow
|
from omemo.gtk.progress import ProgressWindow
|
||||||
|
from omemo.backend.aes import aes_decrypt_file
|
||||||
|
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
import certifi
|
import certifi
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo.filedecryption')
|
log = logging.getLogger('gajim.plugin_system.omemo.filedecryption')
|
||||||
|
|
||||||
ERROR = False
|
|
||||||
try:
|
|
||||||
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
|
|
||||||
from omemo.omemo.aes_gcm_native import aes_encrypt
|
|
||||||
except ImportError:
|
|
||||||
log.exception('ImportError')
|
|
||||||
ERROR = True
|
|
||||||
|
|
||||||
DIRECTORY = os.path.join(configpaths.get('MY_DATA'), 'downloads')
|
DIRECTORY = os.path.join(configpaths.get('MY_DATA'), 'downloads')
|
||||||
|
|
||||||
|
ERROR = False
|
||||||
try:
|
try:
|
||||||
if not os.path.exists(DIRECTORY):
|
if not os.path.exists(DIRECTORY):
|
||||||
os.makedirs(DIRECTORY)
|
os.makedirs(DIRECTORY)
|
||||||
@@ -63,15 +54,6 @@ except Exception:
|
|||||||
log.exception('Error')
|
log.exception('Error')
|
||||||
|
|
||||||
|
|
||||||
def encrypt_file(data):
|
|
||||||
key = os.urandom(32)
|
|
||||||
iv = os.urandom(16)
|
|
||||||
|
|
||||||
payload, tag = aes_encrypt(key, iv, data)
|
|
||||||
encrypted_data = payload + tag
|
|
||||||
return (encrypted_data, key, iv)
|
|
||||||
|
|
||||||
|
|
||||||
class File:
|
class File:
|
||||||
def __init__(self, url, account):
|
def __init__(self, url, account):
|
||||||
self.account = account
|
self.account = account
|
||||||
@@ -105,7 +87,7 @@ class FileDecryption:
|
|||||||
if not self.is_encrypted(file):
|
if not self.is_encrypted(file):
|
||||||
log.info('Url not encrypted: %s', url)
|
log.info('Url not encrypted: %s', url)
|
||||||
return
|
return
|
||||||
|
print('ADASD')
|
||||||
self.create_paths(file)
|
self.create_paths(file)
|
||||||
|
|
||||||
if os.path.exists(file.filepath):
|
if os.path.exists(file.filepath):
|
||||||
@@ -181,7 +163,10 @@ class Download:
|
|||||||
return
|
return
|
||||||
|
|
||||||
GLib.idle_add(self.progressbar.set_text, _('Decrypting...'))
|
GLib.idle_add(self.progressbar.set_text, _('Decrypting...'))
|
||||||
decrypted_data = self.aes_decrypt(data)
|
|
||||||
|
decrypted_data = aes_decrypt_file(self.file.key,
|
||||||
|
self.file.iv,
|
||||||
|
data.getvalue())
|
||||||
|
|
||||||
GLib.idle_add(
|
GLib.idle_add(
|
||||||
self.progressbar.set_text, _('Writing file to harddisk...'))
|
self.progressbar.set_text, _('Writing file to harddisk...'))
|
||||||
@@ -203,11 +188,11 @@ class Download:
|
|||||||
log.warning('CERT Verification disabled')
|
log.warning('CERT Verification disabled')
|
||||||
get_request = urlopen(self.file.url, timeout=30, context=context)
|
get_request = urlopen(self.file.url, timeout=30, context=context)
|
||||||
else:
|
else:
|
||||||
|
cafile = None
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
get_request = urlopen(
|
cafile = certifi.where()
|
||||||
self.file.url, cafile=certifi.where(), timeout=30)
|
context = ssl.create_default_context(cafile=cafile)
|
||||||
else:
|
get_request = urlopen(self.file.url, timeout=30, context=context)
|
||||||
get_request = urlopen(self.file.url, timeout=30)
|
|
||||||
|
|
||||||
size = get_request.info()['Content-Length']
|
size = get_request.info()['Content-Length']
|
||||||
if not size:
|
if not size:
|
||||||
@@ -241,17 +226,6 @@ class Download:
|
|||||||
stream.close()
|
stream.close()
|
||||||
return str(errormsg)
|
return str(errormsg)
|
||||||
|
|
||||||
def aes_decrypt(self, payload):
|
|
||||||
# Use AES128 GCM with the given key and iv to decrypt the payload.
|
|
||||||
payload = payload.getvalue()
|
|
||||||
data = payload[:-16]
|
|
||||||
tag = payload[-16:]
|
|
||||||
decryptor = Cipher(
|
|
||||||
algorithms.AES(self.file.key),
|
|
||||||
GCM(self.file.iv, tag=tag),
|
|
||||||
backend=default_backend()).decryptor()
|
|
||||||
return decryptor.update(data) + decryptor.finalize()
|
|
||||||
|
|
||||||
def write_file(self, data):
|
def write_file(self, data):
|
||||||
log.info('Writing data to %s', self.file.filepath)
|
log.info('Writing data to %s', self.file.filepath)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
'''
|
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
|
||||||
Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
# Copyright (C) 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
||||||
Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
|
# Copyright (C) 2015 Daniel Gultsch <daniel@cgultsch.de>
|
||||||
Copyright 2016 Philipp Hörist <philipp@hoerist.com>
|
#
|
||||||
|
# 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
|
||||||
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
|
||||||
it under the terms of the GNU General Public License as published by the Free
|
# by the Free Software Foundation; version 3 only.
|
||||||
Software Foundation, either version 3 of the License, or (at your option) any
|
#
|
||||||
later version.
|
# OMEMO Gajim Plugin is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
# GNU General Public License for more details.
|
||||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
You should have received a copy of the GNU General Public License along with
|
# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
'''
|
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
import logging
|
import logging
|
||||||
@@ -171,7 +169,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
|||||||
def cleardevice_button_clicked_cb(self, button, *args):
|
def cleardevice_button_clicked_cb(self, button, *args):
|
||||||
active = self._ui.get_object('account_combobox').get_active()
|
active = self._ui.get_object('account_combobox').get_active()
|
||||||
account = self.account_store[active][0]
|
account = self.account_store[active][0]
|
||||||
app.connections[account].get_module('OMEMO').publish_own_devices_list(new=True)
|
app.connections[account].get_module('OMEMO').set_devicelist(new=True)
|
||||||
self.update_context_list()
|
self.update_context_list()
|
||||||
|
|
||||||
def refresh_button_clicked_cb(self, button, *args):
|
def refresh_button_clicked_cb(self, button, *args):
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
|
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
|
||||||
#
|
#
|
||||||
# This file is part of Gajim.
|
# This file is part of OMEMO Gajim Plugin.
|
||||||
#
|
#
|
||||||
# Gajim is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# by the Free Software Foundation; version 3 only.
|
||||||
#
|
#
|
||||||
# Gajim is distributed in the hope that it will be useful,
|
# OMEMO Gajim Plugin is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import binascii
|
import binascii
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
|
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
|
||||||
#
|
#
|
||||||
# This file is part of Gajim.
|
# This file is part of OMEMO Gajim Plugin.
|
||||||
#
|
#
|
||||||
# Gajim is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# by the Free Software Foundation; version 3 only.
|
||||||
#
|
#
|
||||||
# Gajim is distributed in the hope that it will be useful,
|
# OMEMO Gajim Plugin is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
from gajim.plugins.helpers import get_builder
|
from gajim.plugins.helpers import get_builder
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
# This file is part of Gajim-OMEMO.
|
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
|
||||||
#
|
#
|
||||||
# Gajim-OMEMO is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# by the Free Software Foundation; version 3 only.
|
||||||
#
|
#
|
||||||
# Gajim-OMEMO is distributed in the hope that it will be useful,
|
# OMEMO Gajim Plugin is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim-OMEMO. If not, see <http://www.gnu.org/licenses/>.
|
# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,77 +0,0 @@
|
|||||||
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
|
|
||||||
#
|
|
||||||
# This file is part of OMEMO.
|
|
||||||
#
|
|
||||||
# OMEMO 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 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. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# XEP-0384: OMEMO Encryption
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import nbxmpp
|
|
||||||
|
|
||||||
from gajim.common import app
|
|
||||||
from gajim.common.modules.pep import AbstractPEPModule, AbstractPEPData
|
|
||||||
|
|
||||||
from omemo.modules.util import NS_OMEMO
|
|
||||||
from omemo.modules.util import NS_DEVICE_LIST
|
|
||||||
from omemo.modules.util import unpack_devicelist
|
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo.pep')
|
|
||||||
|
|
||||||
# Module name
|
|
||||||
name = 'OMEMODevicelist'
|
|
||||||
zeroconf = False
|
|
||||||
|
|
||||||
|
|
||||||
class OMEMODevicelistData(AbstractPEPData):
|
|
||||||
|
|
||||||
type_ = 'omemo-devicelist'
|
|
||||||
|
|
||||||
|
|
||||||
class OMEMODevicelist(AbstractPEPModule):
|
|
||||||
'''
|
|
||||||
<item>
|
|
||||||
<list xmlns='eu.siacs.conversations.axolotl'>
|
|
||||||
<device id='12345' />
|
|
||||||
<device id='4223' />
|
|
||||||
</list>
|
|
||||||
</item>
|
|
||||||
'''
|
|
||||||
|
|
||||||
name = 'omemo-devicelist'
|
|
||||||
namespace = NS_DEVICE_LIST
|
|
||||||
pep_class = OMEMODevicelistData
|
|
||||||
store_publish = True
|
|
||||||
_log = log
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _extract_info(item):
|
|
||||||
return unpack_devicelist(item)
|
|
||||||
|
|
||||||
def _notification_received(self, jid, devicelist):
|
|
||||||
con = app.connections[self._account]
|
|
||||||
con.get_module('OMEMO').device_list_received(devicelist.data,
|
|
||||||
jid.getStripped())
|
|
||||||
@staticmethod
|
|
||||||
def _build_node(devicelist):
|
|
||||||
list_node = nbxmpp.Node('list', {'xmlns': NS_OMEMO})
|
|
||||||
if devicelist is None:
|
|
||||||
return list_node
|
|
||||||
for device in devicelist:
|
|
||||||
list_node.addChild('device', attrs={'id': device})
|
|
||||||
return list_node
|
|
||||||
|
|
||||||
|
|
||||||
def get_instance(*args, **kwargs):
|
|
||||||
return OMEMODevicelist(*args, **kwargs), 'OMEMODevicelist'
|
|
||||||
@@ -1,46 +1,30 @@
|
|||||||
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
|
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
|
||||||
#
|
#
|
||||||
# This file is part of OMEMO.
|
# This file is part of OMEMO Gajim Plugin.
|
||||||
#
|
#
|
||||||
# OMEMO is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# by the Free Software Foundation; version 3 only.
|
||||||
#
|
#
|
||||||
# OMEMO is distributed in the hope that it will be useful,
|
# OMEMO Gajim Plugin is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with OMEMO. If not, see <http://www.gnu.org/licenses/>.
|
# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# XEP-0384: OMEMO Encryption
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import nbxmpp
|
import nbxmpp
|
||||||
|
|
||||||
from gajim.common.exceptions import StanzaMalformed
|
|
||||||
|
def prepare_stanza(stanza, plaintext):
|
||||||
|
delete_nodes(stanza, 'encrypted', nbxmpp.NS_OMEMO_TEMP)
|
||||||
|
delete_nodes(stanza, 'body')
|
||||||
|
stanza.setBody(plaintext)
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
def delete_nodes(stanza, name, namespace=None):
|
||||||
|
nodes = stanza.getTags(name, namespace=namespace)
|
||||||
NS_OMEMO = 'eu.siacs.conversations.axolotl'
|
for node in nodes:
|
||||||
NS_DEVICE_LIST = NS_OMEMO + '.devicelist'
|
stanza.delChild(node)
|
||||||
|
|
||||||
|
|
||||||
def unpack_devicelist(item):
|
|
||||||
list_ = item.getTag('list', namespace=NS_OMEMO)
|
|
||||||
if list_ is None:
|
|
||||||
raise StanzaMalformed('No list node')
|
|
||||||
|
|
||||||
device_list = list_.getTags('device')
|
|
||||||
|
|
||||||
devices = []
|
|
||||||
for device in device_list:
|
|
||||||
id_ = device.getAttr('id')
|
|
||||||
if id_ is None:
|
|
||||||
raise StanzaMalformed('No id for device found')
|
|
||||||
|
|
||||||
devices.append(int(id_))
|
|
||||||
return devices
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
|
||||||
#
|
|
||||||
# This file is part of python-omemo library.
|
|
||||||
#
|
|
||||||
# The python-omemo library 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.
|
|
||||||
#
|
|
||||||
# python-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 python-omemo library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from .aes_gcm_native import aes_decrypt
|
|
||||||
from .aes_gcm_native import aes_encrypt
|
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
|
||||||
|
|
||||||
|
|
||||||
def encrypt(key, iv, plaintext):
|
|
||||||
return aes_encrypt(key, iv, plaintext)
|
|
||||||
|
|
||||||
|
|
||||||
def decrypt(key, iv, ciphertext):
|
|
||||||
return aes_decrypt(key, iv, ciphertext).decode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
class NoValidSessions(Exception):
|
|
||||||
pass
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
|
||||||
#
|
|
||||||
# This file is part of python-omemo library.
|
|
||||||
#
|
|
||||||
# The python-omemo library 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.
|
|
||||||
#
|
|
||||||
# python-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 python-omemo library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
|
||||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
|
||||||
from cryptography.hazmat.primitives.ciphers.modes import GCM
|
|
||||||
|
|
||||||
# On Windows we have to import a specific backend because the
|
|
||||||
# default_backend() mechanism doesn't work in Gajim for Windows.
|
|
||||||
# Its because of how Gajim is build with cx_freeze
|
|
||||||
|
|
||||||
if os.name == 'nt':
|
|
||||||
from cryptography.hazmat.backends.openssl import backend
|
|
||||||
else:
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
|
||||||
|
|
||||||
def aes_decrypt(_key, iv, payload):
|
|
||||||
""" Use AES128 GCM with the given key and iv to decrypt the 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:]
|
|
||||||
if os.name == 'nt':
|
|
||||||
_backend = backend
|
|
||||||
else:
|
|
||||||
_backend = default_backend()
|
|
||||||
decryptor = Cipher(
|
|
||||||
algorithms.AES(key),
|
|
||||||
GCM(iv, tag=tag),
|
|
||||||
backend=_backend).decryptor()
|
|
||||||
return decryptor.update(data) + decryptor.finalize()
|
|
||||||
|
|
||||||
|
|
||||||
def aes_encrypt(key, iv, plaintext):
|
|
||||||
""" Use AES128 GCM with the given key and iv to encrypt the plaintext. """
|
|
||||||
if os.name == 'nt':
|
|
||||||
_backend = backend
|
|
||||||
else:
|
|
||||||
_backend = default_backend()
|
|
||||||
encryptor = Cipher(
|
|
||||||
algorithms.AES(key),
|
|
||||||
GCM(iv),
|
|
||||||
backend=_backend).encryptor()
|
|
||||||
return encryptor.update(plaintext) + encryptor.finalize(), encryptor.tag
|
|
||||||
@@ -1,24 +1,20 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
|
||||||
|
# Copyright (C) 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
||||||
'''
|
# Copyright (C) 2015 Daniel Gultsch <daniel@cgultsch.de>
|
||||||
Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
#
|
||||||
Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
|
# This file is part of OMEMO Gajim Plugin.
|
||||||
Copyright 2016 Philipp Hörist <philipp@hoerist.com>
|
#
|
||||||
|
# OMEMO Gajim Plugin is free software; you can redistribute it and/or modify
|
||||||
This file is part of Gajim-OMEMO plugin.
|
# 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
|
# OMEMO Gajim Plugin is distributed in the hope that it will be useful,
|
||||||
Software Foundation, either version 3 of the License, or (at your option) any
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
later version.
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
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
|
# You should have received a copy of the GNU General Public License
|
||||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
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/>.
|
|
||||||
'''
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import binascii
|
import binascii
|
||||||
@@ -36,11 +32,12 @@ from gajim.plugins import GajimPlugin
|
|||||||
from gajim.plugins.plugins_i18n import _
|
from gajim.plugins.plugins_i18n import _
|
||||||
from gajim.groupchat_control import GroupchatControl
|
from gajim.groupchat_control import GroupchatControl
|
||||||
|
|
||||||
|
from omemo import file_crypto
|
||||||
from omemo.gtk.key import KeyDialog
|
from omemo.gtk.key import KeyDialog
|
||||||
from omemo.gtk.config import OMEMOConfigDialog
|
from omemo.gtk.config import OMEMOConfigDialog
|
||||||
|
from omemo.backend.aes import aes_encrypt_file
|
||||||
|
|
||||||
|
|
||||||
CRYPTOGRAPHY_MISSING = 'You are missing Python3-Cryptography'
|
|
||||||
AXOLOTL_MISSING = 'You are missing Python3-Axolotl or use an outdated version'
|
AXOLOTL_MISSING = 'You are missing Python3-Axolotl or use an outdated version'
|
||||||
PROTOBUF_MISSING = "OMEMO can't import Google Protobuf, you can find help in " \
|
PROTOBUF_MISSING = "OMEMO can't import Google Protobuf, you can find help in " \
|
||||||
"the GitHub Wiki"
|
"the GitHub Wiki"
|
||||||
@@ -48,12 +45,11 @@ ERROR_MSG = ''
|
|||||||
|
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||||
|
if log.getEffectiveLevel() == logging.DEBUG:
|
||||||
try:
|
log_axolotl = logging.getLogger('axolotl')
|
||||||
from omemo import file_crypto
|
log_axolotl.setLevel(logging.DEBUG)
|
||||||
except Exception as error:
|
log_axolotl.addHandler(logging.StreamHandler())
|
||||||
log.exception(error)
|
log_axolotl.propagate = False
|
||||||
ERROR_MSG = CRYPTOGRAPHY_MISSING
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import google.protobuf
|
import google.protobuf
|
||||||
@@ -70,14 +66,10 @@ except Exception as error:
|
|||||||
if not ERROR_MSG:
|
if not ERROR_MSG:
|
||||||
try:
|
try:
|
||||||
from omemo.modules import omemo
|
from omemo.modules import omemo
|
||||||
from omemo.modules import omemo_devicelist
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
log.error(error)
|
log.error(error)
|
||||||
ERROR_MSG = 'Error: %s' % error
|
ERROR_MSG = 'Error: %s' % error
|
||||||
|
|
||||||
# pylint: disable=no-init
|
|
||||||
# pylint: disable=attribute-defined-outside-init
|
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class UserMessages(IntEnum):
|
class UserMessages(IntEnum):
|
||||||
@@ -87,7 +79,6 @@ class UserMessages(IntEnum):
|
|||||||
|
|
||||||
class OmemoPlugin(GajimPlugin):
|
class OmemoPlugin(GajimPlugin):
|
||||||
def init(self):
|
def init(self):
|
||||||
""" Init """
|
|
||||||
if ERROR_MSG:
|
if ERROR_MSG:
|
||||||
self.activatable = False
|
self.activatable = False
|
||||||
self.available_text = ERROR_MSG
|
self.available_text = ERROR_MSG
|
||||||
@@ -99,17 +90,13 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
'signed-in': (ged.PRECORE, self.signed_in),
|
'signed-in': (ged.PRECORE, self.signed_in),
|
||||||
'omemo-new-fingerprint': (ged.PRECORE, self._on_new_fingerprints),
|
'omemo-new-fingerprint': (ged.PRECORE, self._on_new_fingerprints),
|
||||||
}
|
}
|
||||||
self.modules = [
|
self.modules = [omemo]
|
||||||
omemo,
|
|
||||||
omemo_devicelist,
|
|
||||||
]
|
|
||||||
self.config_dialog = OMEMOConfigDialog(self)
|
self.config_dialog = OMEMOConfigDialog(self)
|
||||||
self.gui_extension_points = {
|
self.gui_extension_points = {
|
||||||
'hyperlink_handler': (self._file_decryption, None),
|
'hyperlink_handler': (self._file_decryption, None),
|
||||||
'encrypt' + self.encryption_name: (self._encrypt_message, None),
|
'encrypt' + self.encryption_name: (self._encrypt_message, None),
|
||||||
'gc_encrypt' + self.encryption_name: (
|
'gc_encrypt' + self.encryption_name: (
|
||||||
self._gc_encrypt_message, None),
|
self._gc_encrypt_message, None),
|
||||||
'decrypt': (self._message_received, None),
|
|
||||||
'send_message' + self.encryption_name: (
|
'send_message' + self.encryption_name: (
|
||||||
self.before_sendmessage, None),
|
self.before_sendmessage, None),
|
||||||
'encryption_dialog' + self.encryption_name: (
|
'encryption_dialog' + self.encryption_name: (
|
||||||
@@ -198,11 +185,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _message_received(self, conn, obj, callback):
|
|
||||||
if conn.name == 'Local':
|
|
||||||
return
|
|
||||||
app.connections[conn.name].get_module('OMEMO').message_received(conn, obj, callback)
|
|
||||||
|
|
||||||
def _gc_encrypt_message(self, conn, obj, callback):
|
def _gc_encrypt_message(self, conn, obj, callback):
|
||||||
if conn.name == 'Local':
|
if conn.name == 'Local':
|
||||||
return
|
return
|
||||||
@@ -225,12 +207,11 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _encrypt_file_thread(file, callback, *args, **kwargs):
|
def _encrypt_file_thread(file, callback, *args, **kwargs):
|
||||||
encrypted_data, key, iv = file_crypto.encrypt_file(
|
result = aes_encrypt_file(file.get_data(full=True))
|
||||||
file.get_data(full=True))
|
|
||||||
file.encrypted = True
|
file.encrypted = True
|
||||||
file.size = len(encrypted_data)
|
file.size = len(result.payload)
|
||||||
file.user_data = binascii.hexlify(iv + key).decode('utf-8')
|
file.user_data = binascii.hexlify(result.iv + result.key).decode()
|
||||||
file.data = encrypted_data
|
file.data = result.payload
|
||||||
if file.event.isSet():
|
if file.event.isSet():
|
||||||
return
|
return
|
||||||
GLib.idle_add(callback, file)
|
GLib.idle_add(callback, file)
|
||||||
@@ -267,7 +248,7 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
else:
|
else:
|
||||||
# check if we have devices for the contact
|
# check if we have devices for the contact
|
||||||
if not self.get_omemo(account).device_list_for(contact.jid):
|
if not self.get_omemo(account).device_list_for(contact.jid):
|
||||||
con.query_devicelist(contact.jid, True)
|
con.request_devicelist(contact.jid, True)
|
||||||
self.print_message(chat_control, UserMessages.QUERY_DEVICES)
|
self.print_message(chat_control, UserMessages.QUERY_DEVICES)
|
||||||
chat_control.sendmessage = False
|
chat_control.sendmessage = False
|
||||||
return
|
return
|
||||||
308
omemo/xmpp.py
308
omemo/xmpp.py
@@ -1,308 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
|
||||||
#
|
|
||||||
# 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 <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
""" This module handles all the XMPP logic like creating different kind of
|
|
||||||
stanza nodes and getting data from stanzas.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import random
|
|
||||||
from base64 import b64decode, b64encode
|
|
||||||
|
|
||||||
from nbxmpp.protocol import NS_PUBSUB, Iq
|
|
||||||
from nbxmpp.simplexml import Node
|
|
||||||
|
|
||||||
from gajim.common import app # pylint: disable=import-error
|
|
||||||
from gajim.plugins.helpers import log_calls # pylint: disable=import-error
|
|
||||||
|
|
||||||
NS_PUBSUB_EVENT = NS_PUBSUB + '#event'
|
|
||||||
|
|
||||||
NS_EME = 'urn:xmpp:eme:0'
|
|
||||||
NS_OMEMO = 'eu.siacs.conversations.axolotl'
|
|
||||||
NS_DEVICE_LIST = NS_OMEMO + '.devicelist'
|
|
||||||
NS_NOTIFY = NS_DEVICE_LIST + '+notify'
|
|
||||||
NS_BUNDLES = NS_OMEMO + '.bundles:'
|
|
||||||
NS_HINTS = 'urn:xmpp:hints'
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
|
||||||
|
|
||||||
|
|
||||||
class PubsubNode(Node):
|
|
||||||
def __init__(self, data):
|
|
||||||
assert isinstance(data, Node)
|
|
||||||
Node.__init__(self, tag='pubsub', attrs={'xmlns': NS_PUBSUB})
|
|
||||||
self.addChild(node=data)
|
|
||||||
|
|
||||||
|
|
||||||
class OmemoMessage(Node):
|
|
||||||
def __init__(self, msg_dict):
|
|
||||||
# , contact_jid, key, iv, payload, dev_id, my_dev_id):
|
|
||||||
Node.__init__(self, 'encrypted', attrs={'xmlns': NS_OMEMO})
|
|
||||||
header = Node('header', attrs={'sid': msg_dict['sid']})
|
|
||||||
for rid, (key, prekey) in msg_dict['keys'].items():
|
|
||||||
if prekey:
|
|
||||||
child = header.addChild('key',
|
|
||||||
attrs={'prekey': 'true', 'rid': rid})
|
|
||||||
else:
|
|
||||||
child = header.addChild('key',
|
|
||||||
attrs={'rid': rid})
|
|
||||||
child.addData(b64encode(key).decode('utf-8'))
|
|
||||||
header.addChild('iv').addData(b64encode(msg_dict['iv']).decode('utf-8'))
|
|
||||||
self.addChild(node=header)
|
|
||||||
self.addChild('payload').addData(b64encode(msg_dict['payload'])
|
|
||||||
.decode('utf-8'))
|
|
||||||
|
|
||||||
|
|
||||||
class BundleInformationQuery(Iq):
|
|
||||||
def __init__(self, contact_jid, device_id):
|
|
||||||
assert isinstance(device_id, int)
|
|
||||||
id_ = app.get_an_id()
|
|
||||||
attrs = {'id': id_}
|
|
||||||
Iq.__init__(self, typ='get', attrs=attrs, to=contact_jid)
|
|
||||||
items = Node('items', attrs={'node': NS_BUNDLES + str(device_id),
|
|
||||||
'max_items': 1})
|
|
||||||
pubsub = PubsubNode(items)
|
|
||||||
self.addChild(node=pubsub)
|
|
||||||
|
|
||||||
|
|
||||||
class DevicelistQuery(Iq):
|
|
||||||
def __init__(self, contact_jid):
|
|
||||||
id_ = app.get_an_id()
|
|
||||||
attrs = {'id': id_}
|
|
||||||
Iq.__init__(self, typ='get', attrs=attrs, to=contact_jid)
|
|
||||||
items = Node('items', attrs={'node': NS_DEVICE_LIST, 'max_items': 1})
|
|
||||||
pubsub = PubsubNode(items)
|
|
||||||
self.addChild(node=pubsub)
|
|
||||||
|
|
||||||
|
|
||||||
def make_bundle(state_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'))
|
|
||||||
|
|
||||||
prekey_sig_node = result.addChild('signedPreKeySignature')
|
|
||||||
prekey_sig_node.addData(state_bundle['signedPreKeySignature']
|
|
||||||
.decode('utf-8'))
|
|
||||||
|
|
||||||
identity_key_node = result.addChild('identityKey')
|
|
||||||
identity_key_node.addData(state_bundle['identityKey'].decode('utf-8'))
|
|
||||||
prekeys = result.addChild('prekeys')
|
|
||||||
|
|
||||||
for key in state_bundle['prekeys']:
|
|
||||||
prekeys.addChild('preKeyPublic',
|
|
||||||
attrs={'preKeyId': key[0]}) \
|
|
||||||
.addData(key[1].decode('utf-8'))
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def unpack_device_bundle(bundle, device_id):
|
|
||||||
pubsub = bundle.getTag('pubsub', namespace=NS_PUBSUB)
|
|
||||||
if not pubsub:
|
|
||||||
log.warning('OMEMO device bundle has no pubsub node')
|
|
||||||
return
|
|
||||||
items = pubsub.getTag('items', attrs={'node': NS_BUNDLES + str(device_id)})
|
|
||||||
if not items:
|
|
||||||
log.warning('OMEMO device bundle has no items node')
|
|
||||||
return
|
|
||||||
|
|
||||||
item = items.getTag('item', namespace=NS_PUBSUB)
|
|
||||||
if not item:
|
|
||||||
log.warning('OMEMO device bundle has no item node')
|
|
||||||
return
|
|
||||||
|
|
||||||
bundle = item.getTag('bundle', namespace=NS_OMEMO)
|
|
||||||
if not bundle:
|
|
||||||
log.warning('OMEMO device bundle has no bundle node')
|
|
||||||
return
|
|
||||||
|
|
||||||
signed_prekey_node = bundle.getTag('signedPreKeyPublic',
|
|
||||||
namespace=NS_OMEMO)
|
|
||||||
if not signed_prekey_node:
|
|
||||||
log.warning('OMEMO device bundle has no signedPreKeyPublic node')
|
|
||||||
return
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
result['signedPreKeyPublic'] = decode_data(signed_prekey_node)
|
|
||||||
if not result['signedPreKeyPublic']:
|
|
||||||
log.warning('OMEMO device bundle has no signedPreKeyPublic data')
|
|
||||||
return
|
|
||||||
|
|
||||||
if not signed_prekey_node.getAttr('signedPreKeyId'):
|
|
||||||
log.warning('OMEMO device bundle has no signedPreKeyId')
|
|
||||||
return
|
|
||||||
result['signedPreKeyId'] = int(signed_prekey_node.getAttr(
|
|
||||||
'signedPreKeyId'))
|
|
||||||
|
|
||||||
signed_signature_node = bundle.getTag('signedPreKeySignature',
|
|
||||||
namespace=NS_OMEMO)
|
|
||||||
if not signed_signature_node:
|
|
||||||
log.warning('OMEMO device bundle has no signedPreKeySignature node')
|
|
||||||
return
|
|
||||||
|
|
||||||
result['signedPreKeySignature'] = decode_data(signed_signature_node)
|
|
||||||
if not result['signedPreKeySignature']:
|
|
||||||
log.warning('OMEMO device bundle has no signedPreKeySignature data')
|
|
||||||
return
|
|
||||||
|
|
||||||
identity_key_node = bundle.getTag('identityKey', namespace=NS_OMEMO)
|
|
||||||
if not identity_key_node:
|
|
||||||
log.warning('OMEMO device bundle has no identityKey node')
|
|
||||||
return
|
|
||||||
|
|
||||||
result['identityKey'] = decode_data(identity_key_node)
|
|
||||||
if not result['identityKey']:
|
|
||||||
log.warning('OMEMO device bundle has no identityKey data')
|
|
||||||
return
|
|
||||||
|
|
||||||
prekeys = bundle.getTag('prekeys', namespace=NS_OMEMO)
|
|
||||||
if not prekeys or len(prekeys.getChildren()) == 0:
|
|
||||||
log.warning('OMEMO device bundle has no prekys')
|
|
||||||
return
|
|
||||||
|
|
||||||
picked_key_node = random.SystemRandom().choice(prekeys.getChildren())
|
|
||||||
|
|
||||||
if not picked_key_node.getAttr('preKeyId'):
|
|
||||||
log.warning('OMEMO PreKey has no id set')
|
|
||||||
return
|
|
||||||
result['preKeyId'] = int(picked_key_node.getAttr('preKeyId'))
|
|
||||||
|
|
||||||
result['preKeyPublic'] = decode_data(picked_key_node)
|
|
||||||
if not result['preKeyPublic']:
|
|
||||||
return
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def unpack_encrypted(encrypted_node):
|
|
||||||
""" Unpacks the encrypted node, decodes the data and returns a msg_dict.
|
|
||||||
"""
|
|
||||||
if not encrypted_node.getNamespace() == NS_OMEMO:
|
|
||||||
log.warning("Encrypted node with wrong NS")
|
|
||||||
return
|
|
||||||
|
|
||||||
header_node = encrypted_node.getTag('header', namespace=NS_OMEMO)
|
|
||||||
if not header_node:
|
|
||||||
log.warning("OMEMO message without header")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not header_node.getAttr('sid'):
|
|
||||||
log.warning("OMEMO message without sid in header")
|
|
||||||
return
|
|
||||||
|
|
||||||
sid = int(header_node.getAttr('sid'))
|
|
||||||
|
|
||||||
iv_node = header_node.getTag('iv', namespace=NS_OMEMO)
|
|
||||||
if not iv_node:
|
|
||||||
log.warning("OMEMO message without iv")
|
|
||||||
return
|
|
||||||
|
|
||||||
iv = decode_data(iv_node)
|
|
||||||
if not iv:
|
|
||||||
log.warning("OMEMO message without iv data")
|
|
||||||
|
|
||||||
payload_node = encrypted_node.getTag('payload', namespace=NS_OMEMO)
|
|
||||||
payload = None
|
|
||||||
if payload_node:
|
|
||||||
payload = decode_data(payload_node)
|
|
||||||
|
|
||||||
key_nodes = header_node.getTags('key')
|
|
||||||
if len(key_nodes) < 1:
|
|
||||||
log.warning("OMEMO message without keys")
|
|
||||||
return
|
|
||||||
|
|
||||||
keys = {}
|
|
||||||
for kn in key_nodes:
|
|
||||||
rid = kn.getAttr('rid')
|
|
||||||
if not rid:
|
|
||||||
log.warning('Omemo key without rid')
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not kn.getData():
|
|
||||||
log.warning('Omemo key without data')
|
|
||||||
continue
|
|
||||||
|
|
||||||
keys[int(rid)] = decode_data(kn)
|
|
||||||
|
|
||||||
result = {'sid': sid, 'iv': iv, 'keys': keys, 'payload': payload}
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def unpack_device_list_update(stanza, account):
|
|
||||||
""" Unpacks the device list from a stanza
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
stanza
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
[int]
|
|
||||||
List of device ids or empty list if nothing found
|
|
||||||
"""
|
|
||||||
event_node = stanza.getTag('event', namespace=NS_PUBSUB_EVENT)
|
|
||||||
if not event_node:
|
|
||||||
event_node = stanza.getTag('pubsub', namespace=NS_PUBSUB)
|
|
||||||
result = []
|
|
||||||
|
|
||||||
if not event_node:
|
|
||||||
log.warning(account + ' => Device list update event node empty!')
|
|
||||||
return result
|
|
||||||
|
|
||||||
items = event_node.getTag('items', {'node': NS_DEVICE_LIST})
|
|
||||||
if not items or len(items.getChildren()) != 1:
|
|
||||||
log.warning(
|
|
||||||
account +
|
|
||||||
' => Device list update items node empty or not omemo device update')
|
|
||||||
return result
|
|
||||||
|
|
||||||
list_node = items.getChildren()[0].getTag('list')
|
|
||||||
if not list_node or len(list_node.getChildren()) == 0:
|
|
||||||
log.warning(account + ' => Device list update list node empty!')
|
|
||||||
return result
|
|
||||||
|
|
||||||
devices_nodes = list_node.getChildren()
|
|
||||||
|
|
||||||
for dn in devices_nodes:
|
|
||||||
_id = dn.getAttr('id')
|
|
||||||
if _id:
|
|
||||||
result.append(int(_id))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def decode_data(node):
|
|
||||||
""" Fetch the data from specified node and b64decode it. """
|
|
||||||
data = node.getData()
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
log.warning("No node data")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
return b64decode(data)
|
|
||||||
except:
|
|
||||||
log.warning('b64decode broken')
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def successful(stanza):
|
|
||||||
""" Check if stanza type is result. """
|
|
||||||
return stanza.getAttr('type') == 'result'
|
|
||||||
Reference in New Issue
Block a user