From dabfbbf826a091972eaac83c40ada5498f8d8933 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20H=C3=B6rist?= <philipp@hoerist.com>
Date: Sun, 28 Aug 2016 23:26:56 +0200
Subject: [PATCH] OMEMO GTK3 inital

---
 omemo/.pylintrc                      | 379 ++++++++++++
 omemo/.style.yapf                    |   4 +
 omemo/CHANGELOG                      |  80 +++
 omemo/COPYING                        | 674 ++++++++++++++++++++
 omemo/README.md                      |  90 +++
 omemo/__init__.py                    | 883 +++++++++++++++++++++++++++
 omemo/config_dialog.ui               | 417 +++++++++++++
 omemo/fpr_dialog.ui                  | 298 +++++++++
 omemo/manifest.ini                   |  11 +
 omemo/omemo.png                      | Bin 0 -> 5759 bytes
 omemo/omemo/__init__.py              |   1 +
 omemo/omemo/aes_gcm.py               |  42 ++
 omemo/omemo/aes_gcm_fallback.py      | 152 +++++
 omemo/omemo/aes_gcm_native.py        |  61 ++
 omemo/omemo/db_helpers.py            |  15 +
 omemo/omemo/encryption.py            |  64 ++
 omemo/omemo/liteaxolotlstore.py      | 168 +++++
 omemo/omemo/liteidentitykeystore.py  | 167 +++++
 omemo/omemo/liteprekeystore.py       |  87 +++
 omemo/omemo/litesessionstore.py      | 130 ++++
 omemo/omemo/litesignedprekeystore.py | 113 ++++
 omemo/omemo/sql.py                   | 147 +++++
 omemo/omemo/state.py                 | 412 +++++++++++++
 omemo/omemo16x16.png                 | Bin 0 -> 816 bytes
 omemo/pkgs/PKGBUILD                  |  24 +
 omemo/setup.cfg                      |   2 +
 omemo/ui.py                          | 619 +++++++++++++++++++
 omemo/xmpp.py                        | 346 +++++++++++
 28 files changed, 5386 insertions(+)
 create mode 100644 omemo/.pylintrc
 create mode 100644 omemo/.style.yapf
 create mode 100644 omemo/CHANGELOG
 create mode 100644 omemo/COPYING
 create mode 100644 omemo/README.md
 create mode 100644 omemo/__init__.py
 create mode 100644 omemo/config_dialog.ui
 create mode 100644 omemo/fpr_dialog.ui
 create mode 100644 omemo/manifest.ini
 create mode 100644 omemo/omemo.png
 create mode 100644 omemo/omemo/__init__.py
 create mode 100644 omemo/omemo/aes_gcm.py
 create mode 100644 omemo/omemo/aes_gcm_fallback.py
 create mode 100644 omemo/omemo/aes_gcm_native.py
 create mode 100644 omemo/omemo/db_helpers.py
 create mode 100644 omemo/omemo/encryption.py
 create mode 100644 omemo/omemo/liteaxolotlstore.py
 create mode 100644 omemo/omemo/liteidentitykeystore.py
 create mode 100644 omemo/omemo/liteprekeystore.py
 create mode 100644 omemo/omemo/litesessionstore.py
 create mode 100644 omemo/omemo/litesignedprekeystore.py
 create mode 100644 omemo/omemo/sql.py
 create mode 100644 omemo/omemo/state.py
 create mode 100644 omemo/omemo16x16.png
 create mode 100644 omemo/pkgs/PKGBUILD
 create mode 100644 omemo/setup.cfg
 create mode 100644 omemo/ui.py
 create mode 100644 omemo/xmpp.py

