[omemo] Refactor Plugin

- Adapt to nbxmpp supporting OMEMO
- Move python-axolotl code into backend folder
This commit is contained in:
Philipp Hörist
2019-02-11 22:32:27 +01:00
parent a6ed314941
commit 3c78b09fb2
24 changed files with 1460 additions and 2318 deletions

View File

@@ -1 +1 @@
from .omemoplugin import OmemoPlugin
from omemo.plugin import OmemoPlugin

View File

@@ -1 +1 @@
__version__ = "0.1.0"
__version__ = "0.1.0"

86
omemo/backend/aes.py Normal file
View 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)

View File

@@ -1,15 +1,15 @@
''' Database helper functions '''
def table_exists(db, name):
""" Check if the specified table exists in the db. """
query = """ SELECT name FROM sqlite_master
WHERE type='table' AND name=?;
"""
return db.execute(query, (name, )).fetchone() is not None
def user_version(db):
""" Return the value of PRAGMA user_version. """
return db.execute('PRAGMA user_version').fetchone()[0]
''' Database helper functions '''
def table_exists(db, name):
""" Check if the specified table exists in the db. """
query = """ SELECT name FROM sqlite_master
WHERE type='table' AND name=?;
"""
return db.execute(query, (name, )).fetchone() is not None
def user_version(db):
""" Return the value of PRAGMA user_version. """
return db.execute('PRAGMA user_version').fetchone()[0]

View File

@@ -1,64 +1,64 @@
# -*- coding: utf-8 -*-
#
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
# Copyright 2015 Daniel Gultsch <daniel@cgultsch.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/>.
#
class EncryptionState():
""" Used to store if OMEMO is enabled or not between gajim restarts """
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
def activate(self, jid):
q = """INSERT OR REPLACE INTO encryption_state (jid, encryption)
VALUES (?, 1) """
c = self.dbConn.cursor()
c.execute(q, (jid, ))
self.dbConn.commit()
def deactivate(self, jid):
q = """INSERT OR REPLACE INTO encryption_state (jid, encryption)
VALUES (?, 0)"""
c = self.dbConn.cursor()
c.execute(q, (jid, ))
self.dbConn.commit()
def is_active(self, jid):
q = 'SELECT encryption FROM encryption_state where jid = ?;'
c = self.dbConn.cursor()
c.execute(q, (jid, ))
result = c.fetchone()
if result is None:
return False
return result[0]
def exist(self, jid):
q = 'SELECT encryption FROM encryption_state where jid = ?;'
c = self.dbConn.cursor()
c.execute(q, (jid, ))
result = c.fetchone()
if result is None:
return False
else:
return True
# -*- coding: utf-8 -*-
#
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
# Copyright 2015 Daniel Gultsch <daniel@cgultsch.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/>.
#
class EncryptionState():
""" Used to store if OMEMO is enabled or not between gajim restarts """
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
def activate(self, jid):
q = """INSERT OR REPLACE INTO encryption_state (jid, encryption)
VALUES (?, 1) """
c = self.dbConn.cursor()
c.execute(q, (jid, ))
self.dbConn.commit()
def deactivate(self, jid):
q = """INSERT OR REPLACE INTO encryption_state (jid, encryption)
VALUES (?, 0)"""
c = self.dbConn.cursor()
c.execute(q, (jid, ))
self.dbConn.commit()
def is_active(self, jid):
q = 'SELECT encryption FROM encryption_state where jid = ?;'
c = self.dbConn.cursor()
c.execute(q, (jid, ))
result = c.fetchone()
if result is None:
return False
return result[0]
def exist(self, jid):
q = 'SELECT encryption FROM encryption_state where jid = ?;'
c = self.dbConn.cursor()
c.execute(q, (jid, ))
result = c.fetchone()
if result is None:
return False
else:
return True

View File

