Commit dabfbbf8 authored by Philipp Hörist's avatar Philipp Hörist

OMEMO GTK3 inital

parent 9457a180
This diff is collapsed.
[style]
based_on_style = pep8
align_closing_bracket_with_visual_indent = true
join_multiple_lines = true
0.9.0 / 2016-08-28
- Send INFO message to resources who dont support OMEMO
- Check dependencys and give correct error message
- Dont process PreKeyWhisperMessages without PreKey
- Dont process PGP messages
0.8.1 / 2016-08-05
- Query own Device Bundles on send button press
- Make Fingerprint Window higher and rename Buttons for something more appropriate
- Bugfixes
0.8.0 / 2016-08-03
- Encryption improvements:
-- SignedPreKey renews every 24 hours
-- New PreKeys are generated and published if less then 80 are available
-- If the Python Cryptography package is installed native encryption is now used (faster on old devices)
-- Bundle Information is only pulled right before sending a Message (see Business rules of the OMEMO XEP)
-- If Contact supports OMEMO, encryption is activated automatically
- Other Stuff:
-- The Fingerprint Window pops up if the Send Button is pressed and there are new Fingerprints in the DB
-- Message Correction now works with OMEMO (Press STRG + UP Arrow to correct the last send message)
-- SQL Refactoring, so new users dont have to go through DB Migration
-- Small bugfixes
0.7.5 / 2016-07-20
================
- Announcing of Support right after Plugin activation
- New Context Menu for Gajim Compact View
- Own Device Fingerprints are now available in the Fingerprint Window
- Small bugfixes
0.7 / 2016-07-16
================
- Reworked publishing Devicelist
- Deactivate Gajim E2E on startup
- Added new OMEMO popup menu
- UI & handling of inactive Devices
- various refactoring
0.6 / 2016-06-30
================
- Add MAM support
- Added Fingerprint Trustmanagment UI
- Added Plugin Config Menu
0.5 / 2016-05-02
================
- Add Windows support
- Fix bugs
0.4 / 2016-01-21
==================
* Update README.md
* Fix #32: Add own devices as possible OMEMO partners.
* Fix one of the errors in #26
* Fix sqlite db intialization
* Use the standalone python-omemo library
* FIx LOG_DB errors / lost messages
* Move all OMEMO related parts to own dir
* Rename all links from kalkin/.. to omemo/...
* Update archlinux PKGBUILD to 0.3
0.3 / 2016-01-10
==================
* Save if OMEMO is enabled between restarts - #17
* Disable OMEMO if dependencies are missing - #9
* Make logging less verbose
* Add Arch Linux PKGBUILD file (Thanks Tommaso Sardelli)
* Extend README
* Fix hiding OMEMO controls in muc
* Fix "'ChatControl' object has no attribute 'lock_image'" bug - #16
* Ui clearly displays which message is encrypted (and how) - #15
* Plaintext messages are now always marked - #15
# 2015-12-27
- Fix crash, if jid is not in list (Thanks Mic92)
- Fix clear_device_list, if account is not connected (Thanks Mic92)
- Provide python-axolotl installation instructions in README and manifest.ini
This diff is collapsed.
# OMEMO Plugin for Gajim
This Plugin adds support for the [OMEMO Encryption](http://conversations.im/omemo) to [Gajim](https://gajim.org/). This
plugin is [free software](http://www.gnu.org/philosophy/free-sw.en.html)
distributed under the GNU General Public License version 3 or any later version.
## Installation
Before you open any issues please read our [Wiki](https://github.com/omemo/gajim-omemo/wiki) which addresses some problems that can occur during an install
### Linux
See [Linux Wiki](https://github.com/omemo/gajim-omemo/wiki/Installing-on-Linux)
### Windows
See [Windows Wiki](https://github.com/omemo/gajim-omemo/wiki/Installing-on-Windows)
### Via Package Manager
#### Arch
See [Arch Wiki](https://wiki.archlinux.org/index.php/Gajim#OMEMO_Support)
#### Gentoo
`layman -a flow && emerge gajim-omemo`
### Via PluginInstallerPlugin
Install the current stable version via the Gajim PluginManager. You *need* Gajim
version *0.16.5*. If your package manager does not provide an up to date version
you can install it from the official Mercurial repository. *DO NOT USE* gajim
0.16.4 it contains a vulnerability, which is fixed in 0.16.5.
```shell
hg clone https://hg.gajim.org/gajim
cd gajim
hg update gajim-0.16.5 --clean
```
**NOTE:** You *have* to install `python-axolotl` via `pip`. Depending on your setup you might
want to use `pip2` as Gajim is using python2.7. If you are using the official repository,
do not forget to install the `nbxmpp` dependency via pip or you package manager.
if you still have problems, we have written down the most common problems [here](https://github.com/omemo/gajim-omemo/wiki/It-doesnt-work,-what-should-i-do%3F-(Linux))
## Running
Enable *OMEMO Multi-End Message and Object Encryption* in the Plugin-Manager.
If your contact supports OMEMO you should see a new orange fish icon in the chat window.
Encryption will be enabled by default for contacts that support OMEMO.
If you open the chat window, the Plugin will tell you with a green status message if its *enabled* or *disabled*.
If you see no status message, your contact doesnt support OMEMO.
(**Beware**, every status message is green. A green message does not mean encryption is active. Read the message !)
You can also check if encryption is enabled/disabled, when you click on the OMEMO icon.
When you send your first message the Plugin will query your contacts encryption keys and you will
see them in a readable fingerprint format in the fingerprint window which pops up.
you have to trust at least **one** fingerprint to send messages.
you can receive messages from fingerprints where you didnt made a trust decision, but you cant
receive Messages from *not trusted* fingerprints
## Debugging
To see OMEMO related debug output start Gajim with the parameter `-l
gajim.plugin_system.omemo=DEBUG`.
## Hacking
This repository contains the current development version. If you want to
contribute clone the git repository into your Gajim's plugin directory.
```shell
mkdir ~/.local/share/gajim/plugins -p
cd ~/.local/share/gajim/plugins
git clone https://github.com/omemo/gajim-omemo
```
## Support this project
I develop this project in my free time. Your donation allows me to spend more
time working on it and on free software generally.
My Bitcoin Address is: `1CnNM3Mree9hU8eRjCXrfCWVmX6oBnEfV1`
[![Support Me via Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/thing/5038679)
## I found a bug
Please report it to the [issue
tracker](https://github.com/omemo/gajim-omemo/issues). If you are experiencing
misbehaviour please provide detailed steps to reproduce and debugging output.
Always mention the exact Gajim version.
## Contact
You can contact me via email at `bahtiar@gadimov.de` or follow me on
[Twitter](https://twitter.com/_kalkin)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
[info]
name: OMEMO
short_name: omemo
version: 0.9.0
description: OMEMO is an XMPP Extension Protocol (XEP) for secure multi-client end-to-end encryption based on Axolotl and PEP. You need to install some dependencys, you can find install instructions for your system in the Github Wiki.
authors: Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
Daniel Gultsch <daniel@gultsch.de>
Philipp Hörist <philipp@hoerist.com>
homepage: http://github.com/omemo/gajim-omemo.git
min_gajim_version: 0.16.9
max_gajim_version: 0.16.11
__version__ = "0.1.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 logging
log = logging.getLogger('gajim.plugin_system.omemo')
try:
from .aes_gcm_native import aes_decrypt
from .aes_gcm_native import aes_encrypt
log.debug('Using fast cryptography')
except ImportError:
from .aes_gcm_fallback import aes_decrypt
from .aes_gcm_fallback import aes_encrypt
log.debug('Using slow cryptography')
def encrypt(key, iv, plaintext):
return aes_encrypt(key, iv, plaintext)
def decrypt(key, iv, ciphertext):
return aes_decrypt(key, iv, ciphertext)
class NoValidSessions(Exception):
pass
# -*- coding: utf-8 -*-
#
# Copyright 2014 Jonathan Zdziarski <jonathan@zdziarski.com>
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from struct import pack, unpack
from Crypto.Cipher import AES
from Crypto.Util import strxor
def gcm_rightshift(vec):
for x in range(15, 0, -1):
c = vec[x] >> 1
c |= (vec[x - 1] << 7) & 0x80
vec[x] = c
vec[0] >>= 1
return vec
def gcm_gf_mult(a, b):
mask = [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]
poly = [0x00, 0xe1]
Z = [0] * 16
V = [c for c in a]
for x in range(128):
if b[x >> 3] & mask[x & 7]:
Z = [V[y] ^ Z[y] for y in range(16)]
bit = V[15] & 1
V = gcm_rightshift(V)
V[0] ^= poly[bit]
return Z
def ghash(h, auth_data, data):
u = (16 - len(data)) % 16
v = (16 - len(auth_data)) % 16
x = auth_data + chr(0) * v + data + chr(0) * u
x += pack('>QQ', len(auth_data) * 8, len(data) * 8)
y = [0] * 16
vec_h = [ord(c) for c in h]
for i in range(0, len(x), 16):
block = [ord(c) for c in x[i:i + 16]]
y = [y[j] ^ block[j] for j in range(16)]
y = gcm_gf_mult(y, vec_h)
return ''.join(chr(c) for c in y)
def inc32(block):
counter, = unpack('>L', block[12:])
counter += 1
return block[:12] + pack('>L', counter)
def gctr(k, icb, plaintext):
y = ''
if len(plaintext) == 0:
return y
aes = AES.new(k)
cb = icb
for i in range(0, len(plaintext), aes.block_size):
cb = inc32(cb)
encrypted = aes.encrypt(cb)
plaintext_block = plaintext[i:i + aes.block_size]
y += strxor.strxor(plaintext_block, encrypted[:len(plaintext_block)])
return y
def gcm_decrypt(k, iv, encrypted, auth_data, tag):
aes = AES.new(k)
h = aes.encrypt(chr(0) * aes.block_size)
if len(iv) == 12:
y0 = iv + "\x00\x00\x00\x01"
else:
y0 = ghash(h, '', iv)
decrypted = gctr(k, y0, encrypted)
s = ghash(h, auth_data, encrypted)
t = aes.encrypt(y0)
T = strxor.strxor(s, t)
if T != tag:
raise ValueError('Decrypted data is invalid')
else:
return decrypted
def gcm_encrypt(k, iv, plaintext, auth_data):
aes = AES.new(k)
h = aes.encrypt(chr(0) * aes.block_size)
if len(iv) == 12:
y0 = iv + "\x00\x00\x00\x01"
else:
y0 = ghash(h, '', iv)
encrypted = gctr(k, y0, plaintext)
s = ghash(h, auth_data, encrypted)
t = aes.encrypt(y0)
T = strxor.strxor(s, t)
return (encrypted, T)
def aes_encrypt(key, nonce, plaintext):
""" Use AES128 GCM with the given key and iv to encrypt the payload. """
c, t = gcm_encrypt(key, nonce, plaintext, '')
result = c + t
return result
def aes_decrypt(key, nonce, payload):
""" Use AES128 GCM with the given key and iv to decrypt the payload. """
ciphertext = payload[:-16]
mac = payload[-16:]
return gcm_decrypt(key, nonce, ciphertext, '', mac)
# -*- 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
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 doesnt 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
def aes_decrypt(key, iv, payload):
""" Use AES128 GCM with the given key and iv to decrypt the payload. """
data = payload[:-16]
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
''' 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]
# -*- 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 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 isTrustedIdentity(self, recepientId, identityKey):
return self.identityKeyStore.isTrustedIdentity(recepientId,
identityKey)
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 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 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/>.
#
from axolotl.ecc.djbec import DjbECPrivateKey, DjbECPublicKey
from axolotl.identitykey import IdentityKey