diff --git a/omemo/.pylintrc b/omemo/.pylintrc
new file mode 100644
index 00000000..7222fa7d
--- /dev/null
+++ b/omemo/.pylintrc
@@ -0,0 +1,379 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Use multiple processes to speed up Pylint.
+jobs=1
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code
+extension-pkg-whitelist=
+
+# Allow optimization of some AST trees. This will activate a peephole AST
+# optimizer, which will apply various small optimizations. For instance, it can
+# be used to obtain the result of joining multiple strings with the addition
+# operator. Joining a lot of strings can lead to a maximum recursion error in
+# Pylint and this flag can prevent that. It has one side effect, the resulting
+# AST will be different than the one from reality.
+optimize-ast=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
+confidence=
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=yes
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[BASIC]
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,input
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,e,Run,_,log,ui,iq,db
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# Regular expression matching correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for function names
+function-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for variable names
+variable-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct constant names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Naming hint for constant names
+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression matching correct attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for attribute names
+attr-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for argument names
+argument-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Naming hint for class attribute names
+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Naming hint for inline iteration names
+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+
+# Regular expression matching correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Naming hint for class names
+class-name-hint=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression matching correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Naming hint for module names
+module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression matching correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for method names
+method-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+
+[ELIF]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=trailing-comma,dict-separator
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+# Number of spaces of indent required inside a hanging  or continued line.
+indent-after-paren=4
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+
+[SPELLING]
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set). This supports can work
+# with qualified names.
+ignored-classes=
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=_$|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,_cb
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,_fields,_replace,_source,_make,_show_lock_image
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=20
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of boolean expressions in a if statement
+max-bool-expr=5
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/omemo/.style.yapf b/omemo/.style.yapf
new file mode 100644
index 00000000..9277d6a5
--- /dev/null
+++ b/omemo/.style.yapf
@@ -0,0 +1,4 @@
+[style]
+based_on_style = pep8
+align_closing_bracket_with_visual_indent = true
+join_multiple_lines = true
diff --git a/omemo/CHANGELOG b/omemo/CHANGELOG
new file mode 100644
index 00000000..20bd7d10
--- /dev/null
+++ b/omemo/CHANGELOG
@@ -0,0 +1,80 @@
+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
diff --git a/omemo/COPYING b/omemo/COPYING
new file mode 100644
index 00000000..818433ec
--- /dev/null
+++ b/omemo/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program 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.
+
+    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/omemo/README.md b/omemo/README.md
new file mode 100644
index 00000000..e72ccebb
--- /dev/null
+++ b/omemo/README.md
@@ -0,0 +1,90 @@
+# 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)
diff --git a/omemo/__init__.py b/omemo/__init__.py
new file mode 100644
index 00000000..0220169f
--- /dev/null
+++ b/omemo/__init__.py
@@ -0,0 +1,883 @@
+# -*- 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/>.
+#
+
+import logging
+import os
+import sqlite3
+
+from common import caps_cache, gajim, ged
+from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
+from plugins import GajimPlugin
+from plugins.helpers import log_calls
+from nbxmpp.simplexml import Node
+from nbxmpp import NS_CORRECT
+
+from . import ui
+from .ui import Ui
+from .xmpp import (
+    NS_NOTIFY, NS_OMEMO, NS_EME, BundleInformationAnnouncement,
+    BundleInformationQuery, DeviceListAnnouncement, DevicelistQuery,
+    DevicelistPEP, OmemoMessage, successful, unpack_device_bundle,
+    unpack_device_list_update, unpack_encrypted)
+
+# from common import demandimport
+# demandimport.enable()
+# demandimport.ignore += ['_imp']
+
+
+IQ_CALLBACK = {}
+
+AXOLOTL_MISSING = 'You are missing Python-Axolotl or use an outdated version'
+PROTOBUF_MISSING = 'OMEMO cant import Google Protobuf, you can find help in ' \
+                   'the GitHub Wiki'
+GAJIM_VERSION = 'OMEMO only works with the latest Gajim version, get the ' \
+                'latest version from gajim.org'
+ERROR_MSG = ''
+
+NS_HINTS = 'urn:xmpp:hints'
+NS_PGP = 'urn:xmpp:openpgp:0'
+DB_DIR = gajim.gajimpaths.data_root
+
+log = logging.getLogger('gajim.plugin_system.omemo')
+
+
+try:
+    from .omemo.state import OmemoState
+except Exception as e:
+    log.error(e)
+    ERROR_MSG = 'Error: {}'.format(e)
+
+try:
+    import google.protobuf
+except Exception as e:
+    log.error(e)
+    ERROR_MSG = PROTOBUF_MISSING
+
+try:
+    SETUPTOOLS_MISSING = False
+    from pkg_resources import parse_version
+except Exception as e:
+    SETUPTOOLS_MISSING = True
+    ERROR_MSG = 'You are missing the Setuptools package.'
+
+if not SETUPTOOLS_MISSING:
+    try:
+        import axolotl
+        if parse_version(axolotl.__version__) < parse_version('0.1.35'):
+            ERROR_MSG = AXOLOTL_MISSING
+    except Exception as e:
+        log.error(e)
+        ERROR_MSG = AXOLOTL_MISSING
+
+# pylint: disable=no-init
+# pylint: disable=attribute-defined-outside-init
+
+
+class OmemoPlugin(GajimPlugin):
+
+    omemo_states = {}
+    ui_list = {}
+
+    @log_calls('OmemoPlugin')
+    def init(self):
+        """ Init """
+        if ERROR_MSG:
+            self.activatable = False
+            self.available_text = ERROR_MSG
+            return
+        self.events_handlers = {
+            'mam-message-received': (ged.PRECORE, self.mam_message_received),
+            'message-received': (ged.PRECORE, self.message_received),
+            'pep-received': (ged.PRECORE, self.handle_device_list_update),
+            'raw-iq-received': (ged.PRECORE, self.handle_iq_received),
+            'signed-in': (ged.PRECORE, self.signed_in),
+            'stanza-message-outgoing':
+            (ged.PRECORE, self.handle_outgoing_stanza),
+            'message-outgoing':
+            (ged.PRECORE, self.handle_outgoing_event),
+        }
+        self.config_dialog = ui.OMEMOConfigDialog(self)
+        self.gui_extension_points = {'chat_control': (self.connect_ui,
+                                                      self.disconnect_ui)}
+        SUPPORTED_PERSONAL_USER_EVENTS.append(DevicelistPEP)
+        self.plugin = self
+        self.announced = []
+        self.query_for_bundles = []
+
+    @log_calls('OmemoPlugin')
+    def get_omemo_state(self, account):
+        """ Returns the the OmemoState for the specified account.
+            Creates the OmemoState if it does not exist yet.
+
+            Parameters
+            ----------
+            account : str
+                the account name
+
+            Returns
+            -------
+            OmemoState
+        """
+        if account not in self.omemo_states:
+            self.deactivate_gajim_e2e(account)
+            db_path = os.path.join(DB_DIR, 'omemo_' + account + '.db')
+            conn = sqlite3.connect(db_path, check_same_thread=False)
+
+            my_jid = gajim.get_jid_from_account(account)
+
+            self.omemo_states[account] = OmemoState(my_jid, conn, account,
+                                                    self.plugin)
+
+        return self.omemo_states[account]
+
+    @staticmethod
+    def deactivate_gajim_e2e(account):
+        """ Deativates E2E encryption in Gajim """
+        gajim.config.set_per('accounts', account,
+                             'autonegotiate_esessions', False)
+        gajim.config.set_per('accounts', account,
+                             'enable_esessions', False)
+        log.info(str(account) + " => Gajim E2E encryption disabled")
+
+    @log_calls('OmemoPlugin')
+    def signed_in(self, event):
+        """ Method called on SignIn
+
+            Parameters
+            ----------
+            event : SignedInEvent
+        """
+        account = event.conn.name
+        log.debug(account +
+                  ' => Announce Support after Sign In')
+        self.query_for_bundles = []
+        self.announced = []
+        self.announced.append(account)
+        self.publish_bundle(account)
+        self.query_own_devicelist(account)
+
+    @log_calls('OmemoPlugin')
+    def activate(self):
+        """ Method called when the Plugin is activated in the PluginManager
+        """
+        self.query_for_bundles = []
+        if NS_NOTIFY not in gajim.gajim_common_features:
+            gajim.gajim_common_features.append(NS_NOTIFY)
+        self._compute_caps_hash()
+        # Publish bundle information
+        for account in gajim.connections:
+            if account not in self.announced:
+                if gajim.account_is_connected(account):
+                    log.debug(account +
+                              ' => Announce Support after Plugin Activation')
+                    self.announced.append(account)
+                    self.publish_bundle(account)
+                    self.query_own_devicelist(account)
+
+    @log_calls('OmemoPlugin')
+    def deactivate(self):
+        """ Method called when the Plugin is deactivated in the PluginManager
+
+            Removes OMEMO from the Entity Capabilities list
+        """
+        if NS_NOTIFY in gajim.gajim_common_features:
+            gajim.gajim_common_features.remove(NS_NOTIFY)
+        self._compute_caps_hash()
+
+    @staticmethod
+    def _compute_caps_hash():
+        """ Computes the hash for Entity Capabilities and publishes it """
+        for acc in gajim.connections:
+            gajim.caps_hash[acc] = caps_cache.compute_caps_hash(
+                [gajim.gajim_identity],
+                gajim.gajim_common_features +
+                gajim.gajim_optional_features[acc])
+            # re-send presence with new hash
+            connected = gajim.connections[acc].connected
+            if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible':
+                gajim.connections[acc].change_status(
+                    gajim.SHOW_LIST[connected], gajim.connections[acc].status)
+
+    @log_calls('OmemoPlugin')
+    def mam_message_received(self, msg):
+        """ Handles an incoming MAM message
+
+            Payload is decrypted and the plaintext is written into the
+            event object. Afterwards the event is passed on further to Gajim.
+
+            Parameters
+            ----------
+            msg : MamMessageReceivedEvent
+
+            Returns
+            -------
+            Return means that the Event is passed on to Gajim
+        """
+        if msg.msg_.getTag('openpgp', namespace=NS_PGP):
+            return
+
+        omemo_encrypted_tag = msg.msg_.getTag('encrypted', namespace=NS_OMEMO)
+        if omemo_encrypted_tag:
+            account = msg.conn.name
+            log.debug(account + ' => OMEMO MAM msg received')
+
+            state = self.get_omemo_state(account)
+
+            from_jid = str(msg.msg_.getAttr('from'))
+            from_jid = gajim.get_jid_without_resource(from_jid)
+
+            msg_dict = unpack_encrypted(omemo_encrypted_tag)
+
+            msg_dict['sender_jid'] = from_jid
+
+            plaintext = state.decrypt_msg(msg_dict)
+
+            if not plaintext:
+                return
+
+            self.print_msg_to_log(msg.msg_)
+
+            msg.msgtxt = plaintext
+
+            contact_jid = msg.with_
+
+            if account in self.ui_list and \
+                    contact_jid in self.ui_list[account]:
+                self.ui_list[account][contact_jid].activate_omemo()
+            return False
+
+        elif msg.msg_.getTag('body'):
+            account = msg.conn.name
+
+            jid = msg.with_
+            state = self.get_omemo_state(account)
+            omemo_enabled = state.encryption.is_active(jid)
+
+            if omemo_enabled:
+                msg.msgtxt = '**Unencrypted** ' + msg.msgtxt
+
+    @log_calls('OmemoPlugin')
+    def message_received(self, msg):
+        """ Handles an incoming message
+
+            Payload is decrypted and the plaintext is written into the
+            event object. Afterwards the event is passed on further to Gajim.
+
+            Parameters
+            ----------
+            msg : MessageReceivedEvent
+
+            Returns
+            -------
+            Return means that the Event is passed on to Gajim
+        """
+
+        if msg.stanza.getTag('openpgp', namespace=NS_PGP):
+            return
+
+        if msg.stanza.getTag('encrypted', namespace=NS_OMEMO) and \
+                msg.mtype == 'chat':
+            account = msg.conn.name
+            log.debug(account + ' => OMEMO msg received')
+
+            state = self.get_omemo_state(account)
+            if msg.forwarded and msg.sent:
+                from_jid = str(msg.stanza.getTo())  # why gajim? why?
+                log.debug('message was forwarded doing magic')
+            else:
+                from_jid = str(msg.stanza.getFrom())
+            self.print_msg_to_log(msg.stanza)
+            msg_dict = unpack_encrypted(msg.stanza.getTag
+                                        ('encrypted', namespace=NS_OMEMO))
+            msg_dict['sender_jid'] = gajim.get_jid_without_resource(from_jid)
+            plaintext = state.decrypt_msg(msg_dict)
+
+            if not plaintext:
+                return
+
+            msg.msgtxt = plaintext
+            # Gajim bug: there must be a body or the message
+            # gets dropped from history
+            msg.stanza.setBody(plaintext)
+
+            contact_jid = gajim.get_jid_without_resource(from_jid)
+            if account in self.ui_list and \
+                    contact_jid in self.ui_list[account]:
+                self.ui_list[account][contact_jid].activate_omemo()
+            return False
+
+        elif msg.stanza.getTag('body') and msg.mtype == 'chat':
+            account = msg.conn.name
+
+            from_jid = str(msg.stanza.getFrom())
+            jid = gajim.get_jid_without_resource(from_jid)
+            state = self.get_omemo_state(account)
+            omemo_enabled = state.encryption.is_active(jid)
+
+            if omemo_enabled:
+                msg.msgtxt = '**Unencrypted** ' + msg.msgtxt
+                # msg.stanza.setBody(msg.msgtxt)
+
+                try:
+                    gui = self.ui_list[account].get(jid, None)
+                    if gui and gui.encryption_active():
+                        gui.plain_warning()
+                except KeyError:
+                    log.debug('No Ui present for ' + jid +
+                              ', Ui Warning not shown')
+
+    @log_calls('OmemoPlugin')
+    def handle_outgoing_event(self, event):
+        """ Handles a message outgoing event
+
+            In this event we have no stanza. XHTML is set to None
+            so that it doesnt make its way into the stanza
+
+            Parameters
+            ----------
+            event : MessageOutgoingEvent
+
+            Returns
+            -------
+            Return if encryption is not activated
+        """
+        account = event.account
+        state = self.get_omemo_state(account)
+
+        if not state.encryption.is_active(event.jid):
+            return False
+
+        event.xhtml = None
+
+    @log_calls('OmemoPlugin')
+    def handle_outgoing_stanza(self, event):
+        """ Manipulates the outgoing stanza
+
+            The body is getting encrypted
+
+            Parameters
+            ----------
+            event : StanzaMessageOutgoingEvent
+
+            Returns
+            -------
+            Return if encryption is not activated or any other
+            exception or error occurs
+        """
+        try:
+            if not event.msg_iq.getTag('body'):
+                return
+
+            account = event.conn.name
+            state = self.get_omemo_state(account)
+            full_jid = str(event.msg_iq.getAttr('to'))
+            to_jid = gajim.get_jid_without_resource(full_jid)
+            if not state.encryption.is_active(to_jid):
+                return
+
+            # Delete previous Message out of Correction Message Stanza
+            if event.msg_iq.getTag('replace', namespace=NS_CORRECT):
+                event.msg_iq.delChild('encrypted', attrs={'xmlns': NS_OMEMO})
+
+            plaintext = event.msg_iq.getBody().encode('utf-8')
+
+            msg_dict = state.create_msg(
+                gajim.get_jid_from_account(account), to_jid, plaintext)
+
+            if not msg_dict:
+                return True
+
+            encrypted_node = OmemoMessage(msg_dict)
+
+            # Check if non-OMEMO resource is online
+            contacts = gajim.contacts.get_contacts(account, to_jid)
+            non_omemo_resource_online = False
+            for contact in contacts:
+                if contact.show == 'offline':
+                    continue
+                if not contact.supports(NS_NOTIFY):
+                    log.debug(contact.get_full_jid() +
+                              ' => Contact doesnt support OMEMO, '
+                              'adding Info Message to Body')
+                    support_msg = 'You received a message encrypted with ' \
+                                  'OMEMO but your client doesnt support OMEMO.'
+                    event.msg_iq.setBody(support_msg)
+                    non_omemo_resource_online = True
+            if not non_omemo_resource_online:
+                event.msg_iq.delChild('body')
+
+            event.msg_iq.addChild(node=encrypted_node)
+
+            # XEP-xxxx: Explicit Message Encryption
+            if not event.msg_iq.getTag('encrypted', attrs={'xmlns': NS_EME}):
+                eme_node = Node('encrypted', attrs={'xmlns': NS_EME,
+                                                    'name': 'OMEMO',
+                                                    'namespace': NS_OMEMO})
+                event.msg_iq.addChild(node=eme_node)
+
+            # Store Hint for MAM
+            store = Node('store', attrs={'xmlns': NS_HINTS})
+            event.msg_iq.addChild(node=store)
+            self.print_msg_to_log(event.msg_iq)
+        except Exception as e:
+            log.debug(e)
+            return True
+
+    @log_calls('OmemoPlugin')
+    def handle_device_list_update(self, event):
+        """ Check if the passed event is a device list update and store the new
+            device ids.
+
+            Parameters
+            ----------
+            event : PEPReceivedEvent
+
+            Returns
+            -------
+            bool
+                True if the given event was a valid device list update event
+
+
+            See also
+            --------
+            4.2 Discovering peer support
+                http://conversations.im/xeps/multi-end.html#usecases-discovering
+        """
+        if event.pep_type != 'headline':
+            return False
+
+        devices_list = list(set(unpack_device_list_update(event.stanza,
+                                                          event.conn.name)))
+        if len(devices_list) == 0:
+            return False
+        account = event.conn.name
+        contact_jid = gajim.get_jid_without_resource(event.fjid)
+        state = self.get_omemo_state(account)
+        my_jid = gajim.get_jid_from_account(account)
+
+        if contact_jid == my_jid:
+            log.info(account + ' => Received own device list:' + str(
+                devices_list))
+            state.set_own_devices(devices_list)
+            state.store.sessionStore.setActiveState(devices_list, my_jid)
+
+            # remove contact from list, so on send button pressed
+            # we query for bundle and build a session
+            if contact_jid in self.query_for_bundles:
+                self.query_for_bundles.remove(contact_jid)
+
+            if not state.own_device_id_published():
+                # Our own device_id is not in the list, it could be
+                # overwritten by some other client
+                self.publish_own_devices_list(account)
+        else:
+            log.info(account + ' => Received device list for ' +
+                     contact_jid + ':' + str(devices_list))
+            state.set_devices(contact_jid, devices_list)
+            state.store.sessionStore.setActiveState(devices_list, contact_jid)
+
+            # remove contact from list, so on send button pressed
+            # we query for bundle and build a session
+            if contact_jid in self.query_for_bundles:
+                self.query_for_bundles.remove(contact_jid)
+
+            # Enable Encryption on receiving first Device List
+            if not state.encryption.exist(contact_jid):
+                if account in self.ui_list and \
+                        contact_jid in self.ui_list[account]:
+                    log.debug(account +
+                              ' => Switch encryption ON automatically ...')
+                    self.ui_list[account][contact_jid].activate_omemo()
+                else:
+                    log.debug(account +
+                              ' => Switch encryption ON automatically ...')
+                    self.omemo_enable_for(contact_jid, account)
+
+            if account in self.ui_list and \
+                    contact_jid not in self.ui_list[account]:
+
+                chat_control = gajim.interface.msg_win_mgr.get_control(
+                    contact_jid, account)
+
+                if chat_control:
+                    self.connect_ui(chat_control)
+
+        return True
+
+    @log_calls('OmemoPlugin')
+    def publish_own_devices_list(self, account):
+        """ Check if the passed event is a device list update and store the new
+            device ids.
+
+            Parameters
+            ----------
+            account : str
+                the account name
+        """
+        state = self.get_omemo_state(account)
+        devices_list = state.own_devices
+        devices_list.append(state.own_device_id)
+        devices_list = list(set(devices_list))
+        state.set_own_devices(devices_list)
+
+        log.debug(account + ' => Publishing own Devices: ' + str(
+            devices_list))
+        iq = DeviceListAnnouncement(devices_list)
+        gajim.connections[account].connection.send(iq)
+        id_ = str(iq.getAttr('id'))
+        IQ_CALLBACK[id_] = lambda event: log.debug(event)
+
+    @log_calls('OmemoPlugin')
+    def connect_ui(self, chat_control):
+        """ Method called from Gajim when a Chat Window is opened
+
+            Parameters
+            ----------
+            chat_control : ChatControl
+                Gajim ChatControl object
+        """
+        account = chat_control.contact.account.name
+        contact_jid = chat_control.contact.jid
+        if account not in self.ui_list:
+            self.ui_list[account] = {}
+        state = self.get_omemo_state(account)
+        my_jid = gajim.get_jid_from_account(account)
+        omemo_enabled = state.encryption.is_active(contact_jid)
+        if omemo_enabled:
+            log.debug(account + " => Adding OMEMO ui for " + contact_jid)
+            self.ui_list[account][contact_jid] = Ui(self, chat_control,
+                                                    omemo_enabled, state)
+            self.ui_list[account][contact_jid].new_fingerprints_available()
+            return
+        if contact_jid in state.device_ids or contact_jid == my_jid:
+            log.debug(account + " => Adding OMEMO ui for " + contact_jid)
+            self.ui_list[account][contact_jid] = Ui(self, chat_control,
+                                                    omemo_enabled, state)
+            self.ui_list[account][contact_jid].new_fingerprints_available()
+        else:
+            log.warning(account + " => No devices for " + contact_jid)
+
+    @log_calls('OmemoPlugin')
+    def disconnect_ui(self, chat_control):
+        """ Calls the removeUi method to remove all relatad UI objects.
+
+            Parameters
+            ----------
+            chat_control : ChatControl
+                Gajim ChatControl object
+        """
+        contact_jid = chat_control.contact.jid
+        account = chat_control.contact.account.name
+        self.ui_list[account][contact_jid].removeUi()
+
+    def are_keys_missing(self, account, contact_jid):
+        """ Checks if devicekeys are missing and querys the
+            bundles
+
+            Parameters
+            ----------
+            account : str
+                the account name
+            contact_jid : str
+                bare jid of the contact
+
+            Returns
+            -------
+            bool
+                Returns True if there are no trusted Fingerprints
+        """
+        state = self.get_omemo_state(account)
+        my_jid = gajim.get_jid_from_account(account)
+
+        # Fetch Bundles of own other Devices
+        if my_jid not in self.query_for_bundles:
+
+            devices_without_session = state \
+                    .devices_without_sessions(my_jid)
+
+            self.query_for_bundles.append(my_jid)
+
+            if devices_without_session:
+                for device_id in devices_without_session:
+                    self.fetch_device_bundle_information(account, my_jid,
+                                                         device_id)
+
+        # Fetch Bundles of contacts devices
+        if contact_jid not in self.query_for_bundles:
+
+            devices_without_session = state \
+                .devices_without_sessions(contact_jid)
+
+            self.query_for_bundles.append(contact_jid)
+
+            if devices_without_session:
+                for device_id in devices_without_session:
+                    self.fetch_device_bundle_information(account, contact_jid,
+                                                         device_id)
+
+        if state.getTrustedFingerprints(contact_jid):
+            return False
+        else:
+            return True
+
+    @staticmethod
+    def handle_iq_received(event):
+        """ Method called when an IQ is received
+
+            Parameters
+            ----------
+            event : RawIqReceived
+        """
+        id_ = str(event.stanza.getAttr("id"))
+        if id_ in IQ_CALLBACK:
+            try:
+                IQ_CALLBACK[id_](event.stanza)
+            except:
+                raise
+            finally:
+                del IQ_CALLBACK[id_]
+
+    @log_calls('OmemoPlugin')
+    def fetch_device_bundle_information(self, account, jid, device_id):
+        """ Fetch bundle information for specified jid, key, and create axolotl
+            session on success.
+
+            Parameters
+            ----------
+            account : str
+                The account name
+            jid : str
+                The jid to query for bundle information
+            device_id : int
+                The device_id for which we are missing an axolotl session
+        """
+        log.info(account + ' => Fetch bundle device ' + str(device_id) +
+                 '#' + jid)
+        iq = BundleInformationQuery(jid, device_id)
+        iq_id = str(iq.getAttr('id'))
+        IQ_CALLBACK[iq_id] = \
+            lambda stanza: self.session_from_prekey_bundle(account,
+                                                           stanza, jid,
+                                                           device_id)
+        gajim.connections[account].connection.send(iq)
+
+    @log_calls('OmemoPlugin')
+    def session_from_prekey_bundle(self, account, stanza,
+                                   recipient_id, device_id):
+        """ Starts a session from a PreKey bundle.
+
+            This method tries to build an axolotl session when a PreKey bundle
+            is fetched.
+
+            If a session can not be build it will fail silently but log the a
+            warning.
+
+            See also
+            --------
+
+            4.4 Building a session:
+                http://conversations.im/xeps/multi-end.html#usecases-building
+
+            Parameters:
+            -----------
+            account : str
+                The account name
+            stanza
+                The stanza object received from callback
+            recipient_id : str
+                           The recipient jid
+            device_id : int
+                The device_id for which the bundle was queried
+
+        """
+        state = self.get_omemo_state(account)
+        bundle_dict = unpack_device_bundle(stanza, device_id)
+        if not bundle_dict:
+            log.warning('Failed to build Session with ' + recipient_id)
+            return
+
+        if state.build_session(recipient_id, device_id, bundle_dict):
+            log.info(account + ' => session created for: ' + recipient_id)
+            # Trigger dialog to trust new Fingerprints if
+            # the Chat Window is Open
+            if account in self.ui_list and \
+                    recipient_id in self.ui_list[account]:
+                self.ui_list[account][recipient_id]. \
+                    new_fingerprints_available()
+
+    @log_calls('OmemoPlugin')
+    def query_own_devicelist(self, account):
+        """ Query own devicelist from the server.
+
+            Parameters
+            ----------
+            account : str
+                the account name
+        """
+        my_jid = gajim.get_jid_from_account(account)
+        iq = DevicelistQuery(my_jid)
+        gajim.connections[account].connection.send(iq)
+        log.info(account + ' => Querry own devicelist ...')
+        id_ = str(iq.getAttr("id"))
+        IQ_CALLBACK[id_] = lambda stanza: \
+            self.handle_devicelist_result(account, stanza)
+
+    @log_calls('OmemoPlugin')
+    def publish_bundle(self, account):
+        """ Publish our bundle information to the PEP node.
+
+            Parameters
+            ----------
+            account : str
+                the account name
+
+            See also
+            --------
+            4.3 Announcing bundle information:
+                http://conversations.im/xeps/multi-end.html#usecases-announcing
+        """
+        state = self.get_omemo_state(account)
+        iq = BundleInformationAnnouncement(state.bundle, state.own_device_id)
+        gajim.connections[account].connection.send(iq)
+        id_ = str(iq.getAttr("id"))
+        log.info(account + " => Publishing bundle ...")
+        IQ_CALLBACK[id_] = lambda stanza: \
+            self.handle_publish_result(account, stanza)
+
+    @staticmethod
+    def handle_publish_result(account, stanza):
+        """ Log if publishing our bundle was successful
+
+            Parameters
+            ----------
+            account : str
+                the account name
+            stanza
+                The stanza object received from callback
+        """
+        if successful(stanza):
+            log.info(account + ' => Publishing bundle was successful')
+        else:
+            log.error(account + ' => Publishing bundle was NOT successful')
+
+    @log_calls('OmemoPlugin')
+    def handle_devicelist_result(self, account, stanza):
+        """ If query was successful add own device to the list.
+
+            Parameters
+            ----------
+            account : str
+                the account name
+            stanza
+                The stanza object received from callback
+        """
+
+        my_jid = gajim.get_jid_from_account(account)
+        state = self.get_omemo_state(account)
+
+        if successful(stanza):
+            log.info(account + ' => Devicelistquery was successful')
+            devices_list = list(set(unpack_device_list_update(stanza, account)))
+            if len(devices_list) == 0:
+                return False
+            contact_jid = stanza.getAttr('from')
+            if contact_jid == my_jid:
+                state.set_own_devices(devices_list)
+                state.store.sessionStore.setActiveState(devices_list, my_jid)
+
+                # remove contact from list, so on send button pressed
+                # we query for bundle and build a session
+                if contact_jid in self.query_for_bundles:
+                    self.query_for_bundles.remove(contact_jid)
+
+                if not state.own_device_id_published():
+                    # Our own device_id is not in the list, it could be
+                    # overwritten by some other client
+                    self.publish_own_devices_list(account)
+        else:
+            log.error(account + ' => Devicelistquery was NOT successful')
+            self.publish_own_devices_list(account)
+
+    @log_calls('OmemoPlugin')
+    def clear_device_list(self, account):
+        """ Clears the local devicelist of our own devices and publishes
+            a new one including only the current ID of this device
+
+            Parameters
+            ----------
+            account : str
+                the account name
+        """
+        connection = gajim.connections[account].connection
+        if not connection:
+            return
+        state = self.get_omemo_state(account)
+        devices_list = [state.own_device_id]
+        state.set_own_devices(devices_list)
+
+        log.info(account + ' => Clearing devices_list ' + str(devices_list))
+        iq = DeviceListAnnouncement(devices_list)
+        connection.send(iq)
+        id_ = str(iq.getAttr('id'))
+        IQ_CALLBACK[id_] = lambda event: log.info(event)
+
+    @staticmethod
+    def print_msg_to_log(stanza):
+        """ Prints a stanza in a fancy way to the log """
+        log.debug('-'*15)
+        stanzastr = '\n' + stanza.__str__(fancy=True)
+        stanzastr = stanzastr[0:-1]
+        log.debug(stanzastr)
+        log.debug('-'*15)
+
+    @log_calls('OmemoPlugin')
+    def omemo_enable_for(self, jid, account):
+        """ Used by the UI to enable OMEMO for a specified contact.
+
+            To activate OMEMO check first if a Ui Object exists for the
+            Contact. If it exists use Ui.activate_omemo(). Only if there
+            is no Ui Object for the contact this method is to be used.
+
+            Parameters
+            ----------
+            jid : str
+                bare jid
+            account : str
+                the account name
+        """
+        state = self.get_omemo_state(account)
+        state.encryption.activate(jid)
+
+    @log_calls('OmemoPlugin')
+    def omemo_disable_for(self, jid, account):
+        """ Used by the UI to disable OMEMO for a specified contact.
+
+            WARNING - OMEMO should only be disabled through
+            User interaction with the UI.
+
+            Parameters
+            ----------
+            jid : str
+                bare jid
+            account : str
+                the account name
+        """
+        state = self.get_omemo_state(account)
+        state.encryption.deactivate(jid)
diff --git a/omemo/config_dialog.ui b/omemo/config_dialog.ui
new file mode 100644
index 00000000..df0b8497
--- /dev/null
+++ b/omemo/config_dialog.ui
@@ -0,0 +1,417 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkListStore" id="account_store">
+    <columns>
+      <!-- column-name accountname -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkListStore" id="deviceid_store">
+    <columns>
+      <!-- column-name Device -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkListStore" id="fingerprint_store">
+    <columns>
+      <!-- column-name id -->
+      <column type="gint"/>
+      <!-- column-name screenname -->
+      <column type="gchararray"/>
+      <!-- column-name trust -->
+      <column type="gchararray"/>
+      <!-- column-name fingerprint -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkNotebook" id="notebook1">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <child>
+      <object class="GtkVBox" id="vbox1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="border_width">12</property>
+        <property name="spacing">10</property>
+        <child>
+          <object class="GtkHBox" id="hbox2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">5</property>
+            <child>
+              <object class="GtkLabel" id="label4">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes" comments="label for account selector">&lt;b&gt;Account:&lt;/b&gt;</property>
+                <property name="use_markup">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="account_combobox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="model">account_store</property>
+                <signal name="changed" handler="account_combobox_changed_cb" swapped="no"/>
+                <child>
+                  <object class="GtkCellRendererText" id="cellrenderertext1"/>
+                  <attributes>
+                    <attribute name="text">0</attribute>
+                  </attributes>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkLabel" id="fingerprint_label_desc">
+                <property name="width_request">110</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes" comments="Descriptive label">Own Fingerprint:</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="fingerprint_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label">&lt;tt&gt;-------- -------- -------- -------- --------	&lt;/tt&gt;</property>
+                <property name="use_markup">True</property>
+                <property name="selectable">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox5">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkLabel" id="OwnIDLabel">
+                <property name="width_request">110</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">Own Device ID:</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="ID">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">0</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child type="tab">
+      <object class="GtkLabel" id="label1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes" comments="tab label">Own Fingerprints</property>
+      </object>
+      <packing>
+        <property name="tab_fill">False</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkVBox" id="vbox4">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="border_width">12</property>
+        <property name="spacing">10</property>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="height_request">200</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hscrollbar_policy">automatic</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <child>
+              <object class="GtkTreeView" id="fingerprint_view">
+                <property name="height_request">300</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="model">fingerprint_store</property>
+                <property name="search_column">0</property>
+                <property name="tooltip_column">3</property>
+                <signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
+                <child>
+                  <object class="GtkTreeViewColumn" id="name_column">
+                    <property name="resizable">True</property>
+                    <property name="title">Name</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertext2"/>
+                      <attributes>
+                        <attribute name="text">1</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="trust_column">
+                    <property name="resizable">True</property>
+                    <property name="title">Trust</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertoggle1"/>
+                      <attributes>
+                        <attribute name="text">2</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="fingerprint_column">
+                    <property name="resizable">True</property>
+                    <property name="title">Fingerprint</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertext4"/>
+                      <attributes>
+                        <attribute name="markup">3</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox3">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">5</property>
+            <child>
+              <object class="GtkButton" id="trust_button">
+                <property name="label" translatable="yes" comments="button">Trust/Revoke Fingerprint</property>
+                <property name="width_request">200</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <signal name="clicked" handler="trust_button_clicked_cb" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child type="tab">
+      <object class="GtkLabel" id="label2">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes" comments="tab label">Known Fingerprints</property>
+      </object>
+      <packing>
+        <property name="position">1</property>
+        <property name="tab_fill">False</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkVBox" id="vbox3">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="border_width">12</property>
+        <property name="spacing">10</property>
+        <child>
+          <object class="GtkLabel" id="label5">
+            <property name="height_request">25</property>
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Published Devices</property>
+            <attributes>
+              <attribute name="style" value="normal"/>
+              <attribute name="weight" value="bold"/>
+            </attributes>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="padding">7</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow2">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hscrollbar_policy">never</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <child>
+              <object class="GtkTreeView" id="deviceid_view">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="model">deviceid_store</property>
+                <property name="search_column">0</property>
+                <child>
+                  <object class="GtkTreeViewColumn" id="deviceid_column">
+                    <property name="title" translatable="yes">Device ID</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertext3"/>
+                      <attributes>
+                        <attribute name="text">0</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox4">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">5</property>
+            <child>
+              <object class="GtkButton" id="cleardevice_button">
+                <property name="label" translatable="yes">Clear Devices</property>
+                <property name="width_request">160</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <signal name="clicked" handler="cleardevice_button_clicked_cb" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="refresh">
+                <property name="label">gtk-refresh</property>
+                <property name="width_request">160</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+                <signal name="clicked" handler="refresh_button_clicked_cb" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="position">2</property>
+      </packing>
+    </child>
+    <child type="tab">
+      <object class="GtkLabel" id="label3">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Clear Devices</property>
+      </object>
+      <packing>
+        <property name="position">2</property>
+        <property name="tab_fill">False</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkMenu" id="fprclipboard_menu">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkMenuItem" id="copyfprclipboard_item">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes" comments="Context menu item">Copy to clipboard</property>
+        <property name="use_underline">True</property>
+        <signal name="activate" handler="clipboard_button_cb" swapped="no"/>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/omemo/fpr_dialog.ui b/omemo/fpr_dialog.ui
new file mode 100644
index 00000000..76fbcc5c
--- /dev/null
+++ b/omemo/fpr_dialog.ui
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkListStore" id="account_store">
+    <columns>
+      <!-- column-name accountname -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkListStore" id="fingerprint_store">
+    <columns>
+      <!-- column-name id -->
+      <column type="gint"/>
+      <!-- column-name screenname -->
+      <column type="gchararray"/>
+      <!-- column-name trust -->
+      <column type="gchararray"/>
+      <!-- column-name fingerprint -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkNotebook" id="notebook1">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <signal name="switch-page" handler="update_context_list" after="yes" swapped="no"/>
+    <child>
+      <object class="GtkVBox" id="vbox4">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="border_width">12</property>
+        <property name="spacing">10</property>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="height_request">200</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hscrollbar_policy">automatic</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <child>
+              <object class="GtkTreeView" id="fingerprint_view">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="model">fingerprint_store</property>
+                <property name="search_column">0</property>
+                <property name="tooltip_column">3</property>
+                <signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
+                <child>
+                  <object class="GtkTreeViewColumn" id="name_column">
+                    <property name="resizable">True</property>
+                    <property name="title">Name</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertext2"/>
+                      <attributes>
+                        <attribute name="text">1</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="trust_column">
+                    <property name="resizable">True</property>
+                    <property name="title">Trust</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertoggle1"/>
+                      <attributes>
+                        <attribute name="text">2</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="fingerprint_column">
+                    <property name="resizable">True</property>
+                    <property name="title">Fingerprint</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertext4"/>
+                      <attributes>
+                        <attribute name="markup">3</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox3">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">5</property>
+            <child>
+              <object class="GtkButton" id="trust_button">
+                <property name="label" translatable="yes" comments="button">Trust/Revoke Fingerprint</property>
+                <property name="width_request">200</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <signal name="clicked" handler="trust_button_clicked_cb" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child type="tab">
+      <object class="GtkLabel" id="label3">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes" comments="tab label">Contact</property>
+      </object>
+      <packing>
+        <property name="tab_fill">False</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkVBox" id="vbox1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="border_width">12</property>
+        <property name="spacing">10</property>
+        <child>
+          <object class="GtkHBox" id="hbox1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">10</property>
+            <child>
+              <object class="GtkLabel" id="fingerprint_label_desc1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes" comments="Descriptive label">Own Fingerprint:</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="fingerprint_label_own">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label">&lt;tt&gt;-------- -------- -------- -------- --------	&lt;/tt&gt;</property>
+                <property name="use_markup">True</property>
+                <property name="selectable">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow2">
+            <property name="height_request">100</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hscrollbar_policy">automatic</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <child>
+              <object class="GtkTreeView" id="fingerprint_view_own">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="has_tooltip">True</property>
+                <property name="model">fingerprint_store</property>
+                <property name="headers_clickable">False</property>
+                <property name="search_column">0</property>
+                <property name="tooltip_column">3</property>
+                <signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
+                <child>
+                  <object class="GtkTreeViewColumn" id="name_column1">
+                    <property name="resizable">True</property>
+                    <property name="title">Name</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertext1"/>
+                      <attributes>
+                        <attribute name="text">1</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="trust_column1">
+                    <property name="resizable">True</property>
+                    <property name="title">Trust</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertoggle2"/>
+                      <attributes>
+                        <attribute name="text">2</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="fingerprint_column1">
+                    <property name="resizable">True</property>
+                    <property name="title">Fingerprint</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertext3"/>
+                      <attributes>
+                        <attribute name="markup">3</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox4">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">5</property>
+            <child>
+              <object class="GtkButton" id="trust_button1">
+                <property name="label" translatable="yes" comments="button">Trust/Revoke Fingerprint</property>
+                <property name="width_request">200</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <signal name="clicked" handler="trust_button_clicked_cb" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child type="tab">
+      <object class="GtkLabel" id="label1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Own Devices</property>
+      </object>
+      <packing>
+        <property name="position">1</property>
+        <property name="tab_fill">False</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkMenu" id="fprclipboard_menu">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkMenuItem" id="copyfprclipboard_item">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes" comments="Context menu item">Copy to clipboard</property>
+        <property name="use_underline">True</property>
+        <signal name="activate" handler="clipboard_button_cb" swapped="no"/>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/omemo/manifest.ini b/omemo/manifest.ini
new file mode 100644
index 00000000..ee82b117
--- /dev/null
+++ b/omemo/manifest.ini
@@ -0,0 +1,11 @@
+[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
diff --git a/omemo/omemo.png b/omemo/omemo.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d1c0fae2c27a5194ee8bb77c66a9b89ccdadce2
GIT binary patch
literal 5759
zcmZ{oRan$fx5oc7Ln91GDXoAYI&?|RP$JSuN)FwPlrTdhC?HZ25=u$8bR#0&F-Ug|
zh(pKWJLlp&=i==BtoQfqz4p7-_1f<=)D+1`=t%$oAX9oFuXRhl{{#iWtrEs_bln2a
zMn+Wz04ichFOm4SXI6_BTB-ox_Xq&cp#X4xdxhQv0Iyr)wiy73rvLz*YkGsG<ZXh$
zTv<{6mbdFnt`T($V%HZ29=Ck`-{H#kBLDzsIF;mOw0&mxQoZ$c^co@-0=RX^n@=7^
z2`Ny^bU)}Zu@JIPsNfK<==x$C*&~p2?<)brJC2GR{-~bm9LkDhE?PVp{O<6GL6F+q
zN$dbdrCCr^u)bapiiNjsJo7KXm0&L>{}eIV=Dd59`hVqmtyd^s%_E`OujZRK{NulQ
zM<pgL)ZUlth|wI$>M#p(>Yw?$A4Y?@$l)}a#5wmc-Al}yd%5m&I~+Br3mbaw_%39@
zw`?Z%4_lAyPBoS0J2(<cu4~xXE=LK%dunG%vEB?5lGf0tukX2a2@~1EUJ-Mn<6#pL
zAl-T+F1#ZNe@5qMzIwwN?+{Aqohx{qvj<{y@Ch~4swz9Sv1hb{!93PrK*I#j`-AWM
zd-$}evx(=mW{$>N(|j~9C)S6$m0h#>m(t(~|K1mU%Q;_=1of3V2}oMYL@YGBOC3Bc
zK6g4wRvUOA8_gE)!H)-UDfrSO=H)sq(PVG*OssHTXA@FGZ>fViCsWl@w0pkzu4y&q
zmAKcJlZ)e^W6vTw`!5cm0e-StKTp^Gw3t16z&>zDj>hRINB~!9JyyU3|M@fEM3}|~
zAg^CXL0ldEmW;VdChYDAE_DXRXMCt**ps@dQ$nBEhLN7~?&&bzC#||%=DjEmZ}$%2
zX5<WhRAW%tAc3||X<|UTs;1zXy$~pY*q)A3leoV91u;G2a?{kLLluqGMSaaMZBYHI
zWi`h#T_fsoQOG$%TvsA{l)%=fi2d*xTpBGWBBlV0ql(~!t}fMqRlg~?T30p~_Gx=*
z)K-Gc+b|W*y4~jGj%S|TGt>t)q754vXt`QnJ<z(n8hJ=%R;9~!Ogs8ryk36(<tVYn
zWfhq`Lk`(U_~}Az#nDyQc29o0Tc%x$4|~@!^!=-O-v%;FzM->dF`lYukv?$7I%5IM
z$S;YZ#T2Uv<5VfvA-xMSE`5~i>Db+c4!b7aE6DS`i+QkVlNa+;#+a??<;DC)et*!<
zdL1va=&WthBjw+}G&1Po`Ls!|uS^S>;e@Ywh^})cgv&AJNL@1gF{2|s>F}R)QPQn0
zq3N9x-h<>pK{!|-#)JB=wSDF?R->Hzri1-nLllLsobJ2T+|4s(4#c&`lSh*rGwgNS
zLI(qyoxj7UT<FhIe@C{}T~u$cHN&OfMsh@YT<Wqjg;h@N7|G)k1toTbteDlAP+F@%
zvWpIQ&<A<tP_(BBM5C1Yp)Xj^D^Zluf%Wi%v;muSkY2_~-Sv~KAB%q%T?QyBWvp(N
zd5q@uCMyi?PdnOwx>>i1B#CMruK^X?=-0j)B0y%1o6hJXSr4-+Dr2kFK~{9J#auRA
zemi}|avN<^>J!b&c#P9--bCZEV+;IFLGvuiHDnE@+@Q)EC;Sa&U5@wfdOgmc*JfAl
z%u-f%Z2wavZ#RyPa%pW!!4KaI-#j0oD6bi)*0Vv3#}K<XqDW>Jd0vSE$yfH_KKVg*
zb-(D)Bh?Ty!6{(1(^3Jz&D3-!sv}yB%lLN*HvSC0%qPpbX$$Z%;t`Xqx&!tZgRj1s
zk#gl3Y7ohNW|_zP`*M$&t9NLq+5ghzVu`;y-y7jQJmCEzngp~{*vAGWF(vxTjEE7F
zo}dcbZpx_4>Qn;A7-O32rT;!iOCDgdTKV4ewn8B@lI>)?l?6XgaH#nn3ka8J%Dm-9
zuEz0}y{Ym}48FBbStCJqA~F6IL?&_E<*?_MYfq!fRxPw^eR&FCqzH#>{t+C9OvAiA
z$hg4DrqS)&dNTX$9&~rx50S%pwNs1h?~>C(_1Y`7x5z2>w%yB?xY^$q?^YI3m)ev)
zyF51%Lv2tmd~SDSt6lm4cQ24y3g#tpmJL1NZhBGtnM}j5C=*cke-XAVdTBAD$s9j^
zqNubo_+qv+|J$DddeX^D3|_Tr1NP?G9h9hD4vIT(m1#g#ZNyS8<ySofu}Mp)S4Aqn
zH%YXXJ1ng0<mJXD=Jf0p9L98<HW|@>!JPA`Ci5h%D$8(m(k)MPmDaxTNl%b@arvFB
z{>B!VahqMxO|C8*qUEKCmd{Rp%xN8&F=c6}p)-wz)3}+q`4<o?vn3t+h;2>}h$qyC
zw)ztH<?mm*tqO|SwhAMypam+$4SGkQ1nfwRFA_rA-1imRAM8K>+b`Is`Zx&*q#Ham
z5LCN4LWnVYyk3)3@=#w{`sTLsf&C`!I4$&taaukun&l2$GT_3om_BAl(tA2}X*#fQ
z@+6kS_^pYS%?7;s#G4a2SM%L)adk3pct!Gk|G&poRgn6QoQz63bhmq$AiDl}HH5FQ
zI~ge2H$Epq6bo*o{R!z+$`fW#YmX)6<!-1!$BYCF<%xBAgY*Jw>*apaJz8);%s5_2
zD@eH7Vy}a4d=_!=z1Xs^SlSy~pIH;k8Z8MfhQLeM>@abg7VV>N1Typ0626xe-QHb0
zPUXzh`on46N$jp6-;p++cEaP=4K|3Q?pdpo@b-+z(J6zkuvKF3CHK#x%3Hbr{-!Wi
zKeAOFroSk0BVGBs{LnB_d$;syNgtzVI8C?POjPLkA=7K2NU8*XiRrU0SISWHc04}q
z5qsjci&UzWwn>r393QqOn<u_YGwP%71g$y{0$DSOx=`blEHAm7SoLSssW6)IOYUOK
z8CB#yP4&2&K(!C@bD0sD*}U3g80V7|QnZ8!@F@2Tf-W-7{73l1b4iI4UEoA-pn5mq
zpEUL(C2^_~xxsOSjKO^$VLS1J*iuq(frG{F<9_^hIetyAz5KQ`+EkV?8v8jNyq1E9
zrUz>ieY$*lhs_dgAu?-Kpnr#XPtobTMJ(dXp-%?9GSAtAEHZS|D`4#EQxQz>gGE|>
zx9j*>`rhac3DrPa4y&E3M%MfGQ|@5j+w6Q5ApOr)LsdVve|4mKoxlFZW#MX%nW6Q(
z!=cD|C-#@pXXP(7>L{f4mmSGj)Dt!t>nB1G3hOw4Vq+CYVK#r!d;(PK{@4KA;zi}`
zIhY!BvW?hVNw!+PCMo74rp~)u7e1TyH){S<fzrLnwBzCq4<F+RD9%HNRV}N|NCLU6
zLtxer1i+t62Yk<U=S8m}jlY8AO((%(E|s-Fx!cHK!(B*?a*<OJq+JGkWaq=eg}qrc
z9z+Ku31D35+_kXmnBI_r(k0H07(}7+WFC&*R^K(?V6c`Cvw61mb&s#}Z5OC5J8i-q
zz4~OY>9tSD{6%N_s8buRz{C55Dh~C~e@*o9BDF^@F8DD8<^Hjof`hGYejtOeQ2LH!
z6DqqqZ>o%(n|XD1Ilup6B>jbSQ?{7b=8hcScCbrti0eV-{xD5j3UZ&cP4?yWsjBey
zKn#!#VT9wQTs2wTLA>m1Vau8|@gE*0dyd!V*iayl?V?j$XG3Y5s?bnB56TaK4ApQ2
znVe8b2AFgpT)a{rLiv7`gb4u#Ne7H*b8diwqjMRL9OOeQtW0=7VmC>Qh@^-IrPc@%
zNSZ=x@}F=f&V=aLG^#T?WS2%OJ!yqzv!%~cK@t@!C+sRTF5fo$V-*(vob+UK_-CCF
zgqe=0-7B`WL-L@%?V_`RlUn;s&^Y^R%n^nN#Ly}Gl_g{d+H!U+u$cm19rK1|SvJir
zne1f;=C?2*<_+^6bl|(zuj4Ebd!~WGWTq)Cck9r55$D1jur@4}>OFK}ha?T)kbuI?
zmBc5V_B=guMXNn%5d<Fo@ix4Cm3_c0;H!>=7MQ-;wj>2qLi#0u)u756;Jbg60nowi
z2e`}d_Cr|l?Lry&Z{8tE?M(PvVb38cgzpA83}sz_Gx;O&D-k~StsxOlf=Y@lqUC%1
z5T=y0=x(5_<^XfdMJ(GYI*aKim|*l3M{j|L09yqLv{Q>cGuo!!*bvQXJW(%5>6(3U
zy|m*Ibf#7J-9~3JVF6q(r?Zf=o;BPD2lLhP>wP4Bo@<BxalyM{E0V%AV2q$ND|J34
zvD^=a0}3A?SnNnD4w$6V@dB>vgwg{2Y>jz~Op)7wm(%wUTp`e*iv-Yb<0wE`;;<=@
zkf%TiczB*Koq$h8cpXfX@r^Q;zNmFJs7DlHSV57sFL5N=#~5ezm|J^E!#`*C>oQE^
z>BDhq=p?Cj7_h%odjy1XR!RZO`4~KkmK->GKYoc8p`ddmhqhd3s{))ICi48$`R0;g
zTt?(CScs~nhopTN0rJ+edcPu3uI05BpifpjN-le|s^iLAFo<pZM(GBY3$oHq$@~O0
z=NcvcKCy7`3>72#23V&umqyIf^{Bz6iUSpO{bk_5OLMH#NCQ9wW&!oilKcT;xPP+&
zi6z^O5To=Pm7>x1TJp6|v!=m2QcotQe<LP8E#4HetQ(t?qHRk0@zn;+&iZR}twFXn
z(m1Yb#-{|oHYkZ@<I3+N`Vrz3Rqb~XB6|9yte&);ph^t$E`(@jLkM4SlW)ME28~Se
zl|o<Y;1nl)5eb&9OA&sp4fnG!VYZv(%ihacfTBcE7~sufWZ?N%v0zi@%(v!7{Yv-g
zr^dd8h}o8D0|}W<V+%tdX>^a}T`R?7M}P~iH$}#JQ+^!#y0tL*b7NH6QbhKGb1w^6
z!$SrILD*7A0|zK13cR@X#+U}Nh>%cRQ7SJ<kSdfQ)FlY(@MJvbI#GWrYK9m03y-7C
zLN1pO(XFU9zk)N9>Tjt3z-;dstL3iM){?A12k;xJfEF3Mf$){}JLruaPvu0W#pijI
z7KU&!n`*z3DF=$V(VcWxyr<`nJ3#%P#mtFwu~N!DTI4e;<h%1iWXxPGCHj9@7)ui9
zw%-I*XgNZ%OmhF>@zEqto8rRQ3Kch)49I2&XL)895LcTG4YM4>j&8#nOm~-N<3}ZP
znR>4*R{%UcJ6y!+0@lxB{hL&h5nC}JR<qTPhYlW?tuZVAM6y@lC`sB^73kL}exDIM
zPWHES(TQPmt(@p7{swOe6r3QYgCaDecZi|$My$Tr`8pJX{^?e98qO<Inxw>8l<^pd
z!+-I~%lO;%UBvq1y}r1?VSZOks`?;8uDtY>DsKUUZ=cuUOWT=+Qqwg^TX+l*4t~Xl
z_>(v~2bg}DAm}QI_@bvp1tkbUMxPNTq38DFXFh*wU$L*U!{GaVa1=sUmKGlm>T9sT
zQYzt~r&oOh>w$Q&13Kb|cQG&h4n+_nmwkn|^R>f1gEt}-dwrl><r2fojQcV4X?0@e
z8OJkD4*}<sGQH1Jzy0qoLmA;|>7v8J9nJY~jn90${iW%IURSd&@E8)ZdM+yi-CCbH
z5Q9q_4uG{6yal|dl)0@fb{a-~qR?uGiCh~hyf#^2dY;Ui`9_4${5WA%`zK<f|5P9A
zMj=(#K2}X_RK$~)5O`-F;?z*sAa@|Y20J4MF0zv#AZ^~&S0K;KEzU}^z<U~vC2JOu
zJd8Fqx`bcGnxq1pmOS@+{uR!#PWGI;9Xe+P-AzYaMuh>}J0vi)BP$h9yz}<B`;s<&
zETh#mkUacvC1E$Wt8#@}J1`Nz@CZx|EAR=XWZo&Ny>BSj9L7)H#xr8hiN27Tiv@Lf
z7>5A6Jq1;uRqwC}Kv}{X^@`fyM5sh=c3nu9<K<ytVhn{=8i#(wbi?%CZpD?eM&P3;
zH}CM7=c}@Fu*08C$ssra9F}GbRN5bqgZ~2B=toYk<q|H10!0wM*i&`6G)g`>Bg8^1
zbK}4N4lv#ouv=o8u(FIcyTI8sE+6|3@|EpiB;0~_7!vO<iVulXP^FKz{@BYQK$>Z-
zUcOpnTI3&D<4Op%a>2X-I^?(|Skj@=pKEbFEv#y^8vsQp1btcHNZPhE++n<3k#$U;
z(;JEI;#aEw#ayl?I$w6<L{wEk5tA!hW424O$oI=#64CbTLIapO6zK$wlhk|O6vs=G
zEtB;Kw-l-Yg_&Pe@bw!+{n;=lmZVb;4ii>SJ`J}rCtH(pI8}A|BdbUvVTm-P!i#5(
zKbLfJH7x8>*`IeRuoPl`^^6tRXTqxws(3ATdwjTMOV7XTO3rF(F4vo##JR{lw4Wzb
zB~9BcMpP47*Yzuy5Q=j?Sg@v>_WJbUdky$RA!+A*NW0e8QE;Sa_W*!N5)oLJ&Jjgq
z^c>K)l-ropzf$4xQQx&`EU$K6Q}s~8rq@8(1i+I{2E#vHu0uyO8)nIq44PTO$N|^C
zHBsO!770KlGbNX7C8kFwA)}lcEjW60xHS*W2fe>ON079AZBKmkW74NC{W9XC{b{}1
zx&LLfbJtT<<F8<U*@Yns8-M9AO=2Wg92YU|2~BjOv;{jYXgHv~6O?nW!k>ZQ=y3g&
zeAYnYi_a~v&9LO_2KRzq6yG;<b{D!qDSw0w(batY`1bL4qoH6bnf$3MR`?8@^Zgd^
zm<f|N;}~Q>T;VU(3U0J`r!@MSwJHBMDX_s>6axI^HsD6<W-~xs*A+gJwk5%)V!_m$
zx@rW6;j2SU7V$g<N!msSH<BP!3@%2rgl+9Wt?JK|Y($4S-ON-6O<Z@9@ni<!uu9ci
z2*O`;ilO@B{mcb>Xw?SIQ=YN58JgI0y|d00y>G|gH?F)S{&Fjs{)+Q7S{X5xt?Yi?
zP2oCgNt1VnAUk(AMuQ-$NVExWSXJ<Hq_)Vh<lCIO$fK<C!;_s>?L`zaG<hre0u%DD
zVuIY`DPmlhQ9XcE{?Na>w@hYyUJdn=P`(1QCxxFP9+<bC9TscqP;7d7wEMkIiM~Q}
z^y3E<++g~Ll8QsRHXIl4*@v&R()02=<F$k0e&(aa@GEQAoi{lrz0z?Gb8#v3cXCcw
zJGUBTs1^G^^aruXjfQ@qT1+yXpM(+wahaq_IFsAXH>8E+%QP~z*0S8_U5+LHMX6Oj
zIDj3nV9UxygY+B|^V*5c*3OPTYj}C6Pws8--MDM|t>L?R5ns1yqN@ih>c2=Y9#mVf
zm<r!9IA=ORM`tRPL}4#ygU;)a|C&;k;w1eI*V>+G>^<A|@sy(Wd%d6&#aHX5^FTdr
z+}U&|gc*h`^Tlm3mj$2hUR^QGFP#%DAAlp0TWjGrZ^RsH(}~EBV_-07JHlmaD#_|Y
z>`f1iEdsq<><G4usnRS+&qjn0)-G<GI$Vs&Q>hA#{I7JO?%m)kn4D}{1<c+4umT*O
z3i_UwNKY$q3wNtq00Mmc!Z3aT7{9PKzlgXHzc`=h6TVyJ>kzr5{67XK7fU;9-~ZoG
z<se^jYf$@72ah*4;^s&XE0~wN9bj$eXeG|e%WLc6WX0>{Vdc)t<LF|6bmaB0MY>z@
w+92)ioOoRwy=?59J$P^5IJx|fnmW4JxbWPT!eh_exvdN+DX7Vp%9;iL7q#>ADgXcg

literal 0
HcmV?d00001

diff --git a/omemo/omemo/__init__.py b/omemo/omemo/__init__.py
new file mode 100644
index 00000000..3f5c4a7d
--- /dev/null
+++ b/omemo/omemo/__init__.py
@@ -0,0 +1 @@
+__version__ = "0.1.0"
diff --git a/omemo/omemo/aes_gcm.py b/omemo/omemo/aes_gcm.py
new file mode 100644
index 00000000..67e7328f
--- /dev/null
+++ b/omemo/omemo/aes_gcm.py
@@ -0,0 +1,42 @@
+# -*- 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
diff --git a/omemo/omemo/aes_gcm_fallback.py b/omemo/omemo/aes_gcm_fallback.py
new file mode 100644
index 00000000..f157a22e
--- /dev/null
+++ b/omemo/omemo/aes_gcm_fallback.py
@@ -0,0 +1,152 @@
+# -*- 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)
diff --git a/omemo/omemo/aes_gcm_native.py b/omemo/omemo/aes_gcm_native.py
new file mode 100644
index 00000000..77815731
--- /dev/null
+++ b/omemo/omemo/aes_gcm_native.py
@@ -0,0 +1,61 @@
+# -*- 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
diff --git a/omemo/omemo/db_helpers.py b/omemo/omemo/db_helpers.py
new file mode 100644
index 00000000..dc95d6c4
--- /dev/null
+++ b/omemo/omemo/db_helpers.py
@@ -0,0 +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]
diff --git a/omemo/omemo/encryption.py b/omemo/omemo/encryption.py
new file mode 100644
index 00000000..e4d4fd89
--- /dev/null
+++ b/omemo/omemo/encryption.py
@@ -0,0 +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
diff --git a/omemo/omemo/liteaxolotlstore.py b/omemo/omemo/liteaxolotlstore.py
new file mode 100644
index 00000000..64f14b31
--- /dev/null
+++ b/omemo/omemo/liteaxolotlstore.py
@@ -0,0 +1,168 @@
+# -*- 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)
diff --git a/omemo/omemo/liteidentitykeystore.py b/omemo/omemo/liteidentitykeystore.py
new file mode 100644
index 00000000..29974bcb
--- /dev/null
+++ b/omemo/omemo/liteidentitykeystore.py
@@ -0,0 +1,167 @@
+# -*- 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 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, _id, trust):
+        q = "UPDATE identities SET trust = ? WHERE _id = ?"
+        c = self.dbConn.cursor()
+        c.execute(q, (trust, _id))
+        self.dbConn.commit()
diff --git a/omemo/omemo/liteprekeystore.py b/omemo/omemo/liteprekeystore.py
new file mode 100644
index 00000000..78ffc7ad
--- /dev/null
+++ b/omemo/omemo/liteprekeystore.py
@@ -0,0 +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)
diff --git a/omemo/omemo/litesessionstore.py b/omemo/omemo/litesessionstore.py
new file mode 100644
index 00000000..d8ef66c1
--- /dev/null
+++ b/omemo/omemo/litesessionstore.py
@@ -0,0 +1,130 @@
+# -*- 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 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 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 getActiveSessionsKeys(self, recipientId):
+        q = "SELECT record FROM sessions WHERE active = 1 AND recipient_id = ?"
+        c = self.dbConn.cursor()
+        result = []
+        for row in c.execute(q, (recipientId,)):
+            public_key = (SessionRecord(serialized=row[0]).
+                          getSessionState().getRemoteIdentityKey().
+                          getPublicKey())
+            result.append(public_key.serialize())
+        return result
+
+    def getAllActiveSessionsKeys(self):
+        q = "SELECT record FROM sessions WHERE active = 1"
+        c = self.dbConn.cursor()
+        result = []
+        for row in c.execute(q):
+            public_key = (SessionRecord(serialized=row[0]).
+                          getSessionState().getRemoteIdentityKey().
+                          getPublicKey())
+            result.append(public_key.serialize())
+        return result
+
+    def getInactiveSessionsKeys(self, recipientId):
+        q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?"
+        c = self.dbConn.cursor()
+        result = []
+        for row in c.execute(q, (recipientId,)):
+            public_key = (SessionRecord(serialized=row[0]).
+                          getSessionState().getRemoteIdentityKey().
+                          getPublicKey())
+            result.append(public_key.serialize())
+        return result
diff --git a/omemo/omemo/litesignedprekeystore.py b/omemo/omemo/litesignedprekeystore.py
new file mode 100644
index 00000000..d6e4a908
--- /dev/null
+++ b/omemo/omemo/litesignedprekeystore.py
@@ -0,0 +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()
diff --git a/omemo/omemo/sql.py b/omemo/omemo/sql.py
new file mode 100644
index 00000000..25571f8e
--- /dev/null
+++ b/omemo/omemo/sql.py
@@ -0,0 +1,147 @@
+# -*- 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()
+
+    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,
+                    timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP
+                    );
+                '''
+
+            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 entrys and delete them
+        if user_version(self.dbConn) < 2:
+            delete_dupes = """ DELETE FROM identities WHERE _id not in (
+                                SELECT MIN(_id)
+                                FROM identities
+                                GROUP BY
+                                recipient_id, public_key
+                                );
+                            """
+
+            self.dbConn.executescript(""" BEGIN TRANSACTION;
+                                     %s
+                                     PRAGMA user_version=2;
+                                     END TRANSACTION;
+                                 """ % (delete_dupes))
+
+        if user_version(self.dbConn) < 3:
+            # Create a UNIQUE INDEX so every public key/recipient_id tuple
+            # can only be once in the db
+            add_index = """ CREATE UNIQUE INDEX IF NOT EXISTS
+                            public_key_index
+                            ON identities (public_key, recipient_id);
+                        """
+
+            self.dbConn.executescript(""" BEGIN TRANSACTION;
+                                          %s
+                                          PRAGMA user_version=3;
+                                          END TRANSACTION;
+                                      """ % (add_index))
+
+        if user_version(self.dbConn) < 4:
+            # Adds column "active" to the sessions table
+            add_active = """ ALTER TABLE sessions
+                             ADD COLUMN active INTEGER DEFAULT 1;
+                         """
+
+            self.dbConn.executescript(""" BEGIN TRANSACTION;
+                                          %s
+                                          PRAGMA user_version=4;
+                                          END TRANSACTION;
+                                      """ % (add_active))
+
+        if user_version(self.dbConn) < 5:
+            # Adds DEFAULT Timestamp
+            add_timestamp = """
+                DROP TABLE signed_prekeys;
+                CREATE TABLE IF NOT EXISTS signed_prekeys (
+                    _id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    prekey_id INTEGER UNIQUE,
+                    timestamp NUMERIC DEFAULT CURRENT_TIMESTAMP, record BLOB);
+                ALTER TABLE identities ADD COLUMN shown INTEGER DEFAULT 0;
+                UPDATE identities SET shown = 1;
+            """
+
+            self.dbConn.executescript(""" BEGIN TRANSACTION;
+                                          %s
+                                          PRAGMA user_version=5;
+                                          END TRANSACTION;
+                                      """ % (add_timestamp))
diff --git a/omemo/omemo/state.py b/omemo/omemo/state.py
new file mode 100644
index 00000000..dd06e05e
--- /dev/null
+++ b/omemo/omemo/state.py
@@ -0,0 +1,412 @@
+# -*- 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/>.
+#
+
+import logging
+import time
+from base64 import b64encode
+
+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
+from axolotl.sessioncipher import SessionCipher
+from axolotl.state.prekeybundle import PreKeyBundle
+from axolotl.util.keyhelper import KeyHelper
+from Crypto.Random import get_random_bytes
+
+from .aes_gcm import NoValidSessions, decrypt, 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
+TRUSTED = 1
+UNDECIDED = 2
+
+
+class OmemoState:
+    def __init__(self, own_jid, connection, account, plugin):
+        """ Instantiates an OmemoState object.
+
+            :param connection: an :py:class:`sqlite3.Connection`
+        """
+        self.account = account
+        self.plugin = plugin
+        self.session_ciphers = {}
+        self.own_jid = own_jid
+        self.device_ids = {}
+        self.own_devices = []
+        self.store = LiteAxolotlStore(connection)
+        self.encryption = self.store.encryptionStore
+        for jid, device_id in self.store.getActiveDeviceTuples():
+            if jid != own_jid:
+                self.add_device(jid, device_id)
+            else:
+                self.add_own_device(device_id)
+
+        log.info(self.account + ' => Roster devices after boot:' +
+                 str(self.device_ids))
+        log.info(self.account + ' => Own devices after boot:' +
+                 str(self.own_devices))
+        log.debug(self.account + ' => ' +
+                  str(self.store.preKeyStore.getPreKeyCount()) +
+                  ' PreKeys available')
+
+    def build_session(self, recipient_id, device_id, bundle_dict):
+        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:])
+
+        signedPreKeyPublic = DjbECPublicKey(bundle_dict['signedPreKeyPublic'][
+            1:])
+        identityKey = IdentityKey(DjbECPublicKey(bundle_dict['identityKey'][
+            1:]))
+
+        prekey_bundle = PreKeyBundle(
+            registration_id, device_id, bundle_dict['preKeyId'], preKeyPublic,
+            bundle_dict['signedPreKeyId'], signedPreKeyPublic,
+            bundle_dict['signedPreKeySignature'], identityKey)
+
+        sessionBuilder.processPreKeyBundle(prekey_bundle)
+        return self.get_session_cipher(recipient_id, device_id)
+
+    def set_devices(self, name, devices):
+        """ Return a an.
+
+            Parameters
+            ----------
+            jid : string
+                The contacts jid
+
+            devices: [int]
+                A list of devices
+        """
+
+        self.device_ids[name] = devices
+        log.info(self.account + ' => Saved devices for ' + name)
+
+    def add_device(self, name, device_id):
+        if name not in self.device_ids:
+            self.device_ids[name] = [device_id]
+        elif device_id not in self.device_ids[name]:
+            self.device_ids[name].append(device_id)
+
+    def set_own_devices(self, devices):
+        """ Overwrite the current :py:attribute:`OmemoState.own_devices` with
+            the given devices.
+
+            Parameters
+            ----------
+            devices : [int]
+                A list of device_ids
+        """
+        self.own_devices = devices
+        log.info(self.account + ' => Saved own devices')
+
+    def add_own_device(self, device_id):
+        if device_id not in self.own_devices:
+            self.own_devices.append(device_id)
+
+    @property
+    def own_device_id(self):
+        reg_id = self.store.getLocalRegistrationId()
+        assert reg_id is not None, \
+            "Requested device_id but there is no generated"
+
+        return ((reg_id % 2147483646) + 1)
+
+    def own_device_id_published(self):
+        """ Return `True` only if own device id was added via
+            :py:method:`OmemoState.set_own_devices()`.
+        """
+        return self.own_device_id in self.own_devices
+
+    @property
+    def bundle(self):
+        self.checkPreKeyAmount()
+        prekeys = [
+            (k.getId(), b64encode(k.getKeyPair().getPublicKey().serialize()))
+            for k in self.store.loadPreKeys()
+        ]
+
+        identityKeyPair = self.store.getIdentityKeyPair()
+
+        self.cycleSignedPreKey(identityKeyPair)
+
+        signedPreKey = self.store.loadSignedPreKey(
+            self.store.getCurrentSignedPreKeyId())
+
+        result = {
+            'signedPreKeyId': signedPreKey.getId(),
+            'signedPreKeyPublic':
+            b64encode(signedPreKey.getKeyPair().getPublicKey().serialize()),
+            'signedPreKeySignature': b64encode(signedPreKey.getSignature()),
+            'identityKey':
+            b64encode(identityKeyPair.getPublicKey().serialize()),
+            'prekeys': prekeys
+        }
+        return result
+
+    def decrypt_msg(self, msg_dict):
+        own_id = self.own_device_id
+        if msg_dict['sid'] == own_id:
+            log.info('Received previously sent message by us')
+            return
+        if own_id not in msg_dict['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 = msg_dict['keys'][own_id]
+
+        try:
+            key = self.handlePreKeyWhisperMessage(sender_jid, sid,
+                                                  encrypted_key)
+        except (InvalidVersionException, InvalidMessageException):
+            try:
+                key = self.handleWhisperMessage(sender_jid, sid, encrypted_key)
+            except (NoSessionException, InvalidMessageException) as e:
+                log.warning('No Session found ' + e.message)
+                log.warning('sender_jid =>  ' + str(sender_jid) +
+                            ' sid =>' + sid)
+                return
+            except (DuplicateMessageException) as e:
+                log.warning('Duplicate message found ' + str(e.args))
+                return
+
+        except (DuplicateMessageException) as e:
+            log.warning('Duplicate message found ' + str(e.args))
+            return
+
+        result = decrypt(key, iv, payload).decode('utf-8')
+
+        log.debug("Decrypted Message => " + result)
+        return result
+
+    def create_msg(self, from_jid, jid, plaintext):
+        key = get_random_bytes(16)
+        iv = get_random_bytes(16)
+        encrypted_keys = {}
+
+        devices_list = self.device_list_for(jid)
+        if len(devices_list) == 0:
+            log.error('No known devices')
+            return
+
+        for dev in devices_list:
+            self.get_session_cipher(jid, dev)
+        session_ciphers = self.session_ciphers[jid]
+        if not session_ciphers:
+            log.warning('No session ciphers for ' + jid)
+            return
+
+        # Encrypt the message key with for each of receivers devices
+        for rid, cipher in session_ciphers.items():
+            try:
+                if self.isTrusted(cipher) == TRUSTED:
+                    encrypted_keys[rid] = cipher.encrypt(key).serialize()
+                else:
+                    log.debug('Skipped Device because Trust is: ' +
+                              str(self.isTrusted(cipher)))
+            except:
+                log.warning('Failed to find key for device ' + str(rid))
+
+        if len(encrypted_keys) == 0:
+            log_msg = 'Encrypted keys empty'
+            log.error(log_msg)
+            raise NoValidSessions(log_msg)
+
+        my_other_devices = set(self.own_devices) - set({self.own_device_id})
+        # Encrypt the message key with for each of our own devices
+        for dev in my_other_devices:
+            try:
+                cipher = self.get_session_cipher(from_jid, dev)
+                if self.isTrusted(cipher) == TRUSTED:
+                    encrypted_keys[dev] = cipher.encrypt(key).serialize()
+                else:
+                    log.debug('Skipped own Device because Trust is: ' +
+                              str(self.isTrusted(cipher)))
+            except:
+                log.warning('Failed to find key for device ' + str(dev))
+
+        payload = encrypt(key, iv, plaintext)
+
+        result = {'sid': self.own_device_id,
+                  'keys': encrypted_keys,
+                  'jid': jid,
+                  'iv': iv,
+                  'payload': payload}
+
+        log.debug('Finished encrypting message')
+        return result
+
+    def isTrusted(self, cipher):
+        self.cipher = cipher
+        self.state = self.cipher.sessionStore. \
+            loadSession(self.cipher.recipientId, self.cipher.deviceId). \
+            getSessionState()
+        self.key = self.state.getRemoteIdentityKey()
+        return self.store.identityKeyStore. \
+            isTrustedIdentity(self.cipher.recipientId, self.key)
+
+    def getTrustedFingerprints(self, recipient_id):
+        inactive = self.store.getInactiveSessionsKeys(recipient_id)
+        trusted = self.store.getTrustedFingerprints(recipient_id)
+        trusted = set(trusted) - set(inactive)
+
+        return trusted
+
+    def getUndecidedFingerprints(self, recipient_id):
+        inactive = self.store.getInactiveSessionsKeys(recipient_id)
+        undecided = self.store.getUndecidedFingerprints(recipient_id)
+        undecided = set(undecided) - set(inactive)
+
+        return undecided
+
+    def device_list_for(self, jid):
+        """ Return a list of known device ids for the specified jid.
+
+            Parameters
+            ----------
+            jid : string
+                The contacts jid
+        """
+        if jid == self.own_jid:
+            return set(self.own_devices) - set({self.own_device_id})
+        if jid not in self.device_ids:
+            return set()
+        return set(self.device_ids[jid])
+
+    def devices_without_sessions(self, jid):
+        """ List device_ids for the given jid which have no axolotl session.
+
+            Parameters
+            ----------
+            jid : string
+                The contacts jid
+
+            Returns
+            -------
+            [int]
+                A list of device_ids
+        """
+        known_devices = self.device_list_for(jid)
+        missing_devices = [dev
+                           for dev in known_devices
+                           if not self.store.containsSession(jid, dev)]
+        if missing_devices:
+            log.info(self.account + ' => Missing device sessions for ' +
+                     jid + ': ' + str(missing_devices))
+        return missing_devices
+
+    def get_session_cipher(self, jid, device_id):
+        if jid not in self.session_ciphers:
+            self.session_ciphers[jid] = {}
+
+        if device_id not in self.session_ciphers[jid]:
+            cipher = SessionCipher(self.store, self.store, self.store,
+                                   self.store, jid, device_id)
+            self.session_ciphers[jid][device_id] = cipher
+
+        return self.session_ciphers[jid][device_id]
+
+    def handlePreKeyWhisperMessage(self, recipient_id, device_id, key):
+        preKeyWhisperMessage = PreKeyWhisperMessage(serialized=key)
+        if not preKeyWhisperMessage.getPreKeyId():
+            raise Exception("Received PreKeyWhisperMessage without PreKey =>" +
+                            recipient_id)
+        sessionCipher = self.get_session_cipher(recipient_id, device_id)
+        try:
+            log.debug(self.account +
+                      " => Received PreKeyWhisperMessage from " +
+                      recipient_id)
+            key = sessionCipher.decryptPkmsg(preKeyWhisperMessage)
+            # Publish new bundle after PreKey has been used
+            # for building a new Session
+            self.plugin.publish_bundle(self.account)
+            return key
+        except UntrustedIdentityException as e:
+            log.info(self.account + " => Received WhisperMessage " +
+                     "from Untrusted Fingerprint! => " + e.getName())
+
+    def handleWhisperMessage(self, recipient_id, device_id, key):
+        whisperMessage = WhisperMessage(serialized=key)
+        sessionCipher = self.get_session_cipher(recipient_id, device_id)
+        log.debug(self.account + " => Received WhisperMessage from " +
+                  recipient_id)
+        if self.isTrusted(sessionCipher) >= TRUSTED:
+            key = sessionCipher.decryptMsg(whisperMessage, textMsg=False)
+            return key
+        else:
+            raise Exception("Received WhisperMessage "
+                            "from Untrusted Fingerprint! => " + recipient_id)
+
+    def checkPreKeyAmount(self):
+        # Check if enough PreKeys are available
+        preKeyCount = self.store.preKeyStore.getPreKeyCount()
+        if preKeyCount < MIN_PREKEY_AMOUNT:
+            newKeys = DEFAULT_PREKEY_AMOUNT - preKeyCount
+            self.store.preKeyStore.generateNewPreKeys(newKeys)
+            log.info(self.account + ' => ' + str(newKeys) +
+                     ' PreKeys created')
+
+    def cycleSignedPreKey(self, identityKeyPair):
+        # Publish every SPK_CYCLE_TIME a new SignedPreKey
+        # Delete all exsiting SignedPreKeys that are older
+        # then SPK_ARCHIVE_TIME
+
+        # Check if SignedPreKey exist and create if not
+        if not self.store.getCurrentSignedPreKeyId():
+            signedPreKey = KeyHelper.generateSignedPreKey(
+                identityKeyPair, self.store.getNextSignedPreKeyId())
+            self.store.storeSignedPreKey(signedPreKey.getId(), signedPreKey)
+            log.debug(self.account +
+                      ' => New SignedPreKey created, because none existed')
+
+        # if SPK_CYCLE_TIME is reached, generate a new SignedPreKey
+        now = int(time.time())
+        timestamp = self.store.getSignedPreKeyTimestamp(
+            self.store.getCurrentSignedPreKeyId())
+
+        if int(timestamp) < now - SPK_CYCLE_TIME:
+            signedPreKey = KeyHelper.generateSignedPreKey(
+                identityKeyPair, self.store.getNextSignedPreKeyId())
+            self.store.storeSignedPreKey(signedPreKey.getId(), signedPreKey)
+            log.debug(self.account + ' => Cycled SignedPreKey')
+
+        # Delete all SignedPreKeys that are older than SPK_ARCHIVE_TIME
+        timestamp = now - SPK_ARCHIVE_TIME
+        self.store.removeOldSignedPreKeys(timestamp)
diff --git a/omemo/omemo16x16.png b/omemo/omemo16x16.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa97d6ea5c05e09d1849c0cad22c7ebf4cf08f41
GIT binary patch
literal 816
zcmV-01JC@4P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00006VoOIv0RI60
z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru;0F>23K;@7<J15E0=-E@
zK~y-)jgxO^%yAsYU%!7l=g#bI%xyn5!_3*goPU!T+CpkJ(QJq^O5}kIA*o483S}Wr
zNIX$S%%2&G+J;583p4+X-EMBi?QXZ*{k|ToWe<?|llOzq`~B(DyU!c`0gJ}Pu`zgo
zb^I^JsQo-Uk(3AP5uP%{p*#dIvwtF4#@Q&&Nn+U=C$Z{a^PKs)Jh*p<f)u%Ut;;w!
z!VLZ}ES&^C`}CS%{M_@=4M$4$%)|UT5L@dRyE0lDFpnGx-T>?`KUk5!FlSS!xtfG0
z`-!sx65Q$-&ytE3P8T^4eIZtydTYduu({=d1>OJ@rBUYmaB@>m)pi0{h}zN0v(Dkj
z5(oh*pk%FLWBPa6$`j>BHI<X)K^K@yr&69b@*^`vE!-%h=2$7p+B^6|py^ovWabng
z1f<6J(XSQC^0Wl<jxA=nD}iD?8k0!|132e~h2l0DS(&t<)2o<47#MBODZB{YfXWBQ
zVo6Q5adT@nvNZ}!3dj=lM&MLFL?Q+qpIYcrYz%Fw<lR1<Y`G1)1@8M(DLGt)8a9Xs
zfo#_LjRUDhjNdH!LgeV(2+>VgKu;JJncaMT(GZfGJiL??NRNY0Kj3a>5~>7}1m142
z>*E*_@&RB7cp6M5-zoQ*G(2yb-H#PtkQ-e=teFmC8opr4jqlQ;R0xKl-g$_hon9t>
zK8vQnm3MaLpZL-{<Y9TMrir?5^_7=Y;Qi4$HXN-3!1T7qUb`jTH>Hs@618?5)jP3R
zL6v~fGiZEL+y1`YXmSkW`mFI(OM^dgxIU4t)@F***|s#5-N`m$B?){1e6Koay5LDW
z^Z1#&(i+9a;tZ+^oizWo6IH9J>E4TYz8P_~gv)=BDF5ozu&-r^0}Jpkn!v7(orqEw
uuK#YND;`U!i{pR}Feiy?8Kc<oC+#mnjsf7$J>b0n0000<MNUMnLSTZtU3e`3

literal 0
HcmV?d00001

diff --git a/omemo/pkgs/PKGBUILD b/omemo/pkgs/PKGBUILD
new file mode 100644
index 00000000..21ce4831
--- /dev/null
+++ b/omemo/pkgs/PKGBUILD
@@ -0,0 +1,24 @@
+# Maintainer: Tommaso Sardelli <lacapannadelloziotom AT gmail DOT com>
+
+pkgname=gajim-plugin-omemo
+_pkgname=gajim-omemo
+pkgver=0.8.1
+pkgrel=2
+pkgdesc="Gajim plugin for OMEMO Multi-End Message and Object Encryption."
+arch=(any)
+url="https://github.com/omemo/${_pkgname}"
+license=('GPL')
+depends=("gajim" "python2-setuptools" "python2-cryptography" "python2-axolotl-git")
+provides=('gajim-plugin-omemo')
+conflicts=('gajim-plugin-omemo-git')
+source=("https://github.com/omemo/${_pkgname}/archive/${pkgver}.tar.gz")
+sha512sums=('e9280033fbe111f5010f2e9e8fa32c5b8c0abe308000f9a043a1c5e8215c96f8be434876b1d72cc8d68aed4ddaebe9655c70f9648a2db718cba71d90434fee2e')
+
+package() {
+  cd $srcdir/gajim-omemo-${pkgver}
+  rm -r CHANGELOG COPYING doc pkgs README.md
+  install -d ${pkgdir}/usr/share/gajim/plugins/omemo
+  cp -r * ${pkgdir}/usr/share/gajim/plugins/omemo/
+}
+
+# vim:set ts=2 sw=2 et:
diff --git a/omemo/setup.cfg b/omemo/setup.cfg
new file mode 100644
index 00000000..2cd96ccd
--- /dev/null
+++ b/omemo/setup.cfg
@@ -0,0 +1,2 @@
+[flake8]
+max-line-length=80
diff --git a/omemo/ui.py b/omemo/ui.py
new file mode 100644
index 00000000..e81813af
--- /dev/null
+++ b/omemo/ui.py
@@ -0,0 +1,619 @@
+# -*- 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/>.
+#
+
+import binascii
+import logging
+
+from gi.repository import GObject
+from gi.repository import Gtk
+
+# pylint: disable=import-error
+import gtkgui_helpers
+from common import gajim
+from plugins.gui import GajimPluginConfigDialog
+# pylint: enable=import-error
+
+log = logging.getLogger('gajim.plugin_system.omemo')
+
+UNDECIDED = 2
+TRUSTED = 1
+UNTRUSTED = 0
+
+
+class OmemoButton(Gtk.Button):
+    def __init__(self, plugin, chat_control, ui, enabled):
+        super(OmemoButton, self).__init__(label=None, stock=None)
+        self.chat_control = chat_control
+
+        self.set_property('relief', Gtk.ReliefStyle.NONE)
+        self.set_property('can-focus', False)
+        self.set_sensitive(True)
+
+        icon = Gtk.Image.new_from_file(
+            plugin.local_file_path('omemo16x16.png'))
+        self.set_image(icon)
+        self.set_tooltip_text('OMEMO Encryption')
+
+        self.connect('clicked', self.on_click)
+
+        self.menu = OmemoMenu(ui, enabled)
+
+    def on_click(self, widget):
+        """
+        Popup omemo menu
+        """
+        gtkgui_helpers.popup_emoticons_under_button(
+            self.menu, widget, self.chat_control.parent_win)
+
+    def set_omemo_state(self, state):
+        self.menu.set_omemo_state(state)
+
+
+class OmemoMenu(Gtk.Menu):
+    def __init__(self, ui, enabled):
+        super(OmemoMenu, self).__init__()
+        self.ui = ui
+
+        self.item_omemo_state = Gtk.CheckMenuItem('Activate OMEMO')
+        self.item_omemo_state.set_active(enabled)
+        self.item_omemo_state.connect('activate', self.on_toggle_omemo)
+        self.append(self.item_omemo_state)
+
+        item = Gtk.ImageMenuItem('Fingerprints')
+        icon = Gtk.Image.new_from_stock(Gtk.STOCK_DIALOG_AUTHENTICATION,
+                                        Gtk.IconSize.MENU)
+        item.set_image(icon)
+        item.connect('activate', self.on_open_fingerprint_window)
+        self.append(item)
+
+        self.show_all()
+
+    def on_toggle_omemo(self, widget):
+        self.ui.set_omemo_state(widget.get_active())
+
+    def on_open_fingerprint_window(self, widget):
+        self.ui.show_fingerprint_window()
+
+    def set_omemo_state(self, state):
+        self.item_omemo_state.handler_block_by_func(self.on_toggle_omemo)
+        self.item_omemo_state.set_active(state)
+        self.item_omemo_state.handler_unblock_by_func(self.on_toggle_omemo)
+
+
+class Ui(object):
+    def __init__(self, plugin, chat_control, enabled, state):
+        self.contact = chat_control.contact
+        self.chat_control = chat_control
+        self.plugin = plugin
+        self.state = state
+        self.account = self.contact.account.name
+        self.windowinstances = {}
+
+        self.display_omemo_state()
+        self.refresh_auth_lock_icon()
+
+        self.omemobutton = OmemoButton(plugin, chat_control, self, enabled)
+
+        self.actions_hbox = chat_control.xml.get_object('actions_hbox')
+        send_button = chat_control.xml.get_object('send_button')
+        send_button_pos = self.actions_hbox.child_get_property(send_button,
+                                                               'position')
+        self.actions_hbox.add(self.omemobutton)
+        self.actions_hbox.reorder_child(self.omemobutton, send_button_pos - 1)
+        self.omemobutton.show_all()
+
+        # add a OMEMO entry to the context/advanced menu
+        self.chat_control.omemo_orig_prepare_context_menu = \
+            self.chat_control.prepare_context_menu
+
+        def omemo_prepare_context_menu(hide_buttonbar_items=False):
+            menu = self.chat_control. \
+                omemo_orig_prepare_context_menu(hide_buttonbar_items)
+            submenu = OmemoMenu(self, self.encryption_active())
+
+            item = Gtk.ImageMenuItem('OMEMO Encryption')
+            icon_path = plugin.local_file_path('omemo16x16.png')
+            item.set_image(Gtk.Image.new_from_file(icon_path))
+            item.set_submenu(submenu)
+
+            # at index 8 is the separator after the esession encryption entry
+            menu.insert(item, 8)
+            return menu
+        self.chat_control.prepare_context_menu = omemo_prepare_context_menu
+
+        # Hook into Send Button so we can check Stuff before sending
+        self.chat_control.orig_send_message = \
+            self.chat_control.send_message
+
+        def omemo_send_message(message, keyID='', chatstate=None, xhtml=None,
+                               process_commands=True, attention=False):
+            self.new_fingerprints_available()
+            if self.encryption_active() and \
+                    self.plugin.are_keys_missing(self.account,
+                                                 self.contact.jid):
+
+                log.debug(self.account + ' => No Trusted Fingerprints for ' +
+                          self.contact.jid)
+                self.no_trusted_fingerprints_warning()
+            else:
+                self.chat_control.orig_send_message(message, keyID, chatstate,
+                                                    xhtml, process_commands,
+                                                    attention)
+                log.debug(self.account + ' => Sending Message to ' +
+                          self.contact.jid)
+
+        self.chat_control.send_message = omemo_send_message
+
+    def set_omemo_state(self, enabled):
+        """
+        Enable or disable OMEMO for this window's contact and update the
+        window ui accordingly
+        """
+        if enabled:
+            log.debug(self.contact.account.name + ' => Enable OMEMO for ' +
+                      self.contact.jid)
+            self.plugin.omemo_enable_for(self.contact.jid,
+                                         self.contact.account.name)
+            self.refresh_auth_lock_icon()
+        else:
+            log.debug(self.contact.account.name + ' => Disable OMEMO for ' +
+                      self.contact.jid)
+            self.plugin.omemo_disable_for(self.contact.jid,
+                                          self.contact.account.name)
+            self.refresh_auth_lock_icon()
+
+        self.omemobutton.set_omemo_state(enabled)
+        self.display_omemo_state()
+
+    def encryption_active(self):
+        return self.state.encryption.is_active(self.contact.jid)
+
+    def activate_omemo(self):
+        if not self.encryption_active():
+            self.set_omemo_state(True)
+
+    def new_fingerprints_available(self):
+        fingerprints = self.state.store.getNewFingerprints(self.contact.jid)
+        if fingerprints:
+            self.show_fingerprint_window(fingerprints)
+
+    def show_fingerprint_window(self, fingerprints=None):
+        if 'dialog' not in self.windowinstances:
+            self.windowinstances['dialog'] = \
+                FingerprintWindow(self.plugin, self.contact,
+                                  self.chat_control.parent_win.window,
+                                  self.windowinstances)
+            self.windowinstances['dialog'].show_all()
+            if fingerprints:
+                log.debug(self.account +
+                          ' => Showing Fingerprint Prompt for ' +
+                          self.contact.jid)
+                self.state.store.setShownFingerprints(fingerprints)
+        else:
+            self.windowinstances['dialog'].update_context_list()
+            if fingerprints:
+                self.state.store.setShownFingerprints(fingerprints)
+
+    def plain_warning(self):
+        self.chat_control.print_conversation_line(
+            'Received plaintext message! ' +
+            'Your next message will still be encrypted!', 'status', '', None)
+
+    def display_omemo_state(self):
+        if self.encryption_active():
+            msg = u'OMEMO encryption enabled'
+        else:
+            msg = u'OMEMO encryption disabled'
+        self.chat_control.print_conversation_line(msg, 'status', '', None)
+
+    def no_trusted_fingerprints_warning(self):
+        msg = "To send an encrypted message, you have to " \
+                          "first trust the fingerprint of your contact!"
+        self.chat_control.print_conversation_line(msg, 'status', '', None)
+
+    def refresh_auth_lock_icon(self):
+        if self.encryption_active():
+            if self.state.getUndecidedFingerprints(self.contact.jid):
+                self.chat_control._show_lock_image(True, 'OMEMO', True, True,
+                                                   False)
+            else:
+                self.chat_control._show_lock_image(True, 'OMEMO', True, True,
+                                                   True)
+        else:
+            self.chat_control._show_lock_image(False, 'OMEMO', False, True,
+                                               False)
+
+    def removeUi(self):
+        self.actions_hbox.remove(self.omemobutton)
+        self.chat_control.prepare_context_menu = \
+            self.chat_control.omemo_orig_prepare_context_menu
+        self.chat_control.send_message = self.chat_control.orig_send_message
+
+
+class OMEMOConfigDialog(GajimPluginConfigDialog):
+    def init(self):
+        # pylint: disable=attribute-defined-outside-init
+        self.GTK_BUILDER_FILE_PATH = \
+            self.plugin.local_file_path('config_dialog.ui')
+        self.B = Gtk.Builder()
+        self.B.set_translation_domain('gajim_plugins')
+        self.B.add_from_file(self.GTK_BUILDER_FILE_PATH)
+
+        self.fpr_model = Gtk.ListStore(GObject.TYPE_INT,
+                                       GObject.TYPE_STRING,
+                                       GObject.TYPE_STRING,
+                                       GObject.TYPE_STRING)
+
+        self.device_model = Gtk.ListStore(GObject.TYPE_INT)
+
+        self.account_store = self.B.get_object('account_store')
+
+        for account in sorted(gajim.contacts.get_accounts()):
+            self.account_store.append(row=(account,))
+
+        self.fpr_view = self.B.get_object('fingerprint_view')
+        self.fpr_view.set_model(self.fpr_model)
+        self.fpr_view.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
+
+        self.device_view = self.B.get_object('deviceid_view')
+        self.device_view.set_model(self.device_model)
+
+        if len(self.account_store) > 0:
+            self.B.get_object('account_combobox').set_active(0)
+
+        vbox = self.get_content_area()
+        vbox.pack_start(self.B.get_object('notebook1'), True, True, 0)
+
+        self.B.connect_signals(self)
+
+    def on_run(self):
+        self.update_context_list()
+        self.account_combobox_changed_cb(self.B.get_object('account_combobox'))
+
+    def account_combobox_changed_cb(self, box, *args):
+        self.update_context_list()
+
+    def trust_button_clicked_cb(self, button, *args):
+        active = self.B.get_object('account_combobox').get_active()
+        account = self.account_store[active][0]
+
+        state = self.plugin.get_omemo_state(account)
+
+        mod, paths = self.fpr_view.get_selection().get_selected_rows()
+
+        for path in paths:
+            it = mod.get_iter(path)
+            _id, user, fpr = mod.get(it, 0, 1, 3)
+            fpr = fpr[31:-12]
+            dlg = Gtk.Dialog('Trust / Revoke Fingerprint', self,
+                             Gtk.DialogFlags.MODAL |
+                             Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                             (Gtk.STOCK_YES, Gtk.ResponseType.YES,
+                              Gtk.STOCK_NO, Gtk.ResponseType.NO))
+            l = Gtk.Label()
+            l.set_markup('Do you want to trust the '
+                         'fingerprint of <b>%s</b> on your account <b>%s</b>?'
+                         '\n\n<tt>%s</tt>' % (user, account, fpr))
+            l.set_line_wrap(True)
+            l.set_padding(12, 12)
+            vbox = dlg.get_content_area()
+            vbox.add(l)
+            dlg.show_all()
+
+            response = dlg.run()
+            if response == Gtk.ResponseType.YES:
+                state.store.identityKeyStore.setTrust(_id, TRUSTED)
+                try:
+                    if self.plugin.ui_list[account]:
+                        self.plugin.ui_list[account][user].refresh_auth_lock_icon()
+                except:
+                    dlg.destroy()
+            else:
+                if response == Gtk.ResponseType.NO:
+                    state.store.identityKeyStore.setTrust(_id, UNTRUSTED)
+                    try:
+                        if user in self.plugin.ui_list[account]:
+                            self.plugin.ui_list[account][user].refresh_auth_lock_icon()
+                    except:
+                        dlg.destroy()
+
+        self.update_context_list()
+
+    def cleardevice_button_clicked_cb(self, button, *args):
+        active = self.B.get_object('account_combobox').get_active()
+        account = self.account_store[active][0]
+        self.plugin.clear_device_list(account)
+        self.update_context_list()
+
+    def refresh_button_clicked_cb(self, button, *args):
+        self.update_context_list()
+
+    def fpr_button_pressed_cb(self, tw, event):
+        if event.button == 3:
+            pthinfo = tw.get_path_at_pos(int(event.x), int(event.y))
+
+            if pthinfo is None:
+                # only show the popup when we right clicked on list content
+                # ie. don't show it when we click at empty rows
+                return False
+
+            # if the row under the mouse is already selected, we keep the
+            # selection, otherwise we only select the new item
+            keep_selection = tw.get_selection().path_is_selected(pthinfo[0])
+
+            pop = self.B.get_object('fprclipboard_menu')
+            pop.popup(None, None, None, event.button, event.time)
+
+            # keep_selection=True -> no further processing of click event
+            # keep_selection=False-> further processing -> GTK usually selects
+            #   the item below the cursor
+            return keep_selection
+
+    def clipboard_button_cb(self, menuitem):
+        mod, paths = self.fpr_view.get_selection().get_selected_rows()
+
+        fprs = []
+        for path in paths:
+            it = mod.get_iter(path)
+            jid, fpr = mod.get(it, 1, 3)
+            fprs.append('%s: %s' % (jid, fpr[4:-5]))
+        Gtk.Clipboard().set_text('\n'.join(fprs))
+        Gtk.Clipboard(selection='PRIMARY').set_text('\n'.join(fprs))
+
+    def update_context_list(self):
+        self.fpr_model.clear()
+        self.device_model.clear()
+        active = self.B.get_object('account_combobox').get_active()
+        account = self.account_store[active][0]
+        state = self.plugin.get_omemo_state(account)
+
+        deviceid = state.own_device_id
+        self.B.get_object('ID').set_markup('<tt>%s</tt>' % deviceid)
+
+        ownfpr = binascii.hexlify(state.store.getIdentityKeyPair()
+                                  .getPublicKey().serialize()).decode('utf-8')
+        ownfpr = self.human_hash(ownfpr[2:])
+        self.B.get_object('fingerprint_label').set_markup('<tt>%s</tt>'
+                                                          % ownfpr)
+
+        fprDB = state.store.identityKeyStore.getAllFingerprints()
+        activeSessions = state.store.sessionStore. \
+            getAllActiveSessionsKeys()
+        for item in fprDB:
+            _id, jid, fpr, tr = item
+            active = fpr in activeSessions
+            fpr = binascii.hexlify(fpr).decode('utf-8')
+            fpr = self.human_hash(fpr[2:])
+            jid = jid.decode('utf-8')
+            if tr == UNTRUSTED:
+                if active:
+                    self.fpr_model.append((_id, jid, 'False',
+                                           '<tt><span foreground="#FF0040">%s</span></tt>' % fpr))
+                else:
+                    self.fpr_model.append((_id, jid, 'False',
+                                           '<tt><span foreground="#585858">%s</span></tt>' % fpr))
+            elif tr == TRUSTED:
+                if active:
+                    self.fpr_model.append((_id, jid, 'True',
+                                           '<tt><span foreground="#2EFE2E">%s</span></tt>' % fpr))
+                else:
+                    self.fpr_model.append((_id, jid, 'True',
+                                           '<tt><span foreground="#585858">%s</span></tt>' % fpr))
+            else:
+                if active:
+                    self.fpr_model.append((_id, jid, 'Undecided',
+                                           '<tt><span foreground="#FF8000">%s</span></tt>' % fpr))
+                else:
+                    self.fpr_model.append((_id, jid, 'Undecided',
+                                           '<tt><span foreground="#585858">%s</span></tt>' % fpr))
+
+        for item in state.own_devices:
+            self.device_model.append([item])
+
+    def human_hash(self, fpr):
+        fpr = fpr.upper()
+        fplen = len(fpr)
+        wordsize = fplen // 8
+        buf = ''
+        for w in range(0, fplen, wordsize):
+            buf += '{0} '.format(fpr[w:w + wordsize])
+        return buf.rstrip()
+
+
+class FingerprintWindow(Gtk.Dialog):
+    def __init__(self, plugin, contact, parent, windowinstances):
+        self.contact = contact
+        self.windowinstances = windowinstances
+        Gtk.Dialog.__init__(self,
+                            title=('Fingerprints for %s') % contact.jid,
+                            parent=parent,
+                            flags=Gtk.DialogFlags.DESTROY_WITH_PARENT)
+        close_button = self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
+        close_button.connect('clicked', self.on_close_button_clicked)
+        self.connect('delete-event', self.on_window_delete)
+        self.plugin = plugin
+        self.GTK_BUILDER_FILE_PATH = \
+            self.plugin.local_file_path('fpr_dialog.ui')
+        self.B = Gtk.Builder()
+        self.B.set_translation_domain('gajim_plugins')
+        self.B.add_from_file(self.GTK_BUILDER_FILE_PATH)
+
+        self.fpr_model = Gtk.ListStore(GObject.TYPE_INT,
+                                       GObject.TYPE_STRING,
+                                       GObject.TYPE_STRING,
+                                       GObject.TYPE_STRING)
+
+        self.fpr_view = self.B.get_object('fingerprint_view')
+        self.fpr_view.set_model(self.fpr_model)
+        self.fpr_view.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
+
+        self.fpr_view_own = self.B.get_object('fingerprint_view_own')
+        self.fpr_view_own.set_model(self.fpr_model)
+        self.fpr_view_own.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
+
+        self.notebook = self.B.get_object('notebook1')
+
+        vbox = self.get_content_area()
+        vbox.pack_start(self.notebook, True, True, 0)
+
+        self.B.connect_signals(self)
+
+        self.account = self.contact.account.name
+        self.omemostate = self.plugin.get_omemo_state(self.account)
+
+        ownfpr = binascii.hexlify(self.omemostate.store.getIdentityKeyPair()
+                                  .getPublicKey().serialize()).decode('utf-8')
+        ownfpr = self.human_hash(ownfpr[2:])
+
+        self.B.get_object('fingerprint_label_own').set_markup('<tt>%s</tt>'
+                                                              % ownfpr)
+
+        self.update_context_list()
+
+    def on_close_button_clicked(self, widget):
+        del self.windowinstances['dialog']
+        self.hide()
+
+    def on_window_delete(self, widget, event):
+        del self.windowinstances['dialog']
+        self.hide()
+
+    def trust_button_clicked_cb(self, button, *args):
+        if self.notebook.get_current_page() == 1:
+            mod, paths = self.fpr_view_own.get_selection().get_selected_rows()
+        else:
+            mod, paths = self.fpr_view.get_selection().get_selected_rows()
+
+        for path in paths:
+            it = mod.get_iter(path)
+            _id, user, fpr = mod.get(it, 0, 1, 3)
+            fpr = fpr[31:-12]
+            dlg = Gtk.Dialog('Trust / Revoke Fingerprint', self,
+                             Gtk.DialogFlags.MODAL |
+                             Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                             (Gtk.STOCK_YES, Gtk.ResponseType.YES,
+                              Gtk.STOCK_NO, Gtk.ResponseType.NO))
+            l = Gtk.Label()
+            l.set_markup('Do you want to trust the '
+                         'fingerprint of <b>%s</b> on your account <b>%s</b>?'
+                         '\n\n<tt>%s</tt>' % (user, self.account, fpr))
+            l.set_line_wrap(True)
+            l.set_padding(12, 12)
+            vbox = dlg.get_content_area()
+            vbox.add(l)
+            dlg.show_all()
+            response = dlg.run()
+            if response == Gtk.ResponseType.YES:
+                self.omemostate.store.identityKeyStore.setTrust(_id, TRUSTED)
+                self.plugin.ui_list[self.account][self.contact.jid]. \
+                    refresh_auth_lock_icon()
+                dlg.destroy()
+            else:
+                if response == Gtk.ResponseType.NO:
+                    self.omemostate.store.identityKeyStore.setTrust(_id, UNTRUSTED)
+                    self.plugin.ui_list[self.account][self.contact.jid]. \
+                        refresh_auth_lock_icon()
+            dlg.destroy()
+
+        self.update_context_list()
+
+    def fpr_button_pressed_cb(self, tw, event):
+        if event.button == 3:
+            pthinfo = tw.get_path_at_pos(int(event.x), int(event.y))
+
+            if pthinfo is None:
+                # only show the popup when we right clicked on list content
+                # ie. don't show it when we click at empty rows
+                return False
+
+            # if the row under the mouse is already selected, we keep the
+            # selection, otherwise we only select the new item
+            keep_selection = tw.get_selection().path_is_selected(pthinfo[0])
+
+            pop = self.B.get_object('fprclipboard_menu')
+            pop.popup(None, None, None, event.button, event.time)
+
+            # keep_selection=True -> no further processing of click event
+            # keep_selection=False-> further processing -> GTK usually selects
+            #   the item below the cursor
+            return keep_selection
+
+    def clipboard_button_cb(self, menuitem):
+        if self.notebook.get_current_page() == 1:
+            mod, paths = self.fpr_view_own.get_selection().get_selected_rows()
+        else:
+            mod, paths = self.fpr_view.get_selection().get_selected_rows()
+
+        fprs = []
+        for path in paths:
+            it = mod.get_iter(path)
+            jid, fpr = mod.get(it, 1, 3)
+            fprs.append('%s: %s' % (jid, fpr[31:-12]))
+        Gtk.Clipboard().set_text('\n'.join(fprs))
+        Gtk.Clipboard(selection='PRIMARY').set_text('\n'.join(fprs))
+
+    def update_context_list(self, *args):
+        self.fpr_model.clear()
+
+        if self.notebook.get_current_page() == 1:
+            jid = gajim.get_jid_from_account(self.account)
+        else:
+            jid = self.contact.jid
+
+        fprDB = self.omemostate.store.identityKeyStore.getFingerprints(jid)
+        activeSessions = self.omemostate.store.sessionStore. \
+            getActiveSessionsKeys(jid)
+
+        for item in fprDB:
+            _id, jid, fpr, tr = item
+            active = fpr in activeSessions
+            fpr = binascii.hexlify(fpr).decode('utf-8')
+            fpr = self.human_hash(fpr[2:])
+            jid = jid.decode('utf-8')
+            if tr == UNTRUSTED:
+                if active:
+                    self.fpr_model.append((_id, jid, 'False',
+                                           '<tt><span foreground="#FF0040">%s</span></tt>' % fpr))
+                else:
+                    self.fpr_model.append((_id, jid, 'False',
+                                           '<tt><span foreground="#585858">%s</span></tt>' % fpr))
+            elif tr == TRUSTED:
+                if active:
+                    self.fpr_model.append((_id, jid, 'True',
+                                           '<tt><span foreground="#2EFE2E">%s</span></tt>' % fpr))
+                else:
+                    self.fpr_model.append((_id, jid, 'True',
+                                           '<tt><span foreground="#585858">%s</span></tt>' % fpr))
+            else:
+                if active:
+                    self.fpr_model.append((_id, jid, 'Undecided',
+                                           '<tt><span foreground="#FF8000">%s</span></tt>' % fpr))
+                else:
+                    self.fpr_model.append((_id, jid, 'Undecided',
+                                           '<tt><span foreground="#585858">%s</span></tt>' % fpr))
+
+    def human_hash(self, fpr):
+        fpr = fpr.upper()
+        fplen = len(fpr)
+        wordsize = fplen // 8
+        buf = ''
+        for w in range(0, fplen, wordsize):
+            buf += '{0} '.format(fpr[w:w + wordsize])
+        return buf.rstrip()
diff --git a/omemo/xmpp.py b/omemo/xmpp.py
new file mode 100644
index 00000000..c4097a51
--- /dev/null
+++ b/omemo/xmpp.py
@@ -0,0 +1,346 @@
+# -*- 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 geting 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 common import gajim  # pylint: disable=import-error
+from common.pep import AbstractPEP  # pylint: disable=import-error
+from 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:'
+log = logging.getLogger('gajim.plugin_system.omemo')
+
+
+class PublishNode(Node):
+    def __init__(self, node_str, data):
+        assert node_str is not None and isinstance(data, Node)
+        Node.__init__(self, tag='publish', attrs={'node': node_str})
+        self.addChild('item').addChild(node=data)
+
+
+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 DeviceListAnnouncement(Iq):
+    def __init__(self, device_list):
+        assert isinstance(device_list, list)
+        assert len(device_list) > 0
+        id_ = gajim.get_an_id()
+        attrs = {'id': id_}
+        Iq.__init__(self, typ='set', attrs=attrs)
+
+        list_node = Node('list', attrs={'xmlns': NS_OMEMO})
+        for device in device_list:
+            list_node.addChild('device').setAttr('id', device)
+
+        publish = PublishNode(NS_DEVICE_LIST, list_node)
+        pubsub = PubsubNode(publish)
+
+        self.addChild(node=pubsub)
+
+
+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 in msg_dict['keys'].items():
+            header.addChild('key', attrs={'rid': rid}).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_ = gajim.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)})
+        pubsub = PubsubNode(items)
+        self.addChild(node=pubsub)
+
+
+class BundleInformationAnnouncement(Iq):
+    def __init__(self, state_bundle, device_id):
+        id_ = gajim.get_an_id()
+        attrs = {'id': id_}
+        Iq.__init__(self, typ='set', attrs=attrs)
+        bundle_node = self.make_bundle_node(state_bundle)
+        publish = PublishNode(NS_BUNDLES + str(device_id), bundle_node)
+        pubsub = PubsubNode(publish)
+        self.addChild(node=pubsub)
+
+    def make_bundle_node(self, 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
+
+
+class DevicelistQuery(Iq):
+    def __init__(self, contact_jid,):
+        id_ = gajim.get_an_id()
+        attrs = {'id': id_}
+        Iq.__init__(self, typ='get', attrs=attrs, to=contact_jid)
+        items = Node('items', attrs={'node': NS_DEVICE_LIST})
+        pubsub = PubsubNode(items)
+        self.addChild(node=pubsub)
+
+
+class DevicelistPEP(AbstractPEP):
+    type_ = 'headline'
+    namespace = NS_DEVICE_LIST
+
+    def _extract_info(self, items):
+        return ({}, [])
+
+
+@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.debug(
+            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'
-- 
GitLab