@@ -1,186 +1,186 @@
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
import logging
from axolotl.state.axolotlstore import AxolotlStore
from axolotl.util.keyhelper import KeyHelper
from .liteidentitykeystore import LiteIdentityKeyStore
from .liteprekeystore import LitePreKeyStore
from .litesessionstore import LiteSessionStore
from .litesignedprekeystore import LiteSignedPreKeyStore
from .encryption import EncryptionState
from .sql import SQLDatabase
log = logging.getLogger('gajim.plugin_system.omemo')
DEFAULT_PREKEY_AMOUNT = 100
MIN_PREKEY_AMOUNT = 80
SPK_ARCHIVE_TIME = 86400 * 15 # 15 Days
SPK_CYCLE_TIME = 86400 # 24 Hours
class LiteAxolotlStore(AxolotlStore):
def __init__(self, connection):
try:
connection.text_factory = bytes
except(AttributeError):
raise AssertionError('Expected a sqlite3.Connection got ' +
str(connection))
self.sql = SQLDatabase(connection)
self.identityKeyStore = LiteIdentityKeyStore(connection)
self.preKeyStore = LitePreKeyStore(connection)
self.signedPreKeyStore = LiteSignedPreKeyStore(connection)
self.sessionStore = LiteSessionStore(connection)
self.encryptionStore = EncryptionState(connection)
if not self.getLocalRegistrationId():
log.info("Generating Axolotl keys")
self._generate_axolotl_keys()
def _generate_axolotl_keys(self):
identityKeyPair = KeyHelper.generateIdentityKeyPair()
registrationId = KeyHelper.generateRegistrationId()
preKeys = KeyHelper.generatePreKeys(KeyHelper.getRandomSequence(),
DEFAULT_PREKEY_AMOUNT)
self.storeLocalData(registrationId, identityKeyPair)
signedPreKey = KeyHelper.generateSignedPreKey(
identityKeyPair, KeyHelper.getRandomSequence(65536))
self.storeSignedPreKey(signedPreKey.getId(), signedPreKey)
for preKey in preKeys:
self.storePreKey(preKey.getId(), preKey)
def getIdentityKeyPair(self):
return self.identityKeyStore.getIdentityKeyPair()
def storeLocalData(self, registrationId, identityKeyPair):
self.identityKeyStore.storeLocalData(registrationId, identityKeyPair)
def getLocalRegistrationId(self):
return self.identityKeyStore.getLocalRegistrationId()
def saveIdentity(self, recepientId, identityKey):
self.identityKeyStore.saveIdentity(recepientId, identityKey)
def deleteIdentity(self, recipientId, identityKey):
self.identityKeyStore.deleteIdentity(recipientId, identityKey)
def isTrustedIdentity(self, recepientId, identityKey):
return self.identityKeyStore.isTrustedIdentity(recepientId,
identityKey)
def setTrust(self, identityKey, trust):
return self.identityKeyStore.setTrust(identityKey, trust)
def getTrustedFingerprints(self, jid):
return self.identityKeyStore.getTrustedFingerprints(jid)
def getUndecidedFingerprints(self, jid):
return self.identityKeyStore.getUndecidedFingerprints(jid)
def setShownFingerprints(self, jid):
return self.identityKeyStore.setShownFingerprints(jid)
def getNewFingerprints(self, jid):
return self.identityKeyStore.getNewFingerprints(jid)
def loadPreKey(self, preKeyId):
return self.preKeyStore.loadPreKey(preKeyId)
def loadPreKeys(self):
return self.preKeyStore.loadPendingPreKeys()
def storePreKey(self, preKeyId, preKeyRecord):
self.preKeyStore.storePreKey(preKeyId, preKeyRecord)
def containsPreKey(self, preKeyId):
return self.preKeyStore.containsPreKey(preKeyId)
def removePreKey(self, preKeyId):
self.preKeyStore.removePreKey(preKeyId)
def loadSession(self, recepientId, deviceId):
return self.sessionStore.loadSession(recepientId, deviceId)
def getActiveDeviceTuples(self):
return self.sessionStore.getActiveDeviceTuples()
def getInactiveSessionsKeys(self, recipientId):
return self.sessionStore.getInactiveSessionsKeys(recipientId)
def getSubDeviceSessions(self, recepientId):
# TODO Reuse this
return self.sessionStore.getSubDeviceSessions(recepientId)
def getJidFromDevice(self, device_id):
return self.sessionStore.getJidFromDevice(device_id)
def storeSession(self, recepientId, deviceId, sessionRecord):
self.sessionStore.storeSession(recepientId, deviceId, sessionRecord)
def containsSession(self, recepientId, deviceId):
return self.sessionStore.containsSession(recepientId, deviceId)
def deleteSession(self, recepientId, deviceId):
self.sessionStore.deleteSession(recepientId, deviceId)
def deleteAllSessions(self, recepientId):
self.sessionStore.deleteAllSessions(recepientId)
def getSessionsFromJid(self, recipientId):
return self.sessionStore.getSessionsFromJid(recipientId)
def getSessionsFromJids(self, recipientId):
return self.sessionStore.getSessionsFromJids(recipientId)
def getAllSessions(self):
return self.sessionStore.getAllSessions()
def loadSignedPreKey(self, signedPreKeyId):
return self.signedPreKeyStore.loadSignedPreKey(signedPreKeyId)
def loadSignedPreKeys(self):
return self.signedPreKeyStore.loadSignedPreKeys()
def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord):
self.signedPreKeyStore.storeSignedPreKey(signedPreKeyId,
signedPreKeyRecord)
def containsSignedPreKey(self, signedPreKeyId):
return self.signedPreKeyStore.containsSignedPreKey(signedPreKeyId)
def removeSignedPreKey(self, signedPreKeyId):
self.signedPreKeyStore.removeSignedPreKey(signedPreKeyId)
def getNextSignedPreKeyId(self):
return self.signedPreKeyStore.getNextSignedPreKeyId()
def getCurrentSignedPreKeyId(self):
return self.signedPreKeyStore.getCurrentSignedPreKeyId()
def getSignedPreKeyTimestamp(self, signedPreKeyId):
return self.signedPreKeyStore.getSignedPreKeyTimestamp(signedPreKeyId)
def removeOldSignedPreKeys(self, timestamp):
self.signedPreKeyStore.removeOldSignedPreKeys(timestamp)
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
import logging
from axolotl.state.axolotlstore import AxolotlStore
from axolotl.util.keyhelper import KeyHelper
from .liteidentitykeystore import LiteIdentityKeyStore
from .liteprekeystore import LitePreKeyStore
from .litesessionstore import LiteSessionStore
from .litesignedprekeystore import LiteSignedPreKeyStore
from .encryption import EncryptionState
from .sql import SQLDatabase
log = logging.getLogger('gajim.plugin_system.omemo')
DEFAULT_PREKEY_AMOUNT = 100
MIN_PREKEY_AMOUNT = 80
SPK_ARCHIVE_TIME = 86400 * 15 # 15 Days
SPK_CYCLE_TIME = 86400 # 24 Hours
class LiteAxolotlStore(AxolotlStore):
def __init__(self, connection):
try:
connection.text_factory = bytes
except(AttributeError):
raise AssertionError('Expected a sqlite3.Connection got ' +
str(connection))
self.sql = SQLDatabase(connection)
self.identityKeyStore = LiteIdentityKeyStore(connection)
self.preKeyStore = LitePreKeyStore(connection)
self.signedPreKeyStore = LiteSignedPreKeyStore(connection)
self.sessionStore = LiteSessionStore(connection)
self.encryptionStore = EncryptionState(connection)
if not self.getLocalRegistrationId():
log.info("Generating Axolotl keys")
self._generate_axolotl_keys()
def _generate_axolotl_keys(self):
identityKeyPair = KeyHelper.generateIdentityKeyPair()
registrationId = KeyHelper.generateRegistrationId()
preKeys = KeyHelper.generatePreKeys(KeyHelper.getRandomSequence(),
DEFAULT_PREKEY_AMOUNT)
self.storeLocalData(registrationId, identityKeyPair)
signedPreKey = KeyHelper.generateSignedPreKey(
identityKeyPair, KeyHelper.getRandomSequence(65536))
self.storeSignedPreKey(signedPreKey.getId(), signedPreKey)
for preKey in preKeys:
self.storePreKey(preKey.getId(), preKey)
def getIdentityKeyPair(self):
return self.identityKeyStore.getIdentityKeyPair()
def storeLocalData(self, registrationId, identityKeyPair):
self.identityKeyStore.storeLocalData(registrationId, identityKeyPair)
def getLocalRegistrationId(self):
return self.identityKeyStore.getLocalRegistrationId()
def saveIdentity(self, recepientId, identityKey):
self.identityKeyStore.saveIdentity(recepientId, identityKey)
def deleteIdentity(self, recipientId, identityKey):
self.identityKeyStore.deleteIdentity(recipientId, identityKey)
def isTrustedIdentity(self, recepientId, identityKey):
return self.identityKeyStore.isTrustedIdentity(recepientId,
identityKey)
def setTrust(self, identityKey, trust):
return self.identityKeyStore.setTrust(identityKey, trust)
def getTrustedFingerprints(self, jid):
return self.identityKeyStore.getTrustedFingerprints(jid)
def getUndecidedFingerprints(self, jid):
return self.identityKeyStore.getUndecidedFingerprints(jid)
def setShownFingerprints(self, jid):
return self.identityKeyStore.setShownFingerprints(jid)
def getNewFingerprints(self, jid):
return self.identityKeyStore.getNewFingerprints(jid)
def loadPreKey(self, preKeyId):
return self.preKeyStore.loadPreKey(preKeyId)
def loadPreKeys(self):
return self.preKeyStore.loadPendingPreKeys()
def storePreKey(self, preKeyId, preKeyRecord):
self.preKeyStore.storePreKey(preKeyId, preKeyRecord)
def containsPreKey(self, preKeyId):
return self.preKeyStore.containsPreKey(preKeyId)
def removePreKey(self, preKeyId):
self.preKeyStore.removePreKey(preKeyId)
def loadSession(self, recepientId, deviceId):
return self.sessionStore.loadSession(recepientId, deviceId)
def getActiveDeviceTuples(self):
return self.sessionStore.getActiveDeviceTuples()
def getInactiveSessionsKeys(self, recipientId):
return self.sessionStore.getInactiveSessionsKeys(recipientId)
def getSubDeviceSessions(self, recepientId):
# TODO Reuse this
return self.sessionStore.getSubDeviceSessions(recepientId)
def getJidFromDevice(self, device_id):
return self.sessionStore.getJidFromDevice(device_id)
def storeSession(self, recepientId, deviceId, sessionRecord):
self.sessionStore.storeSession(recepientId, deviceId, sessionRecord)
def containsSession(self, recepientId, deviceId):
return self.sessionStore.containsSession(recepientId, deviceId)
def deleteSession(self, recepientId, deviceId):
self.sessionStore.deleteSession(recepientId, deviceId)
def deleteAllSessions(self, recepientId):
self.sessionStore.deleteAllSessions(recepientId)
def getSessionsFromJid(self, recipientId):
return self.sessionStore.getSessionsFromJid(recipientId)
def getSessionsFromJids(self, recipientId):
return self.sessionStore.getSessionsFromJids(recipientId)
def getAllSessions(self):
return self.sessionStore.getAllSessions()
def loadSignedPreKey(self, signedPreKeyId):
return self.signedPreKeyStore.loadSignedPreKey(signedPreKeyId)
def loadSignedPreKeys(self):
return self.signedPreKeyStore.loadSignedPreKeys()
def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord):
self.signedPreKeyStore.storeSignedPreKey(signedPreKeyId,
signedPreKeyRecord)
def containsSignedPreKey(self, signedPreKeyId):
return self.signedPreKeyStore.containsSignedPreKey(signedPreKeyId)
def removeSignedPreKey(self, signedPreKeyId):
self.signedPreKeyStore.removeSignedPreKey(signedPreKeyId)
def getNextSignedPreKeyId(self):
return self.signedPreKeyStore.getNextSignedPreKeyId()
def getCurrentSignedPreKeyId(self):
return self.signedPreKeyStore.getCurrentSignedPreKeyId()
def getSignedPreKeyTimestamp(self, signedPreKeyId):
return self.signedPreKeyStore.getSignedPreKeyTimestamp(signedPreKeyId)
def removeOldSignedPreKeys(self, timestamp):
self.signedPreKeyStore.removeOldSignedPreKeys(timestamp)

View File

@@ -1,174 +1,174 @@
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
from axolotl.ecc.djbec import DjbECPrivateKey, DjbECPublicKey
from axolotl.identitykey import IdentityKey
from axolotl.identitykeypair import IdentityKeyPair
from axolotl.state.identitykeystore import IdentityKeyStore
UNDECIDED = 2
TRUSTED = 1
UNTRUSTED = 0
class LiteIdentityKeyStore(IdentityKeyStore):
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
def getIdentityKeyPair(self):
q = "SELECT public_key, private_key FROM identities " + \
"WHERE recipient_id = -1"
c = self.dbConn.cursor()
c.execute(q)
result = c.fetchone()
publicKey, privateKey = result
return IdentityKeyPair(
IdentityKey(DjbECPublicKey(publicKey[1:])),
DjbECPrivateKey(privateKey))
def getLocalRegistrationId(self):
q = "SELECT registration_id FROM identities WHERE recipient_id = -1"
c = self.dbConn.cursor()
c.execute(q)
result = c.fetchone()
return result[0] if result else None
def storeLocalData(self, registrationId, identityKeyPair):
q = "INSERT INTO identities( " + \
"recipient_id, registration_id, public_key, private_key) " + \
"VALUES(-1, ?, ?, ?)"
c = self.dbConn.cursor()
c.execute(q,
(registrationId,
identityKeyPair.getPublicKey().getPublicKey().serialize(),
identityKeyPair.getPrivateKey().serialize()))
self.dbConn.commit()
def saveIdentity(self, recipientId, identityKey):
q = "INSERT INTO identities (recipient_id, public_key, trust) " \
"VALUES(?, ?, ?)"
c = self.dbConn.cursor()
if not self.getIdentity(recipientId, identityKey):
c.execute(q, (recipientId,
identityKey.getPublicKey().serialize(),
UNDECIDED))
self.dbConn.commit()
def getIdentity(self, recipientId, identityKey):
q = "SELECT * FROM identities WHERE recipient_id = ? " \
"AND public_key = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId, identityKey.getPublicKey().serialize()))
result = c.fetchone()
return result is not None
def deleteIdentity(self, recipientId, identityKey):
q = "DELETE FROM identities WHERE recipient_id = ? AND public_key = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId,
identityKey.getPublicKey().serialize()))
self.dbConn.commit()
def isTrustedIdentity(self, recipientId, identityKey):
q = "SELECT trust FROM identities WHERE recipient_id = ? " \
"AND public_key = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId, identityKey.getPublicKey().serialize()))
result = c.fetchone()
states = [UNTRUSTED, TRUSTED, UNDECIDED]
if result and result[0] in states:
return result[0]
else:
return True
def getAllFingerprints(self):
q = "SELECT _id, recipient_id, public_key, trust FROM identities " \
"WHERE recipient_id != -1 ORDER BY recipient_id ASC"
c = self.dbConn.cursor()
result = []
for row in c.execute(q):
result.append((row[0], row[1], row[2], row[3]))
return result
def getFingerprints(self, jid):
q = "SELECT _id, recipient_id, public_key, trust FROM identities " \
"WHERE recipient_id =? ORDER BY trust ASC"
c = self.dbConn.cursor()
result = []
c.execute(q, (jid,))
rows = c.fetchall()
for row in rows:
result.append((row[0], row[1], row[2], row[3]))
return result
def getTrustedFingerprints(self, jid):
q = "SELECT public_key FROM identities WHERE recipient_id = ? AND trust = ?"
c = self.dbConn.cursor()
result = []
c.execute(q, (jid, TRUSTED))
rows = c.fetchall()
for row in rows:
result.append(row[0])
return result
def getUndecidedFingerprints(self, jid):
q = "SELECT trust FROM identities WHERE recipient_id = ? AND trust = ?"
c = self.dbConn.cursor()
result = []
c.execute(q, (jid, UNDECIDED))
result = c.fetchall()
return result
def getNewFingerprints(self, jid):
q = "SELECT _id FROM identities WHERE shown = 0 AND " \
"recipient_id = ?"
c = self.dbConn.cursor()
result = []
for row in c.execute(q, (jid,)):
result.append(row[0])
return result
def setShownFingerprints(self, fingerprints):
q = "UPDATE identities SET shown = 1 WHERE _id IN ({})" \
.format(', '.join(['?'] * len(fingerprints)))
c = self.dbConn.cursor()
c.execute(q, fingerprints)
self.dbConn.commit()
def setTrust(self, identityKey, trust):
q = "UPDATE identities SET trust = ? WHERE public_key = ?"
c = self.dbConn.cursor()
c.execute(q, (trust, identityKey.getPublicKey().serialize()))
self.dbConn.commit()
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
from axolotl.ecc.djbec import DjbECPrivateKey, DjbECPublicKey
from axolotl.identitykey import IdentityKey
from axolotl.identitykeypair import IdentityKeyPair
from axolotl.state.identitykeystore import IdentityKeyStore
UNDECIDED = 2
TRUSTED = 1
UNTRUSTED = 0
class LiteIdentityKeyStore(IdentityKeyStore):
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
def getIdentityKeyPair(self):
q = "SELECT public_key, private_key FROM identities " + \
"WHERE recipient_id = -1"
c = self.dbConn.cursor()
c.execute(q)
result = c.fetchone()
publicKey, privateKey = result
return IdentityKeyPair(
IdentityKey(DjbECPublicKey(publicKey[1:])),
DjbECPrivateKey(privateKey))
def getLocalRegistrationId(self):
q = "SELECT registration_id FROM identities WHERE recipient_id = -1"
c = self.dbConn.cursor()
c.execute(q)
result = c.fetchone()
return result[0] if result else None
def storeLocalData(self, registrationId, identityKeyPair):
q = "INSERT INTO identities( " + \
"recipient_id, registration_id, public_key, private_key) " + \
"VALUES(-1, ?, ?, ?)"
c = self.dbConn.cursor()
c.execute(q,
(registrationId,
identityKeyPair.getPublicKey().getPublicKey().serialize(),
identityKeyPair.getPrivateKey().serialize()))
self.dbConn.commit()
def saveIdentity(self, recipientId, identityKey):
q = "INSERT INTO identities (recipient_id, public_key, trust) " \
"VALUES(?, ?, ?)"
c = self.dbConn.cursor()
if not self.getIdentity(recipientId, identityKey):
c.execute(q, (recipientId,
identityKey.getPublicKey().serialize(),
UNDECIDED))
self.dbConn.commit()
def getIdentity(self, recipientId, identityKey):
q = "SELECT * FROM identities WHERE recipient_id = ? " \
"AND public_key = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId, identityKey.getPublicKey().serialize()))
result = c.fetchone()
return result is not None
def deleteIdentity(self, recipientId, identityKey):
q = "DELETE FROM identities WHERE recipient_id = ? AND public_key = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId,
identityKey.getPublicKey().serialize()))
self.dbConn.commit()
def isTrustedIdentity(self, recipientId, identityKey):
q = "SELECT trust FROM identities WHERE recipient_id = ? " \
"AND public_key = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId, identityKey.getPublicKey().serialize()))
result = c.fetchone()
states = [UNTRUSTED, TRUSTED, UNDECIDED]
if result and result[0] in states:
return result[0]
else:
return True
def getAllFingerprints(self):
q = "SELECT _id, recipient_id, public_key, trust FROM identities " \
"WHERE recipient_id != -1 ORDER BY recipient_id ASC"
c = self.dbConn.cursor()
result = []
for row in c.execute(q):
result.append((row[0], row[1], row[2], row[3]))
return result
def getFingerprints(self, jid):
q = "SELECT _id, recipient_id, public_key, trust FROM identities " \
"WHERE recipient_id =? ORDER BY trust ASC"
c = self.dbConn.cursor()
result = []
c.execute(q, (jid,))
rows = c.fetchall()
for row in rows:
result.append((row[0], row[1], row[2], row[3]))
return result
def getTrustedFingerprints(self, jid):
q = "SELECT public_key FROM identities WHERE recipient_id = ? AND trust = ?"
c = self.dbConn.cursor()
result = []
c.execute(q, (jid, TRUSTED))
rows = c.fetchall()
for row in rows:
result.append(row[0])
return result
def getUndecidedFingerprints(self, jid):
q = "SELECT trust FROM identities WHERE recipient_id = ? AND trust = ?"
c = self.dbConn.cursor()
result = []
c.execute(q, (jid, UNDECIDED))
result = c.fetchall()
return result
def getNewFingerprints(self, jid):
q = "SELECT _id FROM identities WHERE shown = 0 AND " \
"recipient_id = ?"
c = self.dbConn.cursor()
result = []
for row in c.execute(q, (jid,)):
result.append(row[0])
return result
def setShownFingerprints(self, fingerprints):
q = "UPDATE identities SET shown = 1 WHERE _id IN ({})" \
.format(', '.join(['?'] * len(fingerprints)))
c = self.dbConn.cursor()
c.execute(q, fingerprints)
self.dbConn.commit()
def setTrust(self, identityKey, trust):
q = "UPDATE identities SET trust = ? WHERE public_key = ?"
c = self.dbConn.cursor()
c.execute(q, (trust, identityKey.getPublicKey().serialize()))
self.dbConn.commit()

View File

@@ -1,87 +1,87 @@
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
from axolotl.state.prekeyrecord import PreKeyRecord
from axolotl.state.prekeystore import PreKeyStore
from axolotl.util.keyhelper import KeyHelper
class LitePreKeyStore(PreKeyStore):
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
def loadPreKey(self, preKeyId):
q = "SELECT record FROM prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (preKeyId, ))
result = cursor.fetchone()
if not result:
raise Exception("No such prekeyRecord!")
return PreKeyRecord(serialized=result[0])
def loadPendingPreKeys(self):
q = "SELECT record FROM prekeys"
cursor = self.dbConn.cursor()
cursor.execute(q)
result = cursor.fetchall()
return [PreKeyRecord(serialized=r[0]) for r in result]
def storePreKey(self, preKeyId, preKeyRecord):
q = "INSERT INTO prekeys (prekey_id, record) VALUES(?,?)"
cursor = self.dbConn.cursor()
cursor.execute(q, (preKeyId, preKeyRecord.serialize()))
self.dbConn.commit()
def containsPreKey(self, preKeyId):
q = "SELECT record FROM prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (preKeyId, ))
return cursor.fetchone() is not None
def removePreKey(self, preKeyId):
q = "DELETE FROM prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (preKeyId, ))
self.dbConn.commit()
def getCurrentPreKeyId(self):
q = "SELECT MAX(prekey_id) FROM prekeys"
cursor = self.dbConn.cursor()
cursor.execute(q)
return cursor.fetchone()[0]
def getPreKeyCount(self):
q = "SELECT COUNT(prekey_id) FROM prekeys"
cursor = self.dbConn.cursor()
cursor.execute(q)
return cursor.fetchone()[0]
def generateNewPreKeys(self, count):
startId = self.getCurrentPreKeyId() + 1
preKeys = KeyHelper.generatePreKeys(startId, count)
for preKey in preKeys:
self.storePreKey(preKey.getId(), preKey)
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
from axolotl.state.prekeyrecord import PreKeyRecord
from axolotl.state.prekeystore import PreKeyStore
from axolotl.util.keyhelper import KeyHelper
class LitePreKeyStore(PreKeyStore):
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
def loadPreKey(self, preKeyId):
q = "SELECT record FROM prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (preKeyId, ))
result = cursor.fetchone()
if not result:
raise Exception("No such prekeyRecord!")
return PreKeyRecord(serialized=result[0])
def loadPendingPreKeys(self):
q = "SELECT record FROM prekeys"
cursor = self.dbConn.cursor()
cursor.execute(q)
result = cursor.fetchall()
return [PreKeyRecord(serialized=r[0]) for r in result]
def storePreKey(self, preKeyId, preKeyRecord):
q = "INSERT INTO prekeys (prekey_id, record) VALUES(?,?)"
cursor = self.dbConn.cursor()
cursor.execute(q, (preKeyId, preKeyRecord.serialize()))
self.dbConn.commit()
def containsPreKey(self, preKeyId):
q = "SELECT record FROM prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (preKeyId, ))
return cursor.fetchone() is not None
def removePreKey(self, preKeyId):
q = "DELETE FROM prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (preKeyId, ))
self.dbConn.commit()
def getCurrentPreKeyId(self):
q = "SELECT MAX(prekey_id) FROM prekeys"
cursor = self.dbConn.cursor()
cursor.execute(q)
return cursor.fetchone()[0]
def getPreKeyCount(self):
q = "SELECT COUNT(prekey_id) FROM prekeys"
cursor = self.dbConn.cursor()
cursor.execute(q)
return cursor.fetchone()[0]
def generateNewPreKeys(self, count):
startId = self.getCurrentPreKeyId() + 1
preKeys = KeyHelper.generatePreKeys(startId, count)
for preKey in preKeys:
self.storePreKey(preKey.getId(), preKey)

View File

@@ -1,143 +1,143 @@
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
from axolotl.state.sessionrecord import SessionRecord
from axolotl.state.sessionstore import SessionStore
class LiteSessionStore(SessionStore):
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
def loadSession(self, recipientId, deviceId):
q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId, deviceId))
result = c.fetchone()
if result:
return SessionRecord(serialized=result[0])
else:
return SessionRecord()
def getSubDeviceSessions(self, recipientId):
q = "SELECT device_id from sessions WHERE recipient_id = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId, ))
result = c.fetchall()
deviceIds = [r[0] for r in result]
return deviceIds
def getJidFromDevice(self, device_id):
q = "SELECT recipient_id from sessions WHERE device_id = ?"
c = self.dbConn.cursor()
c.execute(q, (device_id, ))
result = c.fetchone()
return result[0].decode('utf-8') if result else None
def getActiveDeviceTuples(self):
q = "SELECT recipient_id, device_id FROM sessions WHERE active = 1"
c = self.dbConn.cursor()
result = []
for row in c.execute(q):
result.append((row[0].decode('utf-8'), row[1]))
return result
def storeSession(self, recipientId, deviceId, sessionRecord):
self.deleteSession(recipientId, deviceId)
q = "INSERT INTO sessions(recipient_id, device_id, record) VALUES(?,?,?)"
c = self.dbConn.cursor()
c.execute(q, (recipientId, deviceId, sessionRecord.serialize()))
self.dbConn.commit()
def containsSession(self, recipientId, deviceId):
q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId, deviceId))
result = c.fetchone()
return result is not None
def deleteSession(self, recipientId, deviceId):
q = "DELETE FROM sessions WHERE recipient_id = ? AND device_id = ?"
self.dbConn.cursor().execute(q, (recipientId, deviceId))
self.dbConn.commit()
def deleteAllSessions(self, recipientId):
q = "DELETE FROM sessions WHERE recipient_id = ?"
self.dbConn.cursor().execute(q, (recipientId, ))
self.dbConn.commit()
def getAllSessions(self):
q = "SELECT _id, recipient_id, device_id, record, active from sessions"
c = self.dbConn.cursor()
result = []
for row in c.execute(q):
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
return result
def getSessionsFromJid(self, recipientId):
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
" WHERE recipient_id = ?"
c = self.dbConn.cursor()
result = []
for row in c.execute(q, (recipientId,)):
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
return result
def getSessionsFromJids(self, recipientId):
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
" WHERE recipient_id IN ({})" \
.format(', '.join(['?'] * len(recipientId)))
c = self.dbConn.cursor()
result = []
for row in c.execute(q, recipientId):
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
return result
def setActiveState(self, deviceList, jid):
c = self.dbConn.cursor()
q = "UPDATE sessions SET active = {} " \
"WHERE recipient_id = '{}' AND device_id IN ({})" \
.format(1, jid, ', '.join(['?'] * len(deviceList)))
c.execute(q, deviceList)
q = "UPDATE sessions SET active = {} " \
"WHERE recipient_id = '{}' AND device_id NOT IN ({})" \
.format(0, jid, ', '.join(['?'] * len(deviceList)))
c.execute(q, deviceList)
self.dbConn.commit()
def getInactiveSessionsKeys(self, recipientId):
q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?"
c = self.dbConn.cursor()
result = []
for row in c.execute(q, (recipientId,)):
public_key = (SessionRecord(serialized=row[0]).
getSessionState().getRemoteIdentityKey().
getPublicKey())
result.append(public_key.serialize())
return result
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
from axolotl.state.sessionrecord import SessionRecord
from axolotl.state.sessionstore import SessionStore
class LiteSessionStore(SessionStore):
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
def loadSession(self, recipientId, deviceId):
q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId, deviceId))
result = c.fetchone()
if result:
return SessionRecord(serialized=result[0])
else:
return SessionRecord()
def getSubDeviceSessions(self, recipientId):
q = "SELECT device_id from sessions WHERE recipient_id = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId, ))
result = c.fetchall()
deviceIds = [r[0] for r in result]
return deviceIds
def getJidFromDevice(self, device_id):
q = "SELECT recipient_id from sessions WHERE device_id = ?"
c = self.dbConn.cursor()
c.execute(q, (device_id, ))
result = c.fetchone()
return result[0].decode('utf-8') if result else None
def getActiveDeviceTuples(self):
q = "SELECT recipient_id, device_id FROM sessions WHERE active = 1"
c = self.dbConn.cursor()
result = []
for row in c.execute(q):
result.append((row[0].decode('utf-8'), row[1]))
return result
def storeSession(self, recipientId, deviceId, sessionRecord):
self.deleteSession(recipientId, deviceId)
q = "INSERT INTO sessions(recipient_id, device_id, record) VALUES(?,?,?)"
c = self.dbConn.cursor()
c.execute(q, (recipientId, deviceId, sessionRecord.serialize()))
self.dbConn.commit()
def containsSession(self, recipientId, deviceId):
q = "SELECT record FROM sessions WHERE recipient_id = ? AND device_id = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId, deviceId))
result = c.fetchone()
return result is not None
def deleteSession(self, recipientId, deviceId):
q = "DELETE FROM sessions WHERE recipient_id = ? AND device_id = ?"
self.dbConn.cursor().execute(q, (recipientId, deviceId))
self.dbConn.commit()
def deleteAllSessions(self, recipientId):
q = "DELETE FROM sessions WHERE recipient_id = ?"
self.dbConn.cursor().execute(q, (recipientId, ))
self.dbConn.commit()
def getAllSessions(self):
q = "SELECT _id, recipient_id, device_id, record, active from sessions"
c = self.dbConn.cursor()
result = []
for row in c.execute(q):
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
return result
def getSessionsFromJid(self, recipientId):
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
" WHERE recipient_id = ?"
c = self.dbConn.cursor()
result = []
for row in c.execute(q, (recipientId,)):
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
return result
def getSessionsFromJids(self, recipientId):
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
" WHERE recipient_id IN ({})" \
.format(', '.join(['?'] * len(recipientId)))
c = self.dbConn.cursor()
result = []
for row in c.execute(q, recipientId):
result.append((row[0], row[1].decode('utf-8'), row[2], row[3], row[4]))
return result
def setActiveState(self, deviceList, jid):
c = self.dbConn.cursor()
q = "UPDATE sessions SET active = {} " \
"WHERE recipient_id = '{}' AND device_id IN ({})" \
.format(1, jid, ', '.join(['?'] * len(deviceList)))
c.execute(q, deviceList)
q = "UPDATE sessions SET active = {} " \
"WHERE recipient_id = '{}' AND device_id NOT IN ({})" \
.format(0, jid, ', '.join(['?'] * len(deviceList)))
c.execute(q, deviceList)
self.dbConn.commit()
def getInactiveSessionsKeys(self, recipientId):
q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?"
c = self.dbConn.cursor()
result = []
for row in c.execute(q, (recipientId,)):
public_key = (SessionRecord(serialized=row[0]).
getSessionState().getRemoteIdentityKey().
getPublicKey())
result.append(public_key.serialize())
return result

View File

@@ -1,113 +1,113 @@
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
from axolotl.invalidkeyidexception import InvalidKeyIdException
from axolotl.state.signedprekeyrecord import SignedPreKeyRecord
from axolotl.state.signedprekeystore import SignedPreKeyStore
from axolotl.util.medium import Medium
class LiteSignedPreKeyStore(SignedPreKeyStore):
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
def loadSignedPreKey(self, signedPreKeyId):
q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (signedPreKeyId, ))
result = cursor.fetchone()
if not result:
raise InvalidKeyIdException("No such signedprekeyrecord! %s " %
signedPreKeyId)
return SignedPreKeyRecord(serialized=result[0])
def loadSignedPreKeys(self):
q = "SELECT record FROM signed_prekeys"
cursor = self.dbConn.cursor()
cursor.execute(q, )
result = cursor.fetchall()
results = []
for row in result:
results.append(SignedPreKeyRecord(serialized=row[0]))
return results
def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord):
q = "INSERT INTO signed_prekeys (prekey_id, record) VALUES(?,?)"
cursor = self.dbConn.cursor()
cursor.execute(q, (signedPreKeyId, signedPreKeyRecord.serialize()))
self.dbConn.commit()
def containsSignedPreKey(self, signedPreKeyId):
q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (signedPreKeyId, ))
return cursor.fetchone() is not None
def removeSignedPreKey(self, signedPreKeyId):
q = "DELETE FROM signed_prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (signedPreKeyId, ))
self.dbConn.commit()
def getNextSignedPreKeyId(self):
result = self.getCurrentSignedPreKeyId()
if not result:
return 1 # StartId if no SignedPreKeys exist
else:
return (result % (Medium.MAX_VALUE - 1)) + 1
def getCurrentSignedPreKeyId(self):
q = "SELECT MAX(prekey_id) FROM signed_prekeys"
cursor = self.dbConn.cursor()
cursor.execute(q)
result = cursor.fetchone()
if not result:
return None
else:
return result[0]
def getSignedPreKeyTimestamp(self, signedPreKeyId):
q = "SELECT strftime('%s', timestamp) FROM " \
"signed_prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (signedPreKeyId, ))
result = cursor.fetchone()
if not result:
raise InvalidKeyIdException("No such signedprekeyrecord! %s " %
signedPreKeyId)
return result[0]
def removeOldSignedPreKeys(self, timestamp):
q = "DELETE FROM signed_prekeys " \
"WHERE timestamp < datetime(?, 'unixepoch')"
cursor = self.dbConn.cursor()
cursor.execute(q, (timestamp, ))
self.dbConn.commit()
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
from axolotl.invalidkeyidexception import InvalidKeyIdException
from axolotl.state.signedprekeyrecord import SignedPreKeyRecord
from axolotl.state.signedprekeystore import SignedPreKeyStore
from axolotl.util.medium import Medium
class LiteSignedPreKeyStore(SignedPreKeyStore):
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
def loadSignedPreKey(self, signedPreKeyId):
q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (signedPreKeyId, ))
result = cursor.fetchone()
if not result:
raise InvalidKeyIdException("No such signedprekeyrecord! %s " %
signedPreKeyId)
return SignedPreKeyRecord(serialized=result[0])
def loadSignedPreKeys(self):
q = "SELECT record FROM signed_prekeys"
cursor = self.dbConn.cursor()
cursor.execute(q, )
result = cursor.fetchall()
results = []
for row in result:
results.append(SignedPreKeyRecord(serialized=row[0]))
return results
def storeSignedPreKey(self, signedPreKeyId, signedPreKeyRecord):
q = "INSERT INTO signed_prekeys (prekey_id, record) VALUES(?,?)"
cursor = self.dbConn.cursor()
cursor.execute(q, (signedPreKeyId, signedPreKeyRecord.serialize()))
self.dbConn.commit()
def containsSignedPreKey(self, signedPreKeyId):
q = "SELECT record FROM signed_prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (signedPreKeyId, ))
return cursor.fetchone() is not None
def removeSignedPreKey(self, signedPreKeyId):
q = "DELETE FROM signed_prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (signedPreKeyId, ))
self.dbConn.commit()
def getNextSignedPreKeyId(self):
result = self.getCurrentSignedPreKeyId()
if not result:
return 1 # StartId if no SignedPreKeys exist
else:
return (result % (Medium.MAX_VALUE - 1)) + 1
def getCurrentSignedPreKeyId(self):
q = "SELECT MAX(prekey_id) FROM signed_prekeys"
cursor = self.dbConn.cursor()
cursor.execute(q)
result = cursor.fetchone()
if not result:
return None
else:
return result[0]
def getSignedPreKeyTimestamp(self, signedPreKeyId):
q = "SELECT strftime('%s', timestamp) FROM " \
"signed_prekeys WHERE prekey_id = ?"
cursor = self.dbConn.cursor()
cursor.execute(q, (signedPreKeyId, ))
result = cursor.fetchone()
if not result:
raise InvalidKeyIdException("No such signedprekeyrecord! %s " %
signedPreKeyId)
return result[0]
def removeOldSignedPreKeys(self, timestamp):
q = "DELETE FROM signed_prekeys " \
"WHERE timestamp < datetime(?, 'unixepoch')"
cursor = self.dbConn.cursor()
cursor.execute(q, (timestamp, ))
self.dbConn.commit()

View File

@@ -1,154 +1,154 @@
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
from .db_helpers import user_version
class SQLDatabase():
""" SQL Database """
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
self.createDb()
self.migrateDb()
c = self.dbConn.cursor()
c.execute("PRAGMA synchronous=NORMAL;")
c.execute("PRAGMA journal_mode;")
mode = c.fetchone()[0]
# WAL is a persistent DB mode, don't override it if user has set it
if mode != 'wal':
c.execute("PRAGMA journal_mode=MEMORY;")
self.dbConn.commit()
def createDb(self):
if user_version(self.dbConn) == 0:
# Creates
# IdentityKeyStore
# PreKeyStore
# SignedPreKeyStore
# SessionStore
# EncryptionStore
create_tables = '''
CREATE TABLE IF NOT EXISTS identities (
_id INTEGER PRIMARY KEY AUTOINCREMENT, recipient_id TEXT,
registration_id INTEGER, public_key BLOB, private_key BLOB,
next_prekey_id INTEGER, timestamp INTEGER, trust INTEGER,
shown INTEGER DEFAULT 0);
CREATE UNIQUE INDEX IF NOT EXISTS
public_key_index ON identities (public_key, recipient_id);
CREATE TABLE IF NOT EXISTS prekeys(
_id INTEGER PRIMARY KEY AUTOINCREMENT,
prekey_id INTEGER UNIQUE, sent_to_server BOOLEAN,
record BLOB);
CREATE TABLE IF NOT EXISTS signed_prekeys (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
prekey_id INTEGER UNIQUE,
timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB);
CREATE TABLE IF NOT EXISTS sessions (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
recipient_id TEXT, device_id INTEGER,
record BLOB, timestamp INTEGER, active INTEGER DEFAULT 1,
UNIQUE(recipient_id, device_id));
CREATE TABLE IF NOT EXISTS encryption_state (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jid TEXT UNIQUE,
encryption INTEGER
);
'''
create_db_sql = """
BEGIN TRANSACTION;
%s
PRAGMA user_version=5;
END TRANSACTION;
""" % (create_tables)
self.dbConn.executescript(create_db_sql)
def migrateDb(self):
""" Migrates the DB
"""
# Find all double entries and delete them
if user_version(self.dbConn) < 2:
delete_dupes = """ DELETE FROM identities WHERE _id not in (
SELECT MIN(_id)
FROM identities
GROUP BY
recipient_id, public_key
);
"""
self.dbConn.executescript(""" BEGIN TRANSACTION;
%s
PRAGMA user_version=2;
END TRANSACTION;
""" % (delete_dupes))
if user_version(self.dbConn) < 3:
# Create a UNIQUE INDEX so every public key/recipient_id tuple
# can only be once in the db
add_index = """ CREATE UNIQUE INDEX IF NOT EXISTS
public_key_index
ON identities (public_key, recipient_id);
"""
self.dbConn.executescript(""" BEGIN TRANSACTION;
%s
PRAGMA user_version=3;
END TRANSACTION;
""" % (add_index))
if user_version(self.dbConn) < 4:
# Adds column "active" to the sessions table
add_active = """ ALTER TABLE sessions
ADD COLUMN active INTEGER DEFAULT 1;
"""
self.dbConn.executescript(""" BEGIN TRANSACTION;
%s
PRAGMA user_version=4;
END TRANSACTION;
""" % (add_active))
if user_version(self.dbConn) < 5:
# Adds DEFAULT Timestamp
add_timestamp = """
DROP TABLE signed_prekeys;
CREATE TABLE IF NOT EXISTS signed_prekeys (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
prekey_id INTEGER UNIQUE,
timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB);
ALTER TABLE identities ADD COLUMN shown INTEGER DEFAULT 0;
UPDATE identities SET shown = 1;
"""
self.dbConn.executescript(""" BEGIN TRANSACTION;
%s
PRAGMA user_version=5;
END TRANSACTION;
""" % (add_timestamp))
# -*- coding: utf-8 -*-
#
# Copyright 2015 Tarek Galal <tare2.galal@gmail.com>
#
# 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/>.
#
from .db_helpers import user_version
class SQLDatabase():
""" SQL Database """
def __init__(self, dbConn):
"""
:type dbConn: Connection
"""
self.dbConn = dbConn
self.createDb()
self.migrateDb()
c = self.dbConn.cursor()
c.execute("PRAGMA synchronous=NORMAL;")
c.execute("PRAGMA journal_mode;")
mode = c.fetchone()[0]
# WAL is a persistent DB mode, don't override it if user has set it
if mode != 'wal':
c.execute("PRAGMA journal_mode=MEMORY;")
self.dbConn.commit()
def createDb(self):
if user_version(self.dbConn) == 0:
# Creates
# IdentityKeyStore
# PreKeyStore
# SignedPreKeyStore
# SessionStore
# EncryptionStore
create_tables = '''
CREATE TABLE IF NOT EXISTS identities (
_id INTEGER PRIMARY KEY AUTOINCREMENT, recipient_id TEXT,
registration_id INTEGER, public_key BLOB, private_key BLOB,
next_prekey_id INTEGER, timestamp INTEGER, trust INTEGER,
shown INTEGER DEFAULT 0);
CREATE UNIQUE INDEX IF NOT EXISTS
public_key_index ON identities (public_key, recipient_id);
CREATE TABLE IF NOT EXISTS prekeys(
_id INTEGER PRIMARY KEY AUTOINCREMENT,
prekey_id INTEGER UNIQUE, sent_to_server BOOLEAN,
record BLOB);
CREATE TABLE IF NOT EXISTS signed_prekeys (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
prekey_id INTEGER UNIQUE,
timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB);
CREATE TABLE IF NOT EXISTS sessions (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
recipient_id TEXT, device_id INTEGER,
record BLOB, timestamp INTEGER, active INTEGER DEFAULT 1,
UNIQUE(recipient_id, device_id));
CREATE TABLE IF NOT EXISTS encryption_state (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jid TEXT UNIQUE,
encryption INTEGER
);
'''
create_db_sql = """
BEGIN TRANSACTION;
%s
PRAGMA user_version=5;
END TRANSACTION;
""" % (create_tables)
self.dbConn.executescript(create_db_sql)
def migrateDb(self):
""" Migrates the DB
"""
# Find all double entries and delete them
if user_version(self.dbConn) < 2:
delete_dupes = """ DELETE FROM identities WHERE _id not in (
SELECT MIN(_id)
FROM identities
GROUP BY
recipient_id, public_key
);
"""
self.dbConn.executescript(""" BEGIN TRANSACTION;
%s
PRAGMA user_version=2;
END TRANSACTION;
""" % (delete_dupes))
if user_version(self.dbConn) < 3:
# Create a UNIQUE INDEX so every public key/recipient_id tuple
# can only be once in the db
add_index = """ CREATE UNIQUE INDEX IF NOT EXISTS
public_key_index
ON identities (public_key, recipient_id);
"""
self.dbConn.executescript(""" BEGIN TRANSACTION;
%s
PRAGMA user_version=3;
END TRANSACTION;
""" % (add_index))
if user_version(self.dbConn) < 4:
# Adds column "active" to the sessions table
add_active = """ ALTER TABLE sessions
ADD COLUMN active INTEGER DEFAULT 1;
"""
self.dbConn.executescript(""" BEGIN TRANSACTION;
%s
PRAGMA user_version=4;
END TRANSACTION;
""" % (add_active))
if user_version(self.dbConn) < 5:
# Adds DEFAULT Timestamp
add_timestamp = """
DROP TABLE signed_prekeys;
CREATE TABLE IF NOT EXISTS signed_prekeys (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
prekey_id INTEGER UNIQUE,
timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB);
ALTER TABLE identities ADD COLUMN shown INTEGER DEFAULT 0;
UPDATE identities SET shown = 1;
"""
self.dbConn.executescript(""" BEGIN TRANSACTION;
%s
PRAGMA user_version=5;
END TRANSACTION;
""" % (add_timestamp))

View File

@@ -19,16 +19,14 @@
import logging
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.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.nosessionexception import NoSessionException
from axolotl.protocol.prekeywhispermessage import PreKeyWhisperMessage
from axolotl.protocol.whispermessage import WhisperMessage
from axolotl.sessionbuilder import SessionBuilder
@@ -36,13 +34,12 @@ from axolotl.sessioncipher import SessionCipher
from axolotl.state.prekeybundle import PreKeyBundle
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,
MIN_PREKEY_AMOUNT, SPK_CYCLE_TIME,
SPK_ARCHIVE_TIME)
log = logging.getLogger('gajim.plugin_system.omemo')
logAxolotl = logging.getLogger('axolotl')
UNTRUSTED = 0
@@ -78,23 +75,24 @@ class OmemoState:
str(self.store.preKeyStore.getPreKeyCount()) +
' 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,
self.store, recipient_id, device_id)
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'][
1:])
identityKey = IdentityKey(DjbECPublicKey(bundle_dict['identityKey'][
1:]))
signedPreKeyPublic = DjbECPublicKey(bundle.spk['key'][1:])
identityKey = IdentityKey(DjbECPublicKey(bundle.ik[1:]))
prekey_bundle = PreKeyBundle(
registration_id, device_id, bundle_dict['preKeyId'], preKeyPublic,
bundle_dict['signedPreKeyId'], signedPreKeyPublic,
bundle_dict['signedPreKeySignature'], identityKey)
registration_id, device_id,
prekey['id'], preKeyPublic,
bundle.spk['id'], signedPreKeyPublic,
bundle.spk_signature,
identityKey)
sessionBuilder.processPreKeyBundle(prekey_bundle)
return self.get_session_cipher(recipient_id, device_id)
@@ -153,101 +151,85 @@ class OmemoState:
@property
def bundle(self):
self.checkPreKeyAmount()
prekeys = [
(k.getId(), b64encode(k.getKeyPair().getPublicKey().serialize()))
for k in self.store.loadPreKeys()
]
bundle = {'otpks': []}
for k in self.store.loadPreKeys():
key = k.getKeyPair().getPublicKey().serialize()
bundle['otpks'].append({'key': key, 'id': k.getId()})
identityKeyPair = self.store.getIdentityKeyPair()
bundle['ik'] = identityKeyPair.getPublicKey().serialize()
self.cycleSignedPreKey(identityKeyPair)
signedPreKey = self.store.loadSignedPreKey(
self.store.getCurrentSignedPreKeyId())
bundle['spk_signature'] = signedPreKey.getSignature()
bundle['spk'] = {'key': signedPreKey.getKeyPair().getPublicKey().serialize(),
'id': signedPreKey.getId()}
result = {
'signedPreKeyId': signedPreKey.getId(),
'signedPreKeyPublic':
b64encode(signedPreKey.getKeyPair().getPublicKey().serialize()),
'signedPreKeySignature': b64encode(signedPreKey.getSignature()),
'identityKey':
b64encode(identityKeyPair.getPublicKey().serialize()),
'prekeys': prekeys
}
return result
return OMEMOBundle(**bundle)
def decrypt_msg(self, msg_dict):
def decrypt_msg(self, omemo_message, jid):
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')
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')
return
iv = msg_dict['iv']
sid = msg_dict['sid']
sender_jid = msg_dict['sender_jid']
payload = msg_dict['payload']
encrypted_key, prekey = omemo_message.keys[own_id]
encrypted_key = msg_dict['keys'][own_id]
try:
key = self.handlePreKeyWhisperMessage(sender_jid, sid,
encrypted_key)
except (InvalidVersionException, InvalidMessageException):
if prekey:
try:
key = self.handleWhisperMessage(sender_jid, sid, encrypted_key)
except (NoSessionException, InvalidMessageException) as e:
log.warning(e)
log.warning('sender_jid => ' + str(sender_jid) + ' sid =>' +
str(sid))
return
except (DuplicateMessageException) as e:
log.warning('Duplicate message found ' + str(e.args))
key = self.handlePreKeyWhisperMessage(
jid, omemo_message.sid, encrypted_key)
except Exception as error:
log.warning(error)
return
except (DuplicateMessageException) as e:
log.warning('Duplicate message found ' + str(e.args))
return
else:
try:
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
log.debug("Decrypted Key Exchange Message")
else:
result = decrypt(key, iv, payload)
log.debug("Decrypted Message => " + result)
result = aes_decrypt(key, omemo_message.iv, omemo_message.payload)
log.debug("Decrypted Message => %s", result)
return result
def create_msg(self, from_jid, jid, plaintext):
key = os.urandom(16)
iv = os.urandom(16)
encrypted_keys = {}
devices_list = self.device_list_for(jid)
if len(devices_list) == 0:
if not devices_list:
log.error('No known devices')
return
payload, tag = encrypt(key, iv, plaintext)
key += tag
result = aes_encrypt(plaintext)
# Encrypt the message key with for each of receivers devices
for device in devices_list:
try:
if self.isTrusted(jid, device) == TRUSTED:
cipher = self.get_session_cipher(jid, device)
cipher_key = cipher.encrypt(key)
cipher_key = cipher.encrypt(result.key)
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
encrypted_keys[device] = (cipher_key.serialize(), prekey)
else:
log.debug('Skipped Device because Trust is: ' +
str(self.isTrusted(jid, device)))
except:
log.warning('Failed to find key for device ' + str(device))
log.debug('Skipped Device because Trust is: %s',
self.isTrusted(jid, device))
except Exception:
log.warning('Failed to find key for device: %s', device)
if len(encrypted_keys) == 0:
if not encrypted_keys:
log.error('Encrypted keys empty')
raise NoValidSessions('Encrypted keys empty')
@@ -257,36 +239,29 @@ class OmemoState:
try:
if self.isTrusted(from_jid, device) == TRUSTED:
cipher = self.get_session_cipher(from_jid, device)
cipher_key = cipher.encrypt(key)
cipher_key = cipher.encrypt(result.key)
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
encrypted_keys[device] = (cipher_key.serialize(), prekey)
else:
log.debug('Skipped own Device because Trust is: ' +
str(self.isTrusted(from_jid, device)))
except:
log.warning('Failed to find key for device ' + str(device))
result = {'sid': self.own_device_id,
'keys': encrypted_keys,
'jid': jid,
'iv': iv,
'payload': payload}
log.debug('Skipped own Device because Trust is: %s',
self.isTrusted(from_jid, device))
except Exception:
log.warning('Failed to find key for device: %s', device)
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):
key = os.urandom(16)
iv = os.urandom(16)
encrypted_keys = {}
room = jid
encrypted_jids = []
devices_list = self.device_list_for(jid, True)
payload, tag = encrypt(key, iv, plaintext)
key += tag
result = aes_encrypt(plaintext, append_tag=True)
for tup in devices_list:
self.get_session_cipher(tup[0], tup[1])
@@ -303,16 +278,15 @@ class OmemoState:
for rid, cipher in self.session_ciphers[jid_to].items():
try:
if self.isTrusted(jid_to, rid) == TRUSTED:
cipher_key = cipher.encrypt(key)
cipher_key = cipher.encrypt(result.key)
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
encrypted_keys[rid] = (cipher_key.serialize(), prekey)
else:
log.debug('Skipped Device because Trust is: ' +
str(self.isTrusted(jid_to, rid)))
except:
log.debug('Skipped Device because Trust is: %s',
self.isTrusted(jid_to, rid))
except Exception:
log.exception('ERROR:')
log.warning('Failed to find key for device ' +
str(rid))
log.warning('Failed to find key for device %s', rid)
encrypted_jids.append(jid_to)
my_other_devices = set(self.own_devices) - set({self.own_device_id})
@@ -321,28 +295,25 @@ class OmemoState:
try:
cipher = self.get_session_cipher(from_jid, dev)
if self.isTrusted(from_jid, dev) == TRUSTED:
cipher_key = cipher.encrypt(key)
cipher_key = cipher.encrypt(result.key)
prekey = isinstance(cipher_key, PreKeyWhisperMessage)
encrypted_keys[dev] = (cipher_key.serialize(), prekey)
else:
log.debug('Skipped own Device because Trust is: ' +
str(self.isTrusted(from_jid, dev)))
except:
log.debug('Skipped own Device because Trust is: %s',
self.isTrusted(from_jid, dev))
except Exception:
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:
log.error('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')
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):
""" Return a list of known device ids for the specified jid.
@@ -439,7 +410,7 @@ class OmemoState:
key = sessionCipher.decryptPkmsg(preKeyWhisperMessage)
# Publish new bundle after PreKey has been used
# for building a new Session
self.xmpp_con.publish_bundle()
self.xmpp_con.set_bundle()
self.add_device(recipient_id, device_id)
return key
except UntrustedIdentityException as e:
@@ -495,3 +466,7 @@ class OmemoState:
# Delete all SignedPreKeys that are older than SPK_ARCHIVE_TIME
timestamp = now - SPK_ARCHIVE_TIME
self.store.removeOldSignedPreKeys(timestamp)
class NoValidSessions(Exception):
pass

View File

@@ -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
# 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/>.
# 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 hashlib
@@ -32,29 +29,23 @@ from urllib.parse import urlparse, urldefrag
from io import BufferedWriter, FileIO, BytesIO
from gi.repository import GLib
from gajim.common import app
from gajim.common import configpaths
from gajim.plugins.plugins_i18n import _
from gajim.gtk.dialogs import ErrorDialog, YesNoDialog
from omemo.gtk.progress import ProgressWindow
from omemo.backend.aes import aes_decrypt_file
if os.name == 'nt':
import certifi
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')
ERROR = False
try:
if not os.path.exists(DIRECTORY):
os.makedirs(DIRECTORY)
@@ -63,15 +54,6 @@ except Exception:
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:
def __init__(self, url, account):
self.account = account
@@ -105,7 +87,7 @@ class FileDecryption:
if not self.is_encrypted(file):
log.info('Url not encrypted: %s', url)
return
print('ADASD')
self.create_paths(file)
if os.path.exists(file.filepath):
@@ -181,7 +163,10 @@ class Download:
return
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(
self.progressbar.set_text, _('Writing file to harddisk...'))
@@ -203,11 +188,11 @@ class Download:
log.warning('CERT Verification disabled')
get_request = urlopen(self.file.url, timeout=30, context=context)
else:
cafile = None
if os.name == 'nt':
get_request = urlopen(
self.file.url, cafile=certifi.where(), timeout=30)
else:
get_request = urlopen(self.file.url, timeout=30)
cafile = certifi.where()
context = ssl.create_default_context(cafile=cafile)
get_request = urlopen(self.file.url, timeout=30, context=context)
size = get_request.info()['Content-Length']
if not size:
@@ -241,17 +226,6 @@ class Download:
stream.close()
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):
log.info('Writing data to %s', self.file.filepath)
try:

View File

@@ -1,22 +1,20 @@
'''
Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
Copyright 2016 Philipp Hörist <philipp@hoerist.com>
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/>.
'''
# 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>
#
# This file is part of OMEMO Gajim Plugin.
#
# OMEMO Gajim Plugin is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# OMEMO Gajim Plugin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
import binascii
import logging
@@ -171,7 +169,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
def cleardevice_button_clicked_cb(self, button, *args):
active = self._ui.get_object('account_combobox').get_active()
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()
def refresh_button_clicked_cb(self, button, *args):

View File

@@ -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
# 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
# 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 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 binascii

View File

@@ -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
# 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
# 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 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

View File

@@ -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
# 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
# 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 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

File diff suppressed because it is too large Load Diff

View File

@@ -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'

View File

@@ -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
# 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
# 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/>.
# along with OMEMO Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
# XEP-0384: OMEMO Encryption
import logging
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')
NS_OMEMO = 'eu.siacs.conversations.axolotl'
NS_DEVICE_LIST = NS_OMEMO + '.devicelist'
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
def delete_nodes(stanza, name, namespace=None):
nodes = stanza.getTags(name, namespace=namespace)
for node in nodes:
stanza.delChild(node)

View File

@@ -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

View File

@@ -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

View File

@@ -1,24 +1,20 @@
# -*- coding: utf-8 -*-
'''
Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
Copyright 2016 Philipp Hörist <philipp@hoerist.com>
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/>.
'''
# 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>
#
# 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 logging
import binascii
@@ -36,11 +32,12 @@ from gajim.plugins import GajimPlugin
from gajim.plugins.plugins_i18n import _
from gajim.groupchat_control import GroupchatControl
from omemo import file_crypto
from omemo.gtk.key import KeyDialog
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'
PROTOBUF_MISSING = "OMEMO can't import Google Protobuf, you can find help in " \
"the GitHub Wiki"
@@ -48,12 +45,11 @@ ERROR_MSG = ''
log = logging.getLogger('gajim.plugin_system.omemo')
try:
from omemo import file_crypto
except Exception as error:
log.exception(error)
ERROR_MSG = CRYPTOGRAPHY_MISSING
if log.getEffectiveLevel() == logging.DEBUG:
log_axolotl = logging.getLogger('axolotl')
log_axolotl.setLevel(logging.DEBUG)
log_axolotl.addHandler(logging.StreamHandler())
log_axolotl.propagate = False
try:
import google.protobuf
@@ -70,14 +66,10 @@ except Exception as error:
if not ERROR_MSG:
try:
from omemo.modules import omemo
from omemo.modules import omemo_devicelist
except Exception as error:
log.error(error)
ERROR_MSG = 'Error: %s' % error
# pylint: disable=no-init
# pylint: disable=attribute-defined-outside-init
@unique
class UserMessages(IntEnum):
@@ -87,7 +79,6 @@ class UserMessages(IntEnum):
class OmemoPlugin(GajimPlugin):
def init(self):
""" Init """
if ERROR_MSG:
self.activatable = False
self.available_text = ERROR_MSG
@@ -99,17 +90,13 @@ class OmemoPlugin(GajimPlugin):
'signed-in': (ged.PRECORE, self.signed_in),
'omemo-new-fingerprint': (ged.PRECORE, self._on_new_fingerprints),
}
self.modules = [
omemo,
omemo_devicelist,
]
self.modules = [omemo]
self.config_dialog = OMEMOConfigDialog(self)
self.gui_extension_points = {
'hyperlink_handler': (self._file_decryption, None),
'encrypt' + self.encryption_name: (self._encrypt_message, None),
'gc_encrypt' + self.encryption_name: (
self._gc_encrypt_message, None),
'decrypt': (self._message_received, None),
'send_message' + self.encryption_name: (
self.before_sendmessage, None),
'encryption_dialog' + self.encryption_name: (
@@ -198,11 +185,6 @@ class OmemoPlugin(GajimPlugin):
return False
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):
if conn.name == 'Local':
return
@@ -225,12 +207,11 @@ class OmemoPlugin(GajimPlugin):
@staticmethod
def _encrypt_file_thread(file, callback, *args, **kwargs):
encrypted_data, key, iv = file_crypto.encrypt_file(
file.get_data(full=True))
result = aes_encrypt_file(file.get_data(full=True))
file.encrypted = True
file.size = len(encrypted_data)
file.user_data = binascii.hexlify(iv + key).decode('utf-8')
file.data = encrypted_data
file.size = len(result.payload)
file.user_data = binascii.hexlify(result.iv + result.key).decode()
file.data = result.payload
if file.event.isSet():
return
GLib.idle_add(callback, file)
@@ -267,7 +248,7 @@ class OmemoPlugin(GajimPlugin):
else:
# check if we have devices for the contact
if not self.get_omemo(account).device_list_for(contact.jid):
con.query_devicelist(contact.jid, True)
con.request_devicelist(contact.jid, True)
self.print_message(chat_control, UserMessages.QUERY_DEVICES)
chat_control.sendmessage = False
return

View File

@@ -